diff --git a/Makefile.am b/Makefile.am index 58c2a5c9faa14b33ebee1b424f614b28fc12513e..b962614c9181c6005c0920cf6c51eed59d1731ec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -98,6 +98,7 @@ LIBRSVG_SRC = \ src/property_defs.rs \ src/property_macros.rs \ src/rect.rs \ + src/session.rs \ src/shapes.rs \ src/space.rs \ src/structure.rs \ diff --git a/devel-docs/api_observability.rst b/devel-docs/api_observability.rst new file mode 100644 index 0000000000000000000000000000000000000000..5a19b424b53c7a302f0784b4ad86dd63700b4aa3 --- /dev/null +++ b/devel-docs/api_observability.rst @@ -0,0 +1,196 @@ +Observability +============= + +Librsvg supports basic, mostly ad-hoc logging with an ``RSVG_LOG=1`` +environment variable. This has not been very effective in letting me, +the maintainer, know what went wrong when someone reports a bug about +librsvg doing the wrong thing in an application. Part of it is because +the code could be more thorough about logging (e.g. log at all error +points), but also part of it is that there is no logging about what API +calls are made into the library. On each bug reported on behalf of a +particular application, my thought process goes something like this: + +- What was the app doing? + +- Can I obtain the problematic SVG? + +- Does the bug reporter even know what the problematic SVG was? + +- Was the app rendering with direct calls to the librsvg API? + +- Or was it using the gdk-pixbuf loader, and thus has very little + control of how librsvg is used? + +- If non-pixbuf, what was the Cairo state when librsvg was called? + +- What sort of interesting API calls are being used? Stylesheet + injection? Re-rendering single elements with different styles? + +And every time, I must ask the bug reporter for information related to +that, or to point me to the relevant source code where they were using +librsvg… which is not terribly useful, since building their code and +reproducing the bug with it is A Yak That Should Not Have To Be Shaved. + +Desiderata +---------- + +Know exactly what an application did with librsvg: + +- All API calls and their parameters. + +- State of the Cairo context at entry. + +- “What SVG?” - be careful and explicit about exfiltrating SVG data to + the logs. + +- Basic platform stuff? Is the platform triple enough? Distro ID? + +- Versions of dependencies. + +- Version of librsvg itself. + +Internals of the library: + +- Regular debug tracing. We may have options to enable/disable tracing + domains: parsing, cascading, referencing elements, temporary surfaces + during filtering, render tree, etc. + +- Log all points where an error is detected/generated, even if it will + be discarded later (e.g. invalid CSS values are silently ignored, per + the spec). + +Enabling logging +---------------- + +It may be useful to be able to enable logging in various ways: + +- Programmatically, for when one has control of the source code of the + problematic application. Enable logging at the problem spot, for the + SVG you know that exhibits the problem, and be done with it. This can + probably be at the individual ``RsvgHandle`` level, not globally. For + global logging within a single process, see the next point. + +- For a single process which one can easily launch via the command + line; e.g. with an environment variable. This works well for + non-sandboxed applications. Something like + ``RSVG_LOG_CONFIG=my_log_config.toml``. + +- With a configuration file, a la ``~/.config/librsvg.toml``. Many + programs use librsvg and you don’t want logs for all of them; allow + the configuration file to specify a process name, or maybe other ways + of determining when to log. For session programs like gnome-shell, + you can’t easily set an environment variable to enable logging - + hence, a configuration file that only turns on logging from the + gnome-shell process. + +All of the above should be well documented, and then we can deprecate +``RSVG_LOG``. + +Which SVG caused a crash? +------------------------- + +Every once in a while, a bug report comes in like “$application crashed +in librsvg”. The application renders many SVGs, often indirectly via +gdk-pixbuf, and it is hard to know exactly which SVG caused the problem. +Think of gnome-shell or gnome-software. + +For applications that call librsvg directly, if they pass the filename +or a GFile then it is not hard to find out the source SVG. + +But for those that feed bytes into librsvg, including those that use it +indirectly via gdk-pixbuf, librsvg has no knowledge of the filename. We +need to use the base_uri then, or see if the pixbuf loader can be +modified to propagate this information (is it even available from the +GdkPixbufLoader machinery?). + +If all else fails, we can have an exfiltration mechanism. How can we +avoid logging *all* the SVG data that gnome-shell renders, for example? +Configure the logger to skip the first N SVGs, and hope that the order +is deterministic? We can’t really “log only if there is a crash during +rendering”. + +Log only the checksums of SVGs or data lengths, and use that to find +which SVG caused the crash? I.e. have the user use a two-step process to +find a crash: get a log (written synchronously) of all SVG +checksums/lengths, and then reconfigure the logger to only exfiltrate +the last one that got logged - presumably that one caused the crash. + +Which dynamically-created SVG caused a problem? +----------------------------------------------- + +Consider a bug like +https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5415 where an +application dynamically generates an SVG and feeds it to librsvg. That +bug was not a crash; it was about incorrect values returned from an +librsvg API function. For those cases it may be useful to be able to +exfiltrate an SVG and its stylesheets only if it matches a user-provided +substring. + +Global configuration +-------------------- + +``$(XDG_CONFIG_HOME)/librsvg.toml`` - for startup-like processes like +gnome-shell, for which it is hard to set an environment variable: + +Per-process configuration +------------------------- + +``RSVG_LOG_CONFIG=my_log_config.toml my_process`` + +Programmatic API +---------------- + +FIXME + +Configuration format +-------------------- + +.. code:: toml + + [logging] + enabled=true + process=gnome-shell # mandatory for global config - don't want to log all processes - warn to g_log if key is not set + output=/home/username/rsvg.log # if missing, log to g_log only - or use a output_to_g_log=true instead? + +API logging +----------- + +Log cr state at entry, surface type, starting transform. + +Log name/base_uri of rendered document. + +Can we know if it is a gresource? Or a byte buffer? Did it come from +gdk-pixbuf? + +Log contents +------------ + +/home/username/rsvg.log - json doesn’t have comments; put one of these +in a string somehow: + +:: + + ****************************************************************************** + * This log file exists because you enabled logging in ~/.config/librsvg.toml * + * for the "gnome-shell" process. * + * * + * If you want to disable this kind of log, please turn it off in that file * + * or delete that file entirely. * + ****************************************************************************** + + ****************************************************************************** + * This log file exists because you enabled logging with * + * RSVG_LOG_CONFIG=config.toml for the "single-process-name" process. * + * * + * If you want to disable this kind of log, FIXME */ + ****************************************************************************** + +To-do list +---------- + +- Audit code for GIO errors; log there. + +- Audit code for Cairo calls that yield errors; log there. + +- Log the entire ancestry of the element that caused the error? Is + that an insta-reproducer? diff --git a/devel-docs/index.rst b/devel-docs/index.rst index ddb1f68b4280b313a1a9d58736c432f62e27608f..0d8f08d0aa781c98685cfbdc2fe5263bf63a03c5 100644 --- a/devel-docs/index.rst +++ b/devel-docs/index.rst @@ -8,6 +8,7 @@ Development guide for librsvg contributing ci text_layout + api_observability :maxdepth: 1 :caption: Contents: @@ -61,13 +62,12 @@ request. We can then discuss it before coding. This way we will have a sort of big-picture development history apart from commit messages. - :doc:`text_layout` +- :doc:`api_observability` See https://rustc-dev-guide.rust-lang.org/walkthrough.html, section Overview, to formalize the RFC process for features vs. drive-by contributions. -FIXME: link the md here. - Information for maintainers --------------------------- diff --git a/src/accept_language.rs b/src/accept_language.rs index d980c21f6a96071dce33b1f75b12959b4f2bc007..172c3e859f5f85aa34031663991cd98ab88394be 100644 --- a/src/accept_language.rs +++ b/src/accept_language.rs @@ -221,20 +221,6 @@ impl LanguageTags { } impl UserLanguage { - pub fn new(language: &Language) -> UserLanguage { - match *language { - Language::FromEnvironment => UserLanguage::LanguageTags( - LanguageTags::from_locale(&locale_from_environment()) - .map_err(|s| { - rsvg_log!("could not convert locale to language tags: {}", s); - }) - .unwrap_or_else(|_| LanguageTags::empty()), - ), - - Language::AcceptLanguage(ref a) => UserLanguage::AcceptLanguage(a.clone()), - } - } - pub fn any_matches(&self, tags: &LanguageTags) -> bool { match *self { UserLanguage::LanguageTags(ref language_tags) => { @@ -247,28 +233,6 @@ impl UserLanguage { } } -/// Gets the user's preferred locale from the environment and -/// translates it to a `Locale` with `LanguageRange` fallbacks. -/// -/// The `Locale::current()` call only contemplates a single language, -/// but glib is smarter, and `g_get_langauge_names()` can provide -/// fallbacks, for example, when LC_MESSAGES="en_US.UTF-8:de" (USA -/// English and German). This function converts the output of -/// `g_get_language_names()` into a `Locale` with appropriate -/// fallbacks. -fn locale_from_environment() -> Locale { - let mut locale = Locale::invariant(); - - for name in glib::language_names() { - let name = name.as_str(); - if let Ok(range) = LanguageRange::from_unix(name) { - locale.add(&range); - } - } - - locale -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/api.rs b/src/api.rs index 0ab0c51db82033a22626d9332505a8a90b062bc3..38bb0bc4e5b31241803c0fe601a420ef8d39da7b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -5,7 +5,7 @@ #![warn(missing_docs)] pub use crate::{ - accept_language::{AcceptLanguage, Language, UserLanguage}, + accept_language::{AcceptLanguage, Language}, error::{ImplementationLimit, LoadingError, RenderingError}, length::{LengthUnit, RsvgLength as Length}, }; @@ -17,9 +17,13 @@ use std::path::Path; use gio::prelude::*; // Re-exposes glib's prelude as well use gio::Cancellable; +use locale_config::{LanguageRange, Locale}; + use crate::{ + accept_language::{LanguageTags, UserLanguage}, dpi::Dpi, handle::{Handle, LoadOptions}, + session::Session, url_resolver::UrlResolver, }; @@ -31,10 +35,10 @@ use crate::{ /// of `Loader` in sequence to configure how SVG data should be /// loaded, and finally use one of the loading functions to load an /// [`SvgHandle`]. -#[derive(Default)] pub struct Loader { unlimited_size: bool, keep_image_data: bool, + session: Session, } impl Loader { @@ -58,7 +62,19 @@ impl Loader { /// .unwrap(); /// ``` pub fn new() -> Self { - Self::default() + Self::new_with_session(Session::default()) + } + + /// Creates a `Loader` from a pre-created [`Session`]. + /// + /// This is useful when a `Loader` must be created by the C API, which should already + /// have created a session for logging. + pub(crate) fn new_with_session(session: Session) -> Self { + Self { + unlimited_size: false, + keep_image_data: false, + session, + } } /// Controls safety limits used in the XML parser. @@ -198,11 +214,15 @@ impl Loader { .with_unlimited_size(self.unlimited_size) .keep_image_data(self.keep_image_data); - Ok(SvgHandle(Handle::from_stream( - &load_options, - stream.as_ref(), - cancellable.map(|c| c.as_ref()), - )?)) + Ok(SvgHandle { + handle: Handle::from_stream( + self.session.clone(), + &load_options, + stream.as_ref(), + cancellable.map(|c| c.as_ref()), + )?, + session: self.session, + }) } } @@ -214,7 +234,10 @@ fn url_from_file(file: &gio::File) -> Result { /// /// You can create this from one of the `read` methods in /// [`Loader`]. -pub struct SvgHandle(pub(crate) Handle); +pub struct SvgHandle { + session: Session, + pub(crate) handle: Handle, +} impl SvgHandle { /// Checks if the SVG has an element with the specified `id`. @@ -225,7 +248,7 @@ impl SvgHandle { /// The purpose of the `Err()` case in the return value is to indicate an /// incorrectly-formatted `id` argument. pub fn has_element_with_id(&self, id: &str) -> Result { - self.0.has_sub(id) + self.handle.has_sub(id) } /// Sets a CSS stylesheet to use for an SVG document. @@ -237,7 +260,7 @@ impl SvgHandle { /// /// [origin]: https://drafts.csswg.org/css-cascade-3/#cascading-origins pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> { - self.0.set_stylesheet(css) + self.handle.set_stylesheet(css) } } @@ -288,6 +311,44 @@ pub struct IntrinsicDimensions { pub vbox: Option, } +/// Gets the user's preferred locale from the environment and +/// translates it to a `Locale` with `LanguageRange` fallbacks. +/// +/// The `Locale::current()` call only contemplates a single language, +/// but glib is smarter, and `g_get_langauge_names()` can provide +/// fallbacks, for example, when LC_MESSAGES="en_US.UTF-8:de" (USA +/// English and German). This function converts the output of +/// `g_get_language_names()` into a `Locale` with appropriate +/// fallbacks. +fn locale_from_environment() -> Locale { + let mut locale = Locale::invariant(); + + for name in glib::language_names() { + let name = name.as_str(); + if let Ok(range) = LanguageRange::from_unix(name) { + locale.add(&range); + } + } + + locale +} + +impl UserLanguage { + fn new(language: &Language, session: &Session) -> UserLanguage { + match *language { + Language::FromEnvironment => UserLanguage::LanguageTags( + LanguageTags::from_locale(&locale_from_environment()) + .map_err(|s| { + rsvg_log!(session, "could not convert locale to language tags: {}", s); + }) + .unwrap_or_else(|_| LanguageTags::empty()), + ), + + Language::AcceptLanguage(ref a) => UserLanguage::AcceptLanguage(a.clone()), + } + } +} + impl<'a> CairoRenderer<'a> { /// Creates a `CairoRenderer` for the specified `SvgHandle`. /// @@ -296,10 +357,12 @@ impl<'a> CairoRenderer<'a> { /// /// [`with_dpi`]: #method.with_dpi pub fn new(handle: &'a SvgHandle) -> Self { + let session = &handle.session; + CairoRenderer { handle, dpi: Dpi::new(DEFAULT_DPI_X, DEFAULT_DPI_Y), - user_language: UserLanguage::new(&Language::FromEnvironment), + user_language: UserLanguage::new(&Language::FromEnvironment, session), is_testing: false, } } @@ -330,7 +393,7 @@ impl<'a> CairoRenderer<'a> { /// be obtained from the program's environment. To set an explicit list of languages, /// you can use `Language::AcceptLanguage` instead. pub fn with_language(self, language: &Language) -> Self { - let user_language = UserLanguage::new(language); + let user_language = UserLanguage::new(language, &self.handle.session); CairoRenderer { user_language, @@ -350,7 +413,7 @@ impl<'a> CairoRenderer<'a> { /// [`render_document`]: #method.render_document /// [`intrinsic_size_in_pixels`]: #method.intrinsic_size_in_pixels pub fn intrinsic_dimensions(&self) -> IntrinsicDimensions { - let d = self.handle.0.get_intrinsic_dimensions(); + let d = self.handle.handle.get_intrinsic_dimensions(); IntrinsicDimensions { width: Into::into(d.width), @@ -383,7 +446,7 @@ impl<'a> CairoRenderer<'a> { return None; } - Some(self.handle.0.width_height_to_user(self.dpi)) + Some(self.handle.handle.width_height_to_user(self.dpi)) } /// Renders the whole SVG document fitted to a viewport @@ -399,9 +462,13 @@ impl<'a> CairoRenderer<'a> { cr: &cairo::Context, viewport: &cairo::Rectangle, ) -> Result<(), RenderingError> { - self.handle - .0 - .render_document(cr, viewport, &self.user_language, self.dpi, self.is_testing) + self.handle.handle.render_document( + cr, + viewport, + &self.user_language, + self.dpi, + self.is_testing, + ) } /// Computes the (ink_rect, logical_rect) of an SVG element, as if @@ -434,7 +501,7 @@ impl<'a> CairoRenderer<'a> { viewport: &cairo::Rectangle, ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> { self.handle - .0 + .handle .get_geometry_for_layer(id, viewport, &self.user_language, self.dpi, self.is_testing) .map(|(i, l)| (i, l)) } @@ -464,7 +531,7 @@ impl<'a> CairoRenderer<'a> { id: Option<&str>, viewport: &cairo::Rectangle, ) -> Result<(), RenderingError> { - self.handle.0.render_layer( + self.handle.handle.render_layer( cr, id, viewport, @@ -509,7 +576,7 @@ impl<'a> CairoRenderer<'a> { id: Option<&str>, ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> { self.handle - .0 + .handle .get_geometry_for_element(id, &self.user_language, self.dpi, self.is_testing) .map(|(i, l)| (i, l)) } @@ -536,7 +603,7 @@ impl<'a> CairoRenderer<'a> { id: Option<&str>, element_viewport: &cairo::Rectangle, ) -> Result<(), RenderingError> { - self.handle.0.render_element( + self.handle.handle.render_element( cr, id, element_viewport, diff --git a/src/c_api/handle.rs b/src/c_api/handle.rs index 3427273c708b48d6bbc61f90b43aa40c41e7aed7..bd1663ac4c36e0a539fa64cb8028df5c118d197a 100644 --- a/src/c_api/handle.rs +++ b/src/c_api/handle.rs @@ -43,6 +43,7 @@ use crate::api::{self, CairoRenderer, IntrinsicDimensions, Loader, LoadingError, use crate::{ length::RsvgLength, rsvg_log, + session::Session, surface_utils::shared_surface::{SharedImageSurface, SurfaceType}, }; @@ -297,6 +298,7 @@ mod imp { pub struct CHandle { pub(super) inner: RefCell, pub(super) load_state: RefCell, + pub(super) session: Session, } #[derive(Default)] @@ -559,6 +561,7 @@ impl CairoRectangleExt for cairo::Rectangle { 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 { @@ -573,13 +576,14 @@ impl CHandle { match Url::parse(url) { Ok(u) => { - rsvg_log!("setting base_uri to \"{}\"", u.as_str()); + 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 @@ -760,9 +764,11 @@ impl CHandle { } fn make_loader(&self) -> Loader { - let inner = self.imp().inner.borrow(); + let imp = self.imp(); + let inner = imp.inner.borrow(); + let session = imp.session.clone(); - Loader::new() + Loader::new_with_session(session) .with_unlimited_size(inner.load_flags.unlimited_size) .keep_image_data(inner.load_flags.keep_image_data) } @@ -1167,18 +1173,23 @@ pub unsafe extern "C" fn rsvg_handle_internal_set_testing( trait IntoGError { type GlibResult; - fn into_gerror(self, error: *mut *mut glib::ffi::GError) -> Self::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, error: *mut *mut glib::ffi::GError) -> Self::GlibResult { + fn into_gerror( + self, + session: &Session, + error: *mut *mut glib::ffi::GError, + ) -> Self::GlibResult { match self { Ok(()) => true.into_glib(), Err(e) => { - set_gerror(error, 0, &format!("{}", e)); + set_gerror(session, error, 0, &format!("{}", e)); false.into_glib() } } @@ -1202,13 +1213,14 @@ pub unsafe extern "C" fn rsvg_handle_read_stream_sync( } 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(error) + .into_gerror(&session, error) } #[no_mangle] @@ -1246,8 +1258,9 @@ pub unsafe extern "C" fn rsvg_handle_close( } let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); - rhandle.close().into_gerror(error) + rhandle.close().into_gerror(&session, error) } #[no_mangle] @@ -1284,10 +1297,11 @@ pub unsafe extern "C" fn rsvg_handle_render_cairo( } let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); rhandle .render_cairo_sub(cr, None) - .into_gerror(ptr::null_mut()) + .into_gerror(&session, ptr::null_mut()) } #[no_mangle] @@ -1304,11 +1318,13 @@ pub unsafe extern "C" fn rsvg_handle_render_cairo_sub( } 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(ptr::null_mut()) + .into_gerror(&session, ptr::null_mut()) } #[no_mangle] @@ -1326,7 +1342,8 @@ pub unsafe extern "C" fn rsvg_handle_get_pixbuf( match rhandle.get_pixbuf_sub(None) { Ok(pixbuf) => pixbuf.to_glib_full(), Err(e) => { - rsvg_log!("could not render: {}", e); + let session = &rhandle.imp().session; + rsvg_log!(session, "could not render: {}", e); ptr::null_mut() } } @@ -1349,7 +1366,8 @@ pub unsafe extern "C" fn rsvg_handle_get_pixbuf_sub( match rhandle.get_pixbuf_sub(id.as_deref()) { Ok(pixbuf) => pixbuf.to_glib_full(), Err(e) => { - rsvg_log!("could not render: {}", e); + let session = &rhandle.imp().session; + rsvg_log!(session, "could not render: {}", e); ptr::null_mut() } } @@ -1387,7 +1405,8 @@ pub unsafe extern "C" fn rsvg_handle_get_dimensions_sub( } Err(e) => { - rsvg_log!("could not get dimensions: {}", e); + let session = &rhandle.imp().session; + rsvg_log!(session, "could not get dimensions: {}", e); *dimension_data = RsvgDimensionData::empty(); false.into_glib() } @@ -1423,7 +1442,8 @@ pub unsafe extern "C" fn rsvg_handle_get_position_sub( p.x = 0; p.y = 0; - rsvg_log!("could not get position: {}", e); + let session = &rhandle.imp().session; + rsvg_log!(session, "could not get position: {}", e); false.into_glib() } } @@ -1460,7 +1480,12 @@ pub unsafe extern "C" fn rsvg_handle_new_from_file( Ok(p) => p.get_gfile(), Err(s) => { - set_gerror(error, 0, &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(); } }; @@ -1486,6 +1511,7 @@ pub unsafe extern "C" fn rsvg_handle_new_from_gfile_sync( 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); @@ -1501,7 +1527,7 @@ pub unsafe extern "C" fn rsvg_handle_new_from_gfile_sync( Ok(()) => raw_handle, Err(e) => { - set_gerror(error, 0, &format!("{}", e)); + set_gerror(&session, error, 0, &format!("{}", e)); gobject_ffi::g_object_unref(raw_handle as *mut _); ptr::null_mut() } @@ -1528,6 +1554,7 @@ pub unsafe extern "C" fn rsvg_handle_new_from_stream_sync( 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 { @@ -1541,7 +1568,7 @@ pub unsafe extern "C" fn rsvg_handle_new_from_stream_sync( Ok(()) => raw_handle, Err(e) => { - set_gerror(error, 0, &format!("{}", e)); + set_gerror(&session, error, 0, &format!("{}", e)); gobject_ffi::g_object_unref(raw_handle as *mut _); ptr::null_mut() } @@ -1630,6 +1657,7 @@ pub unsafe extern "C" fn rsvg_handle_set_stylesheet( } let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); let css = match (css, css_len) { (p, 0) if p.is_null() => "", @@ -1638,14 +1666,19 @@ pub unsafe extern "C" fn rsvg_handle_set_stylesheet( match str::from_utf8(s) { Ok(s) => s, Err(e) => { - set_gerror(error, 0, &format!("CSS is not valid UTF-8: {}", e)); + set_gerror( + &session, + error, + 0, + &format!("CSS is not valid UTF-8: {}", e), + ); return false.into_glib(); } } } }; - rhandle.set_stylesheet(css).into_gerror(error) + rhandle.set_stylesheet(css).into_gerror(&session, error) } #[no_mangle] @@ -1727,10 +1760,11 @@ pub unsafe extern "C" fn rsvg_handle_render_document( } let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); rhandle .render_document(cr, &(*viewport).into()) - .into_gerror(error) + .into_gerror(&session, error) } #[no_mangle] @@ -1751,6 +1785,7 @@ pub unsafe extern "C" fn rsvg_handle_get_geometry_for_layer( } let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); let id: Option = from_glib_none(id); @@ -1765,7 +1800,7 @@ pub unsafe extern "C" fn rsvg_handle_get_geometry_for_layer( *out_logical_rect = logical_rect; } }) - .into_gerror(error) + .into_gerror(&session, error) } #[no_mangle] @@ -1786,11 +1821,13 @@ pub unsafe extern "C" fn rsvg_handle_render_layer( } 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(error) + .into_gerror(&session, error) } #[no_mangle] @@ -1809,6 +1846,7 @@ pub unsafe extern "C" fn rsvg_handle_get_geometry_for_element( } let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); let id: Option = from_glib_none(id); @@ -1823,7 +1861,7 @@ pub unsafe extern "C" fn rsvg_handle_get_geometry_for_element( *out_logical_rect = logical_rect; } }) - .into_gerror(error) + .into_gerror(&session, error) } #[no_mangle] @@ -1844,11 +1882,13 @@ pub unsafe extern "C" fn rsvg_handle_render_element( } 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(error) + .into_gerror(&session, error) } #[no_mangle] @@ -1985,7 +2025,12 @@ fn check_cairo_context(cr: *mut cairo::ffi::cairo_t) -> Result Result { @@ -155,10 +156,12 @@ unsafe fn pixbuf_from_file_with_size_mode( ) -> *mut gdk_pixbuf::ffi::GdkPixbuf { let path = PathBuf::from_glib_none(filename); - let handle = match Loader::new().read_path(path) { + let session = Session::default(); + + let handle = match Loader::new_with_session(session.clone()).read_path(path) { Ok(handle) => handle, Err(e) => { - set_gerror(error, 0, &format!("{}", e)); + set_gerror(&session, error, 0, &format!("{}", e)); return ptr::null_mut(); } }; @@ -169,7 +172,7 @@ unsafe fn pixbuf_from_file_with_size_mode( let (document_width, document_height) = match renderer.legacy_document_size() { Ok(dim) => dim, Err(e) => { - set_gerror(error, 0, &format!("{}", e)); + set_gerror(&session, error, 0, &format!("{}", e)); return ptr::null_mut(); } }; @@ -186,7 +189,7 @@ unsafe fn pixbuf_from_file_with_size_mode( ) .map(|pixbuf| pixbuf.to_glib_full()) .unwrap_or_else(|e| { - set_gerror(error, 0, &format!("{}", e)); + set_gerror(&session, error, 0, &format!("{}", e)); ptr::null_mut() }) } diff --git a/src/c_api/sizing.rs b/src/c_api/sizing.rs index 902c188f7c8c3c886a9e3d958d72988e2b5dd952..e0c2090a0b2472bde72cb97daf8acef983b26cb9 100644 --- a/src/c_api/sizing.rs +++ b/src/c_api/sizing.rs @@ -51,7 +51,7 @@ impl<'a> LegacySize for CairoRenderer<'a> { let size_from_intrinsic_dimensions = self.intrinsic_size_in_pixels().or_else(|| { size_in_pixels_from_percentage_width_and_height( - &self.handle.0, + &self.handle.handle, &self.intrinsic_dimensions(), self.dpi, ) diff --git a/src/css.rs b/src/css.rs index e9b80bbda1594183b0a5bb912c1028c9d1ee9f36..d7bd4817df245ba8158b0913857ccf72355d7ea7 100644 --- a/src/css.rs +++ b/src/css.rs @@ -93,6 +93,7 @@ use crate::error::*; use crate::io::{self, BinaryData}; use crate::node::{Node, NodeBorrow, NodeCascade}; use crate::properties::{parse_value, ComputedValues, ParseAs, ParsedProperty}; +use crate::session::Session; use crate::url_resolver::UrlResolver; /// A parsed CSS declaration @@ -147,9 +148,10 @@ impl<'i> AtRuleParser<'i> for DeclParser { type Error = ValueErrorKind; } -/// Dummy struct to implement cssparser::QualifiedRuleParser and -/// cssparser::AtRuleParser -pub struct RuleParser; +/// Struct to implement cssparser::QualifiedRuleParser and cssparser::AtRuleParser +pub struct RuleParser { + session: Session, +} /// Errors from the CSS parsing process #[derive(Debug)] @@ -283,7 +285,7 @@ impl<'i> QualifiedRuleParser<'i> for RuleParser { .filter_map(|r| match r { Ok(decl) => Some(decl), Err(e) => { - rsvg_log!("Invalid declaration; ignoring: {:?}", e); + rsvg_log!(self.session, "Invalid declaration; ignoring: {:?}", e); None } }) @@ -804,9 +806,10 @@ impl Stylesheet { buf: &str, url_resolver: &UrlResolver, origin: Origin, + session: Session, ) -> Result { let mut stylesheet = Stylesheet::new(origin); - stylesheet.parse(buf, url_resolver)?; + stylesheet.parse(buf, url_resolver, session)?; Ok(stylesheet) } @@ -814,9 +817,10 @@ impl Stylesheet { href: &str, url_resolver: &UrlResolver, origin: Origin, + session: Session, ) -> Result { let mut stylesheet = Stylesheet::new(origin); - stylesheet.load(href, url_resolver)?; + stylesheet.load(href, url_resolver, session)?; Ok(stylesheet) } @@ -824,22 +828,30 @@ impl Stylesheet { /// /// The `base_url` is required for `@import` rules, so that librsvg /// can determine if the requested path is allowed. - pub fn parse(&mut self, buf: &str, url_resolver: &UrlResolver) -> Result<(), LoadingError> { + pub fn parse( + &mut self, + buf: &str, + url_resolver: &UrlResolver, + session: Session, + ) -> Result<(), LoadingError> { let mut input = ParserInput::new(buf); let mut parser = Parser::new(&mut input); + let rule_parser = RuleParser { + session: session.clone(), + }; - RuleListParser::new_for_stylesheet(&mut parser, RuleParser) + RuleListParser::new_for_stylesheet(&mut parser, rule_parser) .filter_map(|r| match r { Ok(rule) => Some(rule), Err(e) => { - rsvg_log!("Invalid rule; ignoring: {:?}", e); + rsvg_log!(session, "Invalid rule; ignoring: {:?}", e); None } }) .for_each(|rule| match rule { Rule::AtRule(AtRule::Import(url)) => { // ignore invalid imports - let _ = self.load(&url, url_resolver); + let _ = self.load(&url, url_resolver, session.clone()); } Rule::QualifiedRule(qr) => self.qualified_rules.push(qr), }); @@ -848,7 +860,12 @@ impl Stylesheet { } /// Parses a stylesheet referenced by an URL - fn load(&mut self, href: &str, url_resolver: &UrlResolver) -> Result<(), LoadingError> { + fn load( + &mut self, + href: &str, + url_resolver: &UrlResolver, + session: Session, + ) -> Result<(), LoadingError> { let aurl = url_resolver .resolve_href(href) .map_err(|_| LoadingError::BadUrl)?; @@ -864,20 +881,21 @@ impl Stylesheet { if is_text_css(&mime_type) { Ok(bytes) } else { - rsvg_log!("\"{}\" is not of type text/css; ignoring", aurl); + rsvg_log!(session, "\"{}\" is not of type text/css; ignoring", aurl); Err(LoadingError::BadCss) } }) .and_then(|bytes| { String::from_utf8(bytes).map_err(|_| { rsvg_log!( + session, "\"{}\" does not contain valid UTF-8 CSS data; ignoring", aurl ); LoadingError::BadCss }) }) - .and_then(|utf8| self.parse(&utf8, &UrlResolver::new(Some((*aurl).clone())))) + .and_then(|utf8| self.parse(&utf8, &UrlResolver::new(Some((*aurl).clone())), session)) } /// Appends the style declarations that match a specified node to a given vector @@ -923,6 +941,7 @@ pub fn cascade( ua_stylesheets: &[Stylesheet], author_stylesheets: &[Stylesheet], user_stylesheets: &[Stylesheet], + session: &Session, ) { for mut node in root.descendants().filter(|n| n.is_element()) { let mut matches = Vec::new(); @@ -957,7 +976,7 @@ pub fn cascade( .apply_style_declaration(m.declaration, m.origin); } - node.borrow_element_mut().set_style_attribute(); + node.borrow_element_mut().set_style_attribute(session); } let values = ComputedValues::default(); diff --git a/src/document.rs b/src/document.rs index f682f74603daf378b7f3d67eb2f3464c1fd110aa..21252e407f62c6c1e392b5c551bcc3b6058a8a15 100644 --- a/src/document.rs +++ b/src/document.rs @@ -18,6 +18,7 @@ use crate::handle::LoadOptions; use crate::io::{self, BinaryData}; use crate::limits; use crate::node::{Node, NodeBorrow, NodeData}; +use crate::session::Session; use crate::surface_utils::shared_surface::SharedImageSurface; use crate::url_resolver::{AllowedUrl, UrlResolver}; use crate::xml::{xml_load_from_possibly_compressed_stream, Attributes}; @@ -27,8 +28,9 @@ static UA_STYLESHEETS: Lazy> = Lazy::new(|| { include_str!("ua.css"), &UrlResolver::new(None), Origin::UserAgent, + Session::default(), ) - .unwrap()] + .expect("could not parse user agent stylesheet for librsvg, there's a bug!")] }); /// Identifier of a node @@ -72,6 +74,9 @@ pub struct Document { /// Tree of nodes; the root is guaranteed to be an `` element. tree: Node, + /// Metadata about the SVG handle. + session: Session, + /// Mapping from `id` attributes to nodes. ids: HashMap, @@ -94,12 +99,13 @@ pub struct Document { impl Document { /// Constructs a `Document` by loading it from a stream. pub fn load_from_stream( + session: Session, load_options: &LoadOptions, stream: &gio::InputStream, cancellable: Option<&gio::Cancellable>, ) -> Result { xml_load_from_possibly_compressed_stream( - DocumentBuilder::new(load_options), + DocumentBuilder::new(session, load_options), load_options.unlimited_size, stream, cancellable, @@ -115,6 +121,7 @@ impl Document { let stream = gio::MemoryInputStream::from_bytes(&bytes); Document::load_from_stream( + Session::new_for_test_suite(), &LoadOptions::new(UrlResolver::new(None)), &stream.upcast(), None::<&gio::Cancellable>, @@ -134,7 +141,7 @@ impl Document { NodeId::External(url, id) => self .externs .borrow_mut() - .lookup(&self.load_options, url, id) + .lookup(&self.session, &self.load_options, url, id) .ok(), } } @@ -159,8 +166,14 @@ impl Document { /// /// This uses the default UserAgent stylesheet, the document's internal stylesheets, /// plus an extra set of stylesheets supplied by the caller. - pub fn cascade(&mut self, extra: &[Stylesheet]) { - css::cascade(&mut self.tree, &UA_STYLESHEETS, &self.stylesheets, extra); + pub fn cascade(&mut self, extra: &[Stylesheet], session: &Session) { + css::cascade( + &mut self.tree, + &UA_STYLESHEETS, + &self.stylesheets, + extra, + session, + ); } } @@ -177,16 +190,18 @@ impl Resources { pub fn lookup( &mut self, + session: &Session, load_options: &LoadOptions, url: &str, id: &str, ) -> Result { - self.get_extern_document(load_options, url) + self.get_extern_document(session, load_options, url) .and_then(|doc| doc.lookup_internal_node(id).ok_or(LoadingError::BadUrl)) } fn get_extern_document( &mut self, + session: &Session, load_options: &LoadOptions, href: &str, ) -> Result, LoadingError> { @@ -204,6 +219,7 @@ impl Resources { .map_err(LoadingError::from) .and_then(|stream| { Document::load_from_stream( + session.clone(), &load_options.copy_with_base_url(aurl), &stream, None, @@ -477,6 +493,7 @@ impl NodeStack { } pub struct DocumentBuilder { + session: Session, load_options: LoadOptions, tree: Option, ids: HashMap, @@ -484,8 +501,9 @@ pub struct DocumentBuilder { } impl DocumentBuilder { - pub fn new(load_options: &LoadOptions) -> DocumentBuilder { + pub fn new(session: Session, load_options: &LoadOptions) -> DocumentBuilder { DocumentBuilder { + session, load_options: load_options.clone(), tree: None, ids: HashMap::new(), @@ -493,6 +511,10 @@ impl DocumentBuilder { } } + pub fn session(&self) -> &Session { + &self.session + } + pub fn append_stylesheet_from_xml_processing_instruction( &mut self, alternate: Option, @@ -508,9 +530,12 @@ impl DocumentBuilder { } // FIXME: handle CSS errors - if let Ok(stylesheet) = - Stylesheet::from_href(href, &self.load_options.url_resolver, Origin::Author) - { + if let Ok(stylesheet) = Stylesheet::from_href( + href, + &self.load_options.url_resolver, + Origin::Author, + self.session.clone(), + ) { self.stylesheets.push(stylesheet); } @@ -523,7 +548,7 @@ impl DocumentBuilder { attrs: Attributes, parent: Option, ) -> Node { - let node = Node::new(NodeData::new_element(name, attrs)); + let node = Node::new(NodeData::new_element(&self.session, name, attrs)); if let Some(id) = node.borrow_element().get_id() { // This is so we don't overwrite an existing id @@ -545,9 +570,12 @@ impl DocumentBuilder { pub fn append_stylesheet_from_text(&mut self, text: &str) { // FIXME: handle CSS errors - if let Ok(stylesheet) = - Stylesheet::from_data(text, &self.load_options.url_resolver, Origin::Author) - { + if let Ok(stylesheet) = Stylesheet::from_data( + text, + &self.load_options.url_resolver, + Origin::Author, + self.session.clone(), + ) { self.stylesheets.push(stylesheet); } } @@ -575,6 +603,7 @@ impl DocumentBuilder { pub fn build(self) -> Result { let DocumentBuilder { load_options, + session, tree, ids, stylesheets, @@ -586,6 +615,7 @@ impl DocumentBuilder { if is_element_of_type!(root, Svg) { let mut document = Document { tree: root, + session: session.clone(), ids, externs: RefCell::new(Resources::new()), images: RefCell::new(Images::new()), @@ -593,7 +623,7 @@ impl DocumentBuilder { stylesheets, }; - document.cascade(&[]); + document.cascade(&[], &session); Ok(document) } else { diff --git a/src/drawing_ctx.rs b/src/drawing_ctx.rs index cde3c61d32c1fb746e667662daa3b903d96a9604..6347304f7f06f3cb4c1c0394f5c98fef54e4b8ec 100644 --- a/src/drawing_ctx.rs +++ b/src/drawing_ctx.rs @@ -37,6 +37,7 @@ use crate::properties::{ Overflow, PaintTarget, ShapeRendering, StrokeLinecap, StrokeLinejoin, TextRendering, }; use crate::rect::{IRect, Rect}; +use crate::session::Session; use crate::surface_utils::{ shared_surface::ExclusiveImageSurface, shared_surface::SharedImageSurface, shared_surface::SurfaceType, @@ -169,6 +170,8 @@ struct Viewport { } pub struct DrawingCtx { + session: Session, + initial_viewport: Viewport, dpi: Dpi, @@ -196,6 +199,7 @@ pub enum DrawingMode { /// /// This creates a DrawingCtx internally and starts drawing at the specified `node`. pub fn draw_tree( + session: Session, mode: DrawingMode, cr: &cairo::Context, viewport: Rect, @@ -239,6 +243,7 @@ pub fn draw_tree( let viewport = viewport.translate((-viewport.x0, -viewport.y0)); let mut draw_ctx = DrawingCtx::new( + session, cr, transform, viewport, @@ -281,6 +286,7 @@ const CAIRO_TAG_LINK: &str = "Link"; impl DrawingCtx { fn new( + session: Session, cr: &cairo::Context, transform: Transform, viewport: Rect, @@ -296,6 +302,7 @@ impl DrawingCtx { let viewport_stack = vec![initial_viewport]; DrawingCtx { + session, initial_viewport, dpi, cr_stack: Rc::new(RefCell::new(Vec::new())), @@ -320,6 +327,7 @@ impl DrawingCtx { cr_stack.borrow_mut().push(self.cr.clone()); DrawingCtx { + session: self.session.clone(), initial_viewport: self.initial_viewport, dpi: self.dpi, cr_stack, @@ -332,6 +340,10 @@ impl DrawingCtx { } } + pub fn session(&self) -> &Session { + &self.session + } + pub fn user_language(&self) -> &UserLanguage { &self.user_language } @@ -489,6 +501,7 @@ impl DrawingCtx { ), Some(v) => { rsvg_log!( + self.session, "ignoring viewBox ({}, {}, {}, {}) since it is not usable", v.x0, v.y0, @@ -575,7 +588,7 @@ impl DrawingCtx { Ok(n) => n, Err(AcquireError::CircularReference(_)) => { - rsvg_log!("circular reference in element {}", mask_node); + rsvg_log!(self.session, "circular reference in element {}", mask_node); return Ok(None); } @@ -641,8 +654,13 @@ impl DrawingCtx { let mut mask_draw_ctx = self.nested(mask_cr); - let stacking_ctx = - StackingContext::new(acquired_nodes, &mask_element, Transform::identity(), values); + let stacking_ctx = StackingContext::new( + self.session(), + acquired_nodes, + &mask_element, + Transform::identity(), + values, + ); let res = mask_draw_ctx.with_discrete_layer( &stacking_ctx, @@ -793,6 +811,7 @@ impl DrawingCtx { current_color, None, None, + self.session(), ) .to_user_space(&bbox, ¶ms, values), ); @@ -807,6 +826,7 @@ impl DrawingCtx { current_color, None, None, + self.session(), ) .to_user_space(&bbox, ¶ms, values), ); @@ -1036,7 +1056,7 @@ impl DrawingCtx { Ok(n) => n, Err(AcquireError::CircularReference(ref node)) => { - rsvg_log!("circular reference in element {}", node); + rsvg_log!(self.session, "circular reference in element {}", node); return Ok(false); } @@ -1096,6 +1116,7 @@ impl DrawingCtx { let elt = pattern_node.borrow_element(); let stacking_ctx = StackingContext::new( + self.session(), acquired_nodes, &elt, Transform::identity(), @@ -1611,7 +1632,7 @@ impl DrawingCtx { Ok(n) => n, Err(AcquireError::CircularReference(_)) => { - rsvg_log!("circular reference in element {}", node); + rsvg_log!(self.session, "circular reference in element {}", node); return Ok(self.empty_bbox()); } @@ -1622,7 +1643,7 @@ impl DrawingCtx { Ok(acquired) => acquired, Err(AcquireError::CircularReference(node)) => { - rsvg_log!("circular reference in element {}", node); + rsvg_log!(self.session, "circular reference in element {}", node); return Ok(self.empty_bbox()); } @@ -1635,7 +1656,12 @@ impl DrawingCtx { Err(AcquireError::InvalidLinkType(_)) => unreachable!(), Err(AcquireError::LinkNotFound(node_id)) => { - rsvg_log!("element {} references nonexistent \"{}\"", node, node_id); + rsvg_log!( + self.session, + "element {} references nonexistent \"{}\"", + node, + node_id + ); return Ok(self.empty_bbox()); } }; @@ -1674,8 +1700,13 @@ impl DrawingCtx { None }; - let stacking_ctx = - StackingContext::new(acquired_nodes, &use_element, Transform::identity(), values); + let stacking_ctx = StackingContext::new( + self.session(), + acquired_nodes, + &use_element, + Transform::identity(), + values, + ); self.with_discrete_layer( &stacking_ctx, @@ -1708,6 +1739,7 @@ impl DrawingCtx { // otherwise the referenced node is not a ; process it generically let stacking_ctx = StackingContext::new( + self.session(), acquired_nodes, &use_element, Transform::new_translate(use_rect.x0, use_rect.y0), diff --git a/src/element.rs b/src/element.rs index 4619cda8e8a6735466ee4e4a6a6943dfcb46c5eb..3d8fb60ba8fdf2167960dafcb8384f0a8d528827 100644 --- a/src/element.rs +++ b/src/element.rs @@ -38,6 +38,7 @@ use crate::marker::Marker; use crate::node::*; use crate::pattern::Pattern; use crate::properties::{ComputedValues, SpecifiedValues}; +use crate::session::Session; use crate::shapes::{Circle, Ellipse, Line, Path, Polygon, Polyline, Rect}; use crate::structure::{ClipPath, Group, Link, Mask, NonRendering, Svg, Switch, Symbol, Use}; use crate::style::Style; @@ -70,7 +71,7 @@ pub trait SetAttributes { /// Sets per-element attributes. /// /// Each element is supposed to iterate the `attributes`, and parse any ones it needs. - fn set_attributes(&mut self, _attributes: &Attributes) -> ElementResult { + fn set_attributes(&mut self, _attributes: &Attributes, _session: &Session) -> ElementResult { Ok(()) } } @@ -97,7 +98,7 @@ pub struct ElementInner { attributes: Attributes, specified_values: SpecifiedValues, important_styles: HashSet, - result: ElementResult, + is_in_error: bool, values: ComputedValues, required_extensions: Option, required_features: Option, @@ -107,9 +108,10 @@ pub struct ElementInner { impl ElementInner { fn new( + session: &Session, element_name: QualName, attributes: Attributes, - result: Result<(), ElementError>, + is_in_error: bool, element_impl: T, ) -> ElementInner { let mut e = Self { @@ -117,7 +119,7 @@ impl ElementInner { attributes, specified_values: Default::default(), important_styles: Default::default(), - result, + is_in_error, values: Default::default(), required_extensions: Default::default(), required_features: Default::default(), @@ -127,12 +129,12 @@ impl ElementInner { let mut set_attributes = || -> Result<(), ElementError> { e.set_conditional_processing_attributes()?; - e.set_presentation_attributes()?; + e.set_presentation_attributes(session)?; Ok(()) }; if let Err(error) = set_attributes() { - e.set_error(error); + e.set_error(error, session); } e @@ -215,9 +217,9 @@ impl ElementInner { /// Hands the `attrs` to the node's state, to apply the presentation attributes. #[allow(clippy::unnecessary_wraps)] - fn set_presentation_attributes(&mut self) -> Result<(), ElementError> { + fn set_presentation_attributes(&mut self, session: &Session) -> Result<(), ElementError> { self.specified_values - .parse_presentation_attributes(&self.attributes) + .parse_presentation_attributes(session, &self.attributes) } // Applies a style declaration to the node's specified_values @@ -230,7 +232,7 @@ impl ElementInner { } /// Applies CSS styles from the "style" attribute - fn set_style_attribute(&mut self) { + fn set_style_attribute(&mut self, session: &Session) { let style = self .attributes .iter() @@ -242,19 +244,20 @@ impl ElementInner { style, Origin::Author, &mut self.important_styles, + session, ) { - self.set_error(e); + self.set_error(e, session); } } } - fn set_error(&mut self, error: ElementError) { - rsvg_log!("setting node {} in error: {}", self, error); - self.result = Err(error); + fn set_error(&mut self, error: ElementError, session: &Session) { + rsvg_log!(session, "setting node {} in error: {}", self, error); + self.is_in_error = true; } fn is_in_error(&self) -> bool { - self.result.is_err() + self.is_in_error } } @@ -276,7 +279,11 @@ impl Draw for ElementInner { Ok(draw_ctx.empty_bbox()) } } else { - rsvg_log!("(not rendering element {} because it is in error)", self); + rsvg_log!( + draw_ctx.session(), + "(not rendering element {} because it is in error)", + self + ); // maybe we should actually return a RenderingError::ElementIsInError here? Ok(draw_ctx.empty_bbox()) @@ -445,7 +452,7 @@ impl Element { /// /// This operation does not fail. Unknown element names simply produce a [`NonRendering`] /// element. - pub fn new(name: &QualName, mut attrs: Attributes) -> Element { + pub fn new(session: &Session, name: &QualName, mut attrs: Attributes) -> Element { let (create_fn, flags): (ElementCreateFn, ElementCreateFlags) = if name.ns == ns!(svg) { match ELEMENT_CREATORS.get(name.local.as_ref()) { // hack in the SVG namespace for supported element names @@ -466,7 +473,7 @@ impl Element { // sizes::print_sizes(); - create_fn(name, attrs) + create_fn(session, name, attrs) } pub fn element_name(&self) -> &QualName { @@ -509,8 +516,8 @@ impl Element { call_inner!(self, apply_style_declaration, declaration, origin) } - pub fn set_style_attribute(&mut self) { - call_inner!(self, set_style_attribute); + pub fn set_style_attribute(&mut self, session: &Session) { + call_inner!(self, set_style_attribute, session); } pub fn is_in_error(&self) -> bool { @@ -585,15 +592,27 @@ impl fmt::Display for Element { macro_rules! e { ($name:ident, $element_type:ident) => { - pub fn $name(element_name: &QualName, attributes: Attributes) -> Element { + pub fn $name( + session: &Session, + element_name: &QualName, + attributes: Attributes, + ) -> Element { let mut element_impl = <$element_type>::default(); - let result = element_impl.set_attributes(&attributes); + let is_in_error = if let Err(e) = element_impl.set_attributes(&attributes, session) { + // FIXME: this does not provide a clue of what was the problematic attribute, or the + // problematic element. We need tracking of the current parsing position to do that. + rsvg_log!(session, "setting element in error: {}", e); + true + } else { + false + }; let element = Element::$element_type(Box::new(ElementInner::new( + session, element_name.clone(), attributes, - result, + is_in_error, element_impl, ))); @@ -675,7 +694,8 @@ mod creators { use creators::*; -type ElementCreateFn = fn(element_name: &QualName, attributes: Attributes) -> Element; +type ElementCreateFn = + fn(session: &Session, element_name: &QualName, attributes: Attributes) -> Element; #[derive(Copy, Clone, PartialEq)] enum ElementCreateFlags { diff --git a/src/filter.rs b/src/filter.rs index 98552aea84b6f499927db107880f9d82ce470186..0e0180a37bac2eba0a68fc4935fd8b1f6416229a 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -15,6 +15,7 @@ use crate::length::*; use crate::node::NodeBorrow; use crate::parsers::{Parse, ParseValue}; use crate::rect::Rect; +use crate::session::Session; use crate::xml::Attributes; /// The node. @@ -70,7 +71,7 @@ impl Filter { } impl SetAttributes for Filter { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "filterUnits") => self.filter_units = attr.parse(value)?, @@ -125,10 +126,13 @@ fn filter_spec_from_filter_node( node_id: &NodeId, node_being_filtered_name: &str, ) -> Result { + let session = draw_ctx.session().clone(); + acquired_nodes .acquire(node_id) .map_err(|e| { rsvg_log!( + session, "element {} will not be filtered with \"{}\": {}", node_being_filtered_name, node_id, @@ -144,6 +148,7 @@ fn filter_spec_from_filter_node( Element::Filter(_) => { if element.is_in_error() { rsvg_log!( + session, "element {} will not be filtered since its filter \"{}\" is in error", node_being_filtered_name, node_id, @@ -156,6 +161,7 @@ fn filter_spec_from_filter_node( _ => { rsvg_log!( + session, "element {} will not be filtered since \"{}\" is not a filter", node_being_filtered_name, node_id, diff --git a/src/filters/blend.rs b/src/filters/blend.rs index c56d68b8dffbb01e1be7a5ea936bf8c018b1aede..2928fdfa2334732744510de20c2eeebec73c97c9 100644 --- a/src/filters/blend.rs +++ b/src/filters/blend.rs @@ -9,6 +9,7 @@ use crate::node::{CascadedValues, Node}; use crate::parsers::{Parse, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::shared_surface::Operator; use crate::xml::Attributes; @@ -59,7 +60,7 @@ pub struct Blend { } impl SetAttributes for FeBlend { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { let (in1, in2) = self.base.parse_two_inputs(attrs)?; self.params.in1 = in1; self.params.in2 = in2; diff --git a/src/filters/color_matrix.rs b/src/filters/color_matrix.rs index a9af7a914961d76ee6ec938b0d5fe640f4219268..c3c2b538511053abdcd0207a17c05d06e8efa108 100644 --- a/src/filters/color_matrix.rs +++ b/src/filters/color_matrix.rs @@ -10,6 +10,7 @@ use crate::node::{CascadedValues, Node}; use crate::parsers::{NumberList, Parse, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::{ iterators::Pixels, shared_surface::ExclusiveImageSurface, ImageSurfaceDataExt, Pixel, }; @@ -63,7 +64,7 @@ impl Default for ColorMatrix { #[rustfmt::skip] impl SetAttributes for FeColorMatrix { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; // First, determine the operation type. diff --git a/src/filters/component_transfer.rs b/src/filters/component_transfer.rs index 1716ef21df9d30e53599acb358c5b02981a4a4e3..31027e6c5f7dcd9e0c6c66ad2210fc245d448731 100644 --- a/src/filters/component_transfer.rs +++ b/src/filters/component_transfer.rs @@ -11,6 +11,7 @@ use crate::node::{CascadedValues, Node, NodeBorrow}; use crate::parsers::{NumberList, Parse, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::{ iterators::Pixels, shared_surface::ExclusiveImageSurface, ImageSurfaceDataExt, Pixel, }; @@ -40,7 +41,7 @@ pub struct ComponentTransfer { } impl SetAttributes for FeComponentTransfer { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; Ok(()) } @@ -214,7 +215,7 @@ macro_rules! func_x { impl SetAttributes for $func_name { #[inline] - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "type") => self.function_type = attr.parse(value)?, diff --git a/src/filters/composite.rs b/src/filters/composite.rs index 2a2e47ed762398a09d5af4f89a40f5800e791e41..1b69042028aff9a02e0af6c840ae9f4dde820434 100644 --- a/src/filters/composite.rs +++ b/src/filters/composite.rs @@ -9,6 +9,7 @@ use crate::node::{CascadedValues, Node}; use crate::parsers::{Parse, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::shared_surface::Operator as SurfaceOperator; use crate::xml::Attributes; @@ -53,7 +54,7 @@ pub struct Composite { } impl SetAttributes for FeComposite { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { let (in1, in2) = self.base.parse_two_inputs(attrs)?; self.params.in1 = in1; self.params.in2 = in2; diff --git a/src/filters/convolve_matrix.rs b/src/filters/convolve_matrix.rs index bd796730d209c6c591020e49493fa80abc84e90f..54a390c86f2aeadeeec9b6eb33081f6ff1211236 100644 --- a/src/filters/convolve_matrix.rs +++ b/src/filters/convolve_matrix.rs @@ -10,6 +10,7 @@ use crate::node::{CascadedValues, Node}; use crate::parsers::{NonNegative, NumberList, NumberOptionalNumber, Parse, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::{ iterators::{PixelRectangle, Pixels}, shared_surface::ExclusiveImageSurface, @@ -69,7 +70,7 @@ impl Default for ConvolveMatrix { } impl SetAttributes for FeConvolveMatrix { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; for (attr, value) in attrs.iter() { diff --git a/src/filters/displacement_map.rs b/src/filters/displacement_map.rs index b7aaf8654bdf1e0a9166fd6ce08413dcfb89da76..c88b4eec565c3bc494e97f13915282c844ec5ec0 100644 --- a/src/filters/displacement_map.rs +++ b/src/filters/displacement_map.rs @@ -9,6 +9,7 @@ use crate::node::{CascadedValues, Node}; use crate::parsers::{Parse, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::{iterators::Pixels, shared_surface::ExclusiveImageSurface}; use crate::xml::Attributes; @@ -49,7 +50,7 @@ pub struct DisplacementMap { } impl SetAttributes for FeDisplacementMap { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { let (in1, in2) = self.base.parse_two_inputs(attrs)?; self.params.in1 = in1; self.params.in2 = in2; diff --git a/src/filters/flood.rs b/src/filters/flood.rs index a6ee723c3f119072de641c99a73b4d3e77709863..4a596e92d9e6c99f8b97f28aed7a4723547fd883 100644 --- a/src/filters/flood.rs +++ b/src/filters/flood.rs @@ -4,6 +4,7 @@ use crate::element::{ElementResult, SetAttributes}; use crate::node::{CascadedValues, Node}; use crate::paint_server::resolve_color; use crate::rect::IRect; +use crate::session::Session; use crate::xml::Attributes; use super::bounds::BoundsBuilder; @@ -24,7 +25,7 @@ pub struct Flood { } impl SetAttributes for FeFlood { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.base.parse_no_inputs(attrs) } } diff --git a/src/filters/gaussian_blur.rs b/src/filters/gaussian_blur.rs index 114b1d69f422e3a98851c5bb110ab23c7b596374..3d4fe9ae0adca8193921ec4e61cc7c2baa98a617 100644 --- a/src/filters/gaussian_blur.rs +++ b/src/filters/gaussian_blur.rs @@ -11,6 +11,7 @@ use crate::node::{CascadedValues, Node}; use crate::parsers::{NonNegative, NumberOptionalNumber, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::{ shared_surface::{BlurDirection, Horizontal, SharedImageSurface, Vertical}, EdgeMode, @@ -45,7 +46,7 @@ pub struct GaussianBlur { } impl SetAttributes for FeGaussianBlur { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; for (attr, value) in attrs.iter() { diff --git a/src/filters/image.rs b/src/filters/image.rs index 243bf0cf5675750916f99afaf1c596c4fbb7d3f5..65ddf759c8935d612cf14ead66a723863cd3f5f3 100644 --- a/src/filters/image.rs +++ b/src/filters/image.rs @@ -9,6 +9,7 @@ use crate::node::{CascadedValues, Node}; use crate::parsers::ParseValue; use crate::properties::ComputedValues; use crate::rect::Rect; +use crate::session::Session; use crate::surface_utils::shared_surface::SharedImageSurface; use crate::viewbox::ViewBox; use crate::xml::Attributes; @@ -115,7 +116,7 @@ impl Image { } impl SetAttributes for FeImage { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.base.parse_no_inputs(attrs)?; for (attr, value) in attrs.iter() { diff --git a/src/filters/lighting.rs b/src/filters/lighting.rs index f1e94beb0bd4554b8d59cc2cb38881314eec911b..3d4c73a5ae0137535c916c167edcb9dd3bcf2e34 100644 --- a/src/filters/lighting.rs +++ b/src/filters/lighting.rs @@ -21,6 +21,7 @@ use crate::paint_server::resolve_color; use crate::parsers::{NonNegative, NumberOptionalNumber, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::{ shared_surface::{ExclusiveImageSurface, SharedImageSurface, SurfaceType}, ImageSurfaceDataExt, Pixel, @@ -213,7 +214,7 @@ impl FeDistantLight { } impl SetAttributes for FeDistantLight { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "azimuth") => self.azimuth = attr.parse(value)?, @@ -247,7 +248,7 @@ impl FePointLight { } impl SetAttributes for FePointLight { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "x") => self.x = attr.parse(value)?, @@ -297,7 +298,7 @@ impl FeSpotLight { } impl SetAttributes for FeSpotLight { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "x") => self.x = attr.parse(value)?, @@ -332,7 +333,7 @@ fn transform_dist(t: Transform, d: f64) -> f64 { } impl SetAttributes for FeDiffuseLighting { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; for (attr, value) in attrs.iter() { @@ -377,7 +378,7 @@ impl DiffuseLighting { } impl SetAttributes for FeSpecularLighting { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; for (attr, value) in attrs.iter() { diff --git a/src/filters/merge.rs b/src/filters/merge.rs index 747465982ecdff6ea65e3ae19d1feb8cb64f4fd3..e2e9be65877b558ecffd79735b88874e162ac970 100644 --- a/src/filters/merge.rs +++ b/src/filters/merge.rs @@ -7,6 +7,7 @@ use crate::node::{CascadedValues, Node, NodeBorrow}; use crate::parsers::ParseValue; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::shared_surface::{Operator, SharedImageSurface, SurfaceType}; use crate::xml::Attributes; @@ -51,14 +52,14 @@ impl Default for FeMerge { } impl SetAttributes for FeMerge { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.base.parse_no_inputs(attrs) } } impl SetAttributes for FeMergeNode { #[inline] - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { if let expanded_name!("", "in") = attr.expanded() { self.in1 = attr.parse(value)?; diff --git a/src/filters/mod.rs b/src/filters/mod.rs index 0b3136cdf8daef2130ee04b501e1de6f35b444d1..3165bdf8069176e3ad6dd0d88801784963c91ed6 100644 --- a/src/filters/mod.rs +++ b/src/filters/mod.rs @@ -246,6 +246,8 @@ pub fn extract_filter_from_filter_node( acquired_nodes: &mut AcquiredNodes<'_>, draw_ctx: &DrawingCtx, ) -> Result { + let session = draw_ctx.session().clone(); + let filter_node = &*filter_node; assert!(is_element_of_type!(filter_node, Filter)); @@ -270,7 +272,11 @@ pub fn extract_filter_from_filter_node( let in_error = c.borrow_element().is_in_error(); if in_error { - rsvg_log!("(ignoring filter primitive {} because it is in error)", c); + rsvg_log!( + session, + "(ignoring filter primitive {} because it is in error)", + c + ); } !in_error @@ -293,6 +299,7 @@ pub fn extract_filter_from_filter_node( .resolve(acquired_nodes, &primitive_node) .map_err(|e| { rsvg_log!( + session, "(filter primitive {} returned an error: {})", primitive_name, e @@ -320,6 +327,8 @@ pub fn render( transform: Transform, node_bbox: BoundingBox, ) -> Result { + let session = draw_ctx.session().clone(); + FilterContext::new( &filter.user_space_filter, stroke_paint_source, @@ -336,6 +345,7 @@ pub fn render( Ok(output) => { let elapsed = start.elapsed(); rsvg_log!( + session, "(rendered filter primitive {} in\n {} seconds)", user_space_primitive.params.name(), elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9 @@ -349,6 +359,7 @@ pub fn render( Err(err) => { rsvg_log!( + session, "(filter primitive {} returned an error: {})", user_space_primitive.params.name(), err diff --git a/src/filters/morphology.rs b/src/filters/morphology.rs index 7b4dbf377ff64a2cb957ff914dd15ab0fabe0b5e..8a6c0725a61419695d5220a69eca08a4464112da 100644 --- a/src/filters/morphology.rs +++ b/src/filters/morphology.rs @@ -11,6 +11,7 @@ use crate::node::Node; use crate::parsers::{NonNegative, NumberOptionalNumber, Parse, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::{ iterators::{PixelRectangle, Pixels}, shared_surface::ExclusiveImageSurface, @@ -50,7 +51,7 @@ pub struct Morphology { } impl SetAttributes for FeMorphology { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; for (attr, value) in attrs.iter() { diff --git a/src/filters/offset.rs b/src/filters/offset.rs index 1f97b6126b44f75f4bff8b1965f52605727e71e7..d60542a54e8e96bf41a0435d7824568edab5aca5 100644 --- a/src/filters/offset.rs +++ b/src/filters/offset.rs @@ -7,6 +7,7 @@ use crate::node::Node; use crate::parsers::ParseValue; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::xml::Attributes; use super::bounds::BoundsBuilder; @@ -32,7 +33,7 @@ pub struct Offset { } impl SetAttributes for FeOffset { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; for (attr, value) in attrs.iter() { diff --git a/src/filters/tile.rs b/src/filters/tile.rs index 8c397b3ed450be5286f40d907f779733531b069a..45549a71e5f98770536ad63280a6b0bcf659585d 100644 --- a/src/filters/tile.rs +++ b/src/filters/tile.rs @@ -4,6 +4,7 @@ use crate::element::{ElementResult, SetAttributes}; use crate::node::Node; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::xml::Attributes; use super::bounds::BoundsBuilder; @@ -27,7 +28,7 @@ pub struct Tile { } impl SetAttributes for FeTile { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.params.in1 = self.base.parse_one_input(attrs)?; Ok(()) } diff --git a/src/filters/turbulence.rs b/src/filters/turbulence.rs index 2f40f7f26add18cf526a64cadd2a6617eab2fc6d..cb962c45c7f6919ac29b82481f3006c7bf849adf 100644 --- a/src/filters/turbulence.rs +++ b/src/filters/turbulence.rs @@ -9,6 +9,7 @@ use crate::node::{CascadedValues, Node}; use crate::parsers::{NonNegative, NumberOptionalNumber, Parse, ParseValue}; use crate::properties::ColorInterpolationFilters; use crate::rect::IRect; +use crate::session::Session; use crate::surface_utils::{ shared_surface::{ExclusiveImageSurface, SurfaceType}, ImageSurfaceDataExt, Pixel, PixelOps, @@ -74,7 +75,7 @@ impl Default for Turbulence { } impl SetAttributes for FeTurbulence { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { self.base.parse_no_inputs(attrs)?; for (attr, value) in attrs.iter() { diff --git a/src/gradient.rs b/src/gradient.rs index ac914e763a0c69b59cc7a2479160646d6d6fb116..d44e4f2cb776ac49e80ed993cf02b4eb7e06dee9 100644 --- a/src/gradient.rs +++ b/src/gradient.rs @@ -17,6 +17,7 @@ use crate::node::{CascadedValues, Node, NodeBorrow}; use crate::paint_server::resolve_color; use crate::parsers::{Parse, ParseValue}; use crate::properties::ComputedValues; +use crate::session::Session; use crate::transform::{Transform, TransformAttribute}; use crate::unit_interval::UnitInterval; use crate::xml::Attributes; @@ -65,7 +66,7 @@ pub struct Stop { } impl SetAttributes for Stop { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { if let expanded_name!("", "offset") = attr.expanded() { self.offset = attr.parse(value)?; @@ -409,7 +410,7 @@ impl UnresolvedGradient { /// Looks for children inside a linearGradient or radialGradient node, /// and adds their info to the UnresolvedGradient &self. - fn add_color_stops_from_node(&mut self, node: &Node, opacity: UnitInterval) { + fn add_color_stops_from_node(&mut self, node: &Node, opacity: UnitInterval, session: &Session) { assert!(matches!( *node.borrow_element(), Element::LinearGradient(_) | Element::RadialGradient(_) @@ -420,7 +421,11 @@ impl UnresolvedGradient { if let Element::Stop(ref stop) = *elt { if elt.is_in_error() { - rsvg_log!("(not using gradient stop {} because it is in error)", child); + rsvg_log!( + session, + "(not using gradient stop {} because it is in error)", + child + ); } else { let cascaded = CascadedValues::new_from_node(&child); let values = cascaded.get(); @@ -515,7 +520,7 @@ impl RadialGradient { } impl SetAttributes for Common { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "gradientUnits") => self.units = attr.parse(value)?, @@ -540,8 +545,8 @@ impl SetAttributes for Common { } impl SetAttributes for LinearGradient { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { - self.common.set_attributes(attrs)?; + fn set_attributes(&mut self, attrs: &Attributes, session: &Session) -> ElementResult { + self.common.set_attributes(attrs, session)?; for (attr, value) in attrs.iter() { match attr.expanded() { @@ -563,7 +568,12 @@ impl Draw for LinearGradient {} macro_rules! impl_gradient { ($gradient_type:ident, $other_type:ident) => { impl $gradient_type { - fn get_unresolved(&self, node: &Node, opacity: UnitInterval) -> Unresolved { + fn get_unresolved( + &self, + node: &Node, + opacity: UnitInterval, + session: &Session, + ) -> Unresolved { let mut gradient = UnresolvedGradient { units: self.common.units, transform: self.common.transform, @@ -572,7 +582,7 @@ macro_rules! impl_gradient { variant: self.get_unresolved_variant(), }; - gradient.add_color_stops_from_node(node, opacity); + gradient.add_color_stops_from_node(node, opacity, session); Unresolved { gradient, @@ -585,11 +595,12 @@ macro_rules! impl_gradient { node: &Node, acquired_nodes: &mut AcquiredNodes<'_>, opacity: UnitInterval, + session: &Session, ) -> Result { let Unresolved { mut gradient, mut fallback, - } = self.get_unresolved(node, opacity); + } = self.get_unresolved(node, opacity, session); let mut stack = NodeStack::new(); @@ -604,10 +615,10 @@ macro_rules! impl_gradient { let unresolved = match *acquired_node.borrow_element() { Element::$gradient_type(ref g) => { - g.get_unresolved(&acquired_node, opacity) + g.get_unresolved(&acquired_node, opacity, session) } Element::$other_type(ref g) => { - g.get_unresolved(&acquired_node, opacity) + g.get_unresolved(&acquired_node, opacity, session) } _ => return Err(AcquireError::InvalidLinkType(node_id.clone())), }; @@ -632,8 +643,8 @@ impl_gradient!(LinearGradient, RadialGradient); impl_gradient!(RadialGradient, LinearGradient); impl SetAttributes for RadialGradient { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { - self.common.set_attributes(attrs)?; + fn set_attributes(&mut self, attrs: &Attributes, session: &Session) -> ElementResult { + self.common.set_attributes(attrs, session)?; // Create a local expanded name for "fr" because markup5ever doesn't have built-in let expanded_name_fr = ExpandedName { ns: &Namespace::from(""), @@ -734,23 +745,33 @@ mod tests { #[test] fn gradient_resolved_from_defaults_is_really_resolved() { + let session = Session::default(); + let node = Node::new(NodeData::new_element( + &session, &QualName::new(None, ns!(svg), local_name!("linearGradient")), Attributes::new(), )); - let unresolved = borrow_element_as!(node, LinearGradient) - .get_unresolved(&node, UnitInterval::clamp(1.0)); + let unresolved = borrow_element_as!(node, LinearGradient).get_unresolved( + &node, + UnitInterval::clamp(1.0), + &session, + ); let gradient = unresolved.gradient.resolve_from_defaults(); assert!(gradient.is_resolved()); let node = Node::new(NodeData::new_element( + &session, &QualName::new(None, ns!(svg), local_name!("radialGradient")), Attributes::new(), )); - let unresolved = borrow_element_as!(node, RadialGradient) - .get_unresolved(&node, UnitInterval::clamp(1.0)); + let unresolved = borrow_element_as!(node, RadialGradient).get_unresolved( + &node, + UnitInterval::clamp(1.0), + &session, + ); let gradient = unresolved.gradient.resolve_from_defaults(); assert!(gradient.is_resolved()); } diff --git a/src/handle.rs b/src/handle.rs index 2480eb9a8f4cccf346972b0c4ad0aeef0e7e69c2..845da304c4ca7b2ba4fcbe0134ac4274c66f3008 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -12,6 +12,7 @@ use crate::error::{DefsLookupErrorKind, LoadingError, RenderingError}; use crate::length::*; use crate::node::{CascadedValues, Node, NodeBorrow}; use crate::rect::Rect; +use crate::session::Session; use crate::structure::IntrinsicDimensions; use crate::url_resolver::{AllowedUrl, UrlResolver}; @@ -79,18 +80,26 @@ impl LoadOptions { /// /// [`from_stream`]: #method.from_stream pub struct Handle { + session: Session, document: Document, } impl Handle { /// Loads an SVG document into a `Handle`. pub fn from_stream( + session: Session, load_options: &LoadOptions, stream: &gio::InputStream, cancellable: Option<&gio::Cancellable>, ) -> Result { Ok(Handle { - document: Document::load_from_stream(load_options, stream, cancellable)?, + session: session.clone(), + document: Document::load_from_stream( + session.clone(), + load_options, + stream, + cancellable, + )?, }) } @@ -151,6 +160,7 @@ impl Handle { let cr = cairo::Context::new(&target)?; let bbox = draw_tree( + self.session.clone(), DrawingMode::LimitToStack { node, root }, &cr, viewport, @@ -205,6 +215,7 @@ impl Handle { .ok_or(DefsLookupErrorKind::NotFound), NodeId::External(_, _) => { rsvg_log!( + self.session, "the public API is not allowed to look up external references: {}", node_id ); @@ -243,6 +254,7 @@ impl Handle { with_saved_cr(cr, || { draw_tree( + self.session.clone(), DrawingMode::LimitToStack { node, root }, cr, viewport, @@ -269,6 +281,7 @@ impl Handle { let node = node.clone(); draw_tree( + self.session.clone(), DrawingMode::OnlyNode(node), &cr, unit_rectangle(), @@ -341,6 +354,7 @@ impl Handle { cr.translate(-ink_r.x0, -ink_r.y0); draw_tree( + self.session.clone(), DrawingMode::OnlyNode(node), cr, unit_rectangle(), @@ -363,8 +377,8 @@ impl Handle { pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> { let mut stylesheet = Stylesheet::new(Origin::User); - stylesheet.parse(css, &UrlResolver::new(None))?; - self.document.cascade(&[stylesheet]); + stylesheet.parse(css, &UrlResolver::new(None), self.session.clone())?; + self.document.cascade(&[stylesheet], &self.session); Ok(()) } } diff --git a/src/image.rs b/src/image.rs index b825ef342a7b2a8b0c5237c55b2c6b1626968cf2..530bf09e5e27b9981585614f1602f5588bbfcd0c 100644 --- a/src/image.rs +++ b/src/image.rs @@ -14,6 +14,7 @@ use crate::length::*; use crate::node::{CascadedValues, Node, NodeBorrow}; use crate::parsers::ParseValue; use crate::rect::Rect; +use crate::session::Session; use crate::xml::Attributes; /// The `` element. @@ -27,7 +28,7 @@ pub struct Image { } impl SetAttributes for Image { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "preserveAspectRatio") => self.aspect = attr.parse(value)?, @@ -58,7 +59,12 @@ impl Draw for Image { Some(ref url) => match acquired_nodes.lookup_image(url) { Ok(surf) => surf, Err(e) => { - rsvg_log!("could not load image \"{}\": {}", url, e); + rsvg_log!( + draw_ctx.session(), + "could not load image \"{}\": {}", + url, + e + ); return Ok(draw_ctx.empty_bbox()); } }, @@ -97,7 +103,13 @@ impl Draw for Image { }; let elt = node.borrow_element(); - let stacking_ctx = StackingContext::new(acquired_nodes, &elt, values.transform(), values); + let stacking_ctx = StackingContext::new( + draw_ctx.session(), + acquired_nodes, + &elt, + values.transform(), + values, + ); draw_ctx.draw_image(&image, &stacking_ctx, acquired_nodes, values, clipping) } diff --git a/src/layout.rs b/src/layout.rs index f68eb1cf59f5b9d17fc13be7cbbacbb1abbd2ad3..4be72eb8251804939e099ded2d04d574f8ed2884 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -21,6 +21,7 @@ use crate::properties::{ TextDecoration, TextRendering, UnicodeBidi, XmlLang, }; use crate::rect::Rect; +use crate::session::Session; use crate::surface_utils::shared_surface::SharedImageSurface; use crate::transform::Transform; use crate::unit_interval::UnitInterval; @@ -137,6 +138,7 @@ pub struct FontProperties { impl StackingContext { pub fn new( + session: &Session, acquired_nodes: &mut AcquiredNodes<'_>, element: &Element, transform: Transform, @@ -190,6 +192,7 @@ impl StackingContext { _ => { rsvg_log!( + session, "element {} references \"{}\" which is not a mask", element, mask_id @@ -200,6 +203,7 @@ impl StackingContext { } } else { rsvg_log!( + session, "element {} references nonexistent mask \"{}\"", element, mask_id @@ -227,13 +231,14 @@ impl StackingContext { } pub fn new_with_link( + session: &Session, acquired_nodes: &mut AcquiredNodes<'_>, element: &Element, transform: Transform, values: &ComputedValues, link_target: Option, ) -> StackingContext { - let mut ctx = Self::new(acquired_nodes, element, transform, values); + let mut ctx = Self::new(session, acquired_nodes, element, transform, values); ctx.link_target = link_target; ctx } diff --git a/src/lib.rs b/src/lib.rs index 3dfb39f467c6081bd41db45b63c0faed90492506..ce602d444bfa5506fa77b3257ba1a36813feb16b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,6 +209,7 @@ mod pattern; mod properties; mod property_defs; mod rect; +mod session; mod shapes; mod space; mod structure; diff --git a/src/log.rs b/src/log.rs index 346fe25e79d11d574a394b9ef19d69487a433672..36a363e20d53388009537cd0713917980a6c4e9d 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,20 +1,75 @@ //! Utilities for logging messages from the library. -use once_cell::sync::Lazy; - #[macro_export] macro_rules! rsvg_log { ( + $session:expr, $($arg:tt)+ ) => { - if $crate::log::log_enabled() { + if $session.log_enabled() { println!("{}", format_args!($($arg)+)); } }; } -pub fn log_enabled() -> bool { - static ENABLED: Lazy = Lazy::new(|| ::std::env::var_os("RSVG_LOG").is_some()); +/// Captures the basic state of a [`cairo::Context`] for logging purposes. +/// +/// A librsvg "transaction" like rendering a +/// [`crate::api::SvgHandle`], which takes a Cairo context, depends on the state of the +/// context as it was passed in by the caller. For example, librsvg may decide to +/// operate differently depending on the context's target surface type, or its current +/// transformation matrix. This struct captures that sort of information. +#[derive(Copy, Clone, Debug, PartialEq)] +struct CairoContextState { + surface_type: cairo::SurfaceType, + matrix: cairo::Matrix, +} + +impl CairoContextState { + #[cfg(test)] + fn new(cr: &cairo::Context) -> Self { + let surface_type = cr.target().type_(); + let matrix = cr.matrix(); + + Self { + surface_type, + matrix, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn captures_cr_state() { + let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, 10, 10).unwrap(); + let cr = cairo::Context::new(&surface).unwrap(); + let state = CairoContextState::new(&cr); + + assert_eq!( + CairoContextState { + surface_type: cairo::SurfaceType::Image, + matrix: cairo::Matrix::identity(), + }, + state, + ); + + let surface = cairo::RecordingSurface::create(cairo::Content::ColorAlpha, None).unwrap(); + let cr = cairo::Context::new(&surface).unwrap(); + cr.scale(2.0, 3.0); + let state = CairoContextState::new(&cr); + + let mut matrix = cairo::Matrix::identity(); + matrix.scale(2.0, 3.0); - *ENABLED + assert_eq!( + CairoContextState { + surface_type: cairo::SurfaceType::Recording, + matrix, + }, + state, + ); + } } diff --git a/src/marker.rs b/src/marker.rs index ee438201a3f6f5a6398fe9666f39c3219e5aa572..e416122ad7324ef5c93433b7019249ca77a4341a 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -20,6 +20,7 @@ use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw}; use crate::parsers::{Parse, ParseValue}; use crate::path_builder::{arc_segment, ArcParameterization, CubicBezierCurve, Path, PathCommand}; use crate::rect::Rect; +use crate::session::Session; use crate::transform::Transform; use crate::viewbox::*; use crate::xml::Attributes; @@ -184,7 +185,8 @@ impl Marker { }; let elt = node.borrow_element(); - let stacking_ctx = StackingContext::new(acquired_nodes, &elt, transform, values); + let stacking_ctx = + StackingContext::new(draw_ctx.session(), acquired_nodes, &elt, transform, values); draw_ctx.with_discrete_layer( &stacking_ctx, @@ -198,7 +200,7 @@ impl Marker { } impl SetAttributes for Marker { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "markerUnits") => self.units = attr.parse(value)?, @@ -596,7 +598,7 @@ fn emit_marker_by_node( } Err(e) => { - rsvg_log!("could not acquire marker: {}", e); + rsvg_log!(draw_ctx.session(), "could not acquire marker: {}", e); Ok(draw_ctx.empty_bbox()) } } diff --git a/src/node.rs b/src/node.rs index 5d99ec9a65bceaa22517b93b1d80f11c1a89cd38..52f655a5a5d640f535db258ac81f9436962c1699 100644 --- a/src/node.rs +++ b/src/node.rs @@ -18,6 +18,7 @@ use crate::element::*; use crate::error::*; use crate::paint_server::PaintSource; use crate::properties::ComputedValues; +use crate::session::Session; use crate::text::Chars; use crate::xml::Attributes; @@ -65,8 +66,8 @@ pub enum NodeData { } impl NodeData { - pub fn new_element(name: &QualName, attrs: Attributes) -> NodeData { - NodeData::Element(Element::new(name, attrs)) + pub fn new_element(session: &Session, name: &QualName, attrs: Attributes) -> NodeData { + NodeData::Element(Element::new(session, name, attrs)) } pub fn new_chars(initial_text: &str) -> NodeData { diff --git a/src/paint_server.rs b/src/paint_server.rs index a744c7286ed533885cdbd7cc71fc930ba79de354..57d4af4686160239c400b40a37f6693e04e4627b 100644 --- a/src/paint_server.rs +++ b/src/paint_server.rs @@ -12,6 +12,7 @@ use crate::node::NodeBorrow; use crate::parsers::Parse; use crate::pattern::{ResolvedPattern, UserSpacePattern}; use crate::properties::ComputedValues; +use crate::session::Session; use crate::unit_interval::UnitInterval; use crate::util; @@ -126,6 +127,7 @@ impl PaintServer { current_color: cssparser::RGBA, context_fill: Option, context_stroke: Option, + session: &Session, ) -> PaintSource { match self { PaintServer::Iri { @@ -139,7 +141,7 @@ impl PaintServer { match *node.borrow_element() { Element::LinearGradient(ref g) => { - g.resolve(node, acquired_nodes, opacity).map(|g| { + g.resolve(node, acquired_nodes, opacity, session).map(|g| { PaintSource::Gradient( g, alternate.map(|c| resolve_color(&c, opacity, current_color)), @@ -147,7 +149,7 @@ impl PaintServer { }) } Element::Pattern(ref p) => { - p.resolve(node, acquired_nodes, opacity).map(|p| { + p.resolve(node, acquired_nodes, opacity, session).map(|p| { PaintSource::Pattern( p, alternate.map(|c| resolve_color(&c, opacity, current_color)), @@ -155,7 +157,7 @@ impl PaintServer { }) } Element::RadialGradient(ref g) => { - g.resolve(node, acquired_nodes, opacity).map(|g| { + g.resolve(node, acquired_nodes, opacity, session).map(|g| { PaintSource::Gradient( g, alternate.map(|c| resolve_color(&c, opacity, current_color)), @@ -180,6 +182,7 @@ impl PaintServer { // condition to that for an invalid paint server. Some(color) => { rsvg_log!( + session, "could not resolve paint server \"{}\", using alternate color", iri ); @@ -189,6 +192,7 @@ impl PaintServer { None => { rsvg_log!( + session, "could not resolve paint server \"{}\", no alternate color specified", iri ); diff --git a/src/pattern.rs b/src/pattern.rs index 42f6aeb3abf3b6a233cf712472b75e9803b387de..6b1d394c5a389bc1d074c6631dbf7d2a7e7343cf 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -15,6 +15,7 @@ use crate::node::{Node, NodeBorrow, WeakNode}; use crate::parsers::ParseValue; use crate::properties::ComputedValues; use crate::rect::Rect; +use crate::session::Session; use crate::transform::{Transform, TransformAttribute}; use crate::unit_interval::UnitInterval; use crate::viewbox::*; @@ -124,7 +125,7 @@ pub struct Pattern { } impl SetAttributes for Pattern { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "patternUnits") => self.common.units = attr.parse(value)?, @@ -436,6 +437,7 @@ impl Pattern { node: &Node, acquired_nodes: &mut AcquiredNodes<'_>, opacity: UnitInterval, + session: &Session, ) -> Result { let Unresolved { mut pattern, @@ -471,7 +473,7 @@ impl Pattern { } Err(e) => { - rsvg_log!("Stopping pattern resolution: {}", e); + rsvg_log!(session, "Stopping pattern resolution: {}", e); pattern = pattern.resolve_from_defaults(); break; } @@ -495,6 +497,7 @@ mod tests { #[test] fn pattern_resolved_from_defaults_is_really_resolved() { let node = Node::new(NodeData::new_element( + &Session::default(), &QualName::new(None, ns!(svg), local_name!("pattern")), Attributes::new(), )); diff --git a/src/properties.rs b/src/properties.rs index 893afaf00ea8d057571217b75af20dcb15db9fd6..7c6c95a43ddfa6b9b453b92510fd9e82aae6e324 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -26,6 +26,7 @@ use crate::css::{DeclParser, Declaration, Origin}; use crate::error::*; use crate::parsers::{Parse, ParseValue}; use crate::property_macros::Property; +use crate::session::Session; use crate::transform::{Transform, TransformAttribute, TransformProperty}; use crate::xml::Attributes; @@ -805,7 +806,7 @@ impl SpecifiedValues { } } - fn parse_one_presentation_attribute(&mut self, attr: QualName, value: &str) { + fn parse_one_presentation_attribute(&mut self, session: &Session, attr: QualName, value: &str) { let mut input = ParserInput::new(value); let mut parser = Parser::new(&mut input); @@ -815,6 +816,7 @@ impl SpecifiedValues { self.set_parsed_property(&prop); } else { rsvg_log!( + session, "(ignoring invalid presentation attribute {:?}\n value=\"{}\")\n", attr.expanded(), value, @@ -839,6 +841,7 @@ impl SpecifiedValues { t.to_css(&mut tok).unwrap(); // FIXME: what do we do with a fmt::Error? rsvg_log!( + session, "(ignoring invalid presentation attribute {:?}\n value=\"{}\"\n \ unexpected token '{}')", attr.expanded(), @@ -852,6 +855,7 @@ impl SpecifiedValues { .. }) => { rsvg_log!( + session, "(ignoring invalid presentation attribute {:?}\n value=\"{}\"\n \ unexpected end of input)", attr.expanded(), @@ -864,6 +868,7 @@ impl SpecifiedValues { .. }) => { rsvg_log!( + session, "(ignoring invalid presentation attribute {:?}\n value=\"{}\"\n \ unexpected error)", attr.expanded(), @@ -876,6 +881,7 @@ impl SpecifiedValues { .. }) => { rsvg_log!( + session, "(ignoring invalid presentation attribute {:?}\n value=\"{}\"\n {})", attr.expanded(), value, @@ -887,6 +893,7 @@ impl SpecifiedValues { pub fn parse_presentation_attributes( &mut self, + session: &Session, attrs: &Attributes, ) -> Result<(), ElementError> { for (attr, value) in attrs.iter() { @@ -918,7 +925,7 @@ impl SpecifiedValues { ))); } - _ => self.parse_one_presentation_attribute(attr, value), + _ => self.parse_one_presentation_attribute(session, attr, value), } } @@ -951,6 +958,7 @@ impl SpecifiedValues { declarations: &str, origin: Origin, important_styles: &mut HashSet, + session: &Session, ) -> Result<(), ElementError> { let mut input = ParserInput::new(declarations); let mut parser = Parser::new(&mut input); @@ -959,7 +967,7 @@ impl SpecifiedValues { .filter_map(|r| match r { Ok(decl) => Some(decl), Err(e) => { - rsvg_log!("Invalid declaration; ignoring: {:?}", e); + rsvg_log!(session, "Invalid declaration; ignoring: {:?}", e); None } }) @@ -1115,7 +1123,7 @@ mod tests { let mut specified = SpecifiedValues::default(); assert!(specified - .parse_style_declarations("", Origin::Author, &mut HashSet::new()) + .parse_style_declarations("", Origin::Author, &mut HashSet::new(), &Session::default()) .is_ok()) } } diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000000000000000000000000000000000000..fe556cef2541d98a5e9b69ee90d0c5f3cd2b624b --- /dev/null +++ b/src/session.rs @@ -0,0 +1,44 @@ +//! Tracks metadata for a loading/rendering session. + +use std::sync::Arc; + +/// Metadata for a loading/rendering session. +/// +/// When the calling program first uses one of the API entry points (e.g. `Loader::new()` +/// in the Rust API, or `rsvg_handle_new()` in the C API), there is no context yet where +/// librsvg's code may start to track things. This struct provides that context. +#[derive(Clone)] +pub struct Session { + inner: Arc, +} + +struct SessionInner { + log_enabled: bool, +} + +fn log_enabled_via_env_var() -> bool { + ::std::env::var_os("RSVG_LOG").is_some() +} + +impl Default for Session { + fn default() -> Self { + Self { + inner: Arc::new(SessionInner { + log_enabled: log_enabled_via_env_var(), + }), + } + } +} + +impl Session { + #[cfg(test)] + pub fn new_for_test_suite() -> Self { + Self { + inner: Arc::new(SessionInner { log_enabled: false }), + } + } + + pub fn log_enabled(&self) -> bool { + self.inner.log_enabled + } +} diff --git a/src/shapes.rs b/src/shapes.rs index 35716c93e821c209a0023835a296ede04d354449..d8565ee0f1a05c1a3cea5341bcb7e02ab595e33c 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -18,6 +18,7 @@ use crate::node::{CascadedValues, Node, NodeBorrow}; use crate::parsers::{optional_comma, Parse, ParseValue}; use crate::path_builder::{LargeArc, Path as SvgPath, PathBuilder, Sweep}; use crate::properties::ComputedValues; +use crate::session::Session; use crate::xml::Attributes; #[derive(PartialEq)] @@ -62,12 +63,15 @@ macro_rules! impl_draw { let stroke = Stroke::new(values, ¶ms); + let session = draw_ctx.session(); + let stroke_paint = values.stroke().0.resolve( acquired_nodes, values.stroke_opacity().0, values.color().0, cascaded.context_fill.clone(), cascaded.context_stroke.clone(), + session, ); let fill_paint = values.fill().0.resolve( @@ -76,6 +80,7 @@ macro_rules! impl_draw { values.color().0, cascaded.context_fill.clone(), cascaded.context_stroke.clone(), + session, ); let fill_rule = values.fill_rule(); @@ -87,9 +92,12 @@ macro_rules! impl_draw { let marker_end_node; if shape_def.markers == Markers::Yes { - marker_start_node = acquire_marker(acquired_nodes, &values.marker_start().0); - marker_mid_node = acquire_marker(acquired_nodes, &values.marker_mid().0); - marker_end_node = acquire_marker(acquired_nodes, &values.marker_end().0); + marker_start_node = + acquire_marker(session, acquired_nodes, &values.marker_start().0); + marker_mid_node = + acquire_marker(session, acquired_nodes, &values.marker_mid().0); + marker_end_node = + acquire_marker(session, acquired_nodes, &values.marker_end().0); } else { marker_start_node = None; marker_mid_node = None; @@ -130,8 +138,13 @@ macro_rules! impl_draw { }; let elt = node.borrow_element(); - let stacking_ctx = - StackingContext::new(acquired_nodes, &elt, values.transform(), values); + let stacking_ctx = StackingContext::new( + draw_ctx.session(), + acquired_nodes, + &elt, + values.transform(), + values, + ); draw_ctx.draw_shape( &view_params, @@ -146,12 +159,16 @@ macro_rules! impl_draw { }; } -fn acquire_marker(acquired_nodes: &mut AcquiredNodes<'_>, iri: &Iri) -> Option { +fn acquire_marker( + session: &Session, + acquired_nodes: &mut AcquiredNodes<'_>, + iri: &Iri, +) -> Option { iri.get().and_then(|id| { acquired_nodes .acquire(id) .map_err(|e| { - rsvg_log!("cannot render marker: {}", e); + rsvg_log!(session, "cannot render marker: {}", e); }) .ok() .and_then(|acquired| { @@ -160,7 +177,7 @@ fn acquire_marker(acquired_nodes: &mut AcquiredNodes<'_>, iri: &Iri) -> Option ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { if attr.expanded() == expanded_name!("", "d") { let mut builder = PathBuilder::default(); @@ -239,7 +256,7 @@ impl SetAttributes for Path { // FIXME: we don't propagate errors upstream, but creating a partial // path is OK per the spec - rsvg_log!("could not parse path: {}", e); + rsvg_log!(session, "could not parse path: {}", e); } self.path = Rc::new(builder.into_path()); } @@ -322,7 +339,7 @@ pub struct Polygon { impl_draw!(Polygon); impl SetAttributes for Polygon { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { if attr.expanded() == expanded_name!("", "points") { self.points = attr.parse(value)?; @@ -347,7 +364,7 @@ pub struct Polyline { impl_draw!(Polyline); impl SetAttributes for Polyline { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { if attr.expanded() == expanded_name!("", "points") { self.points = attr.parse(value)?; @@ -375,7 +392,7 @@ pub struct Line { impl_draw!(Line); impl SetAttributes for Line { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "x1") => self.x1 = attr.parse(value)?, diff --git a/src/structure.rs b/src/structure.rs index dac71a52cb88446ea0763ba70c3fb68f2cc9db1d..cfc8cbcd0f8a4a0b46003d94c028358a325a72c3 100644 --- a/src/structure.rs +++ b/src/structure.rs @@ -16,6 +16,7 @@ use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw}; use crate::parsers::{Parse, ParseValue}; use crate::properties::ComputedValues; use crate::rect::Rect; +use crate::session::Session; use crate::viewbox::*; use crate::xml::Attributes; @@ -36,7 +37,13 @@ impl Draw for Group { let values = cascaded.get(); let elt = node.borrow_element(); - let stacking_ctx = StackingContext::new(acquired_nodes, &elt, values.transform(), values); + let stacking_ctx = StackingContext::new( + draw_ctx.session(), + acquired_nodes, + &elt, + values.transform(), + values, + ); draw_ctx.with_discrete_layer( &stacking_ctx, @@ -77,7 +84,13 @@ impl Draw for Switch { let values = cascaded.get(); let elt = node.borrow_element(); - let stacking_ctx = StackingContext::new(acquired_nodes, &elt, values.transform(), values); + let stacking_ctx = StackingContext::new( + draw_ctx.session(), + acquired_nodes, + &elt, + values.transform(), + values, + ); draw_ctx.with_discrete_layer( &stacking_ctx, @@ -252,7 +265,7 @@ impl Svg { } impl SetAttributes for Svg { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "preserveAspectRatio") => { @@ -279,7 +292,13 @@ impl Draw for Svg { let values = cascaded.get(); let elt = node.borrow_element(); - let stacking_ctx = StackingContext::new(acquired_nodes, &elt, values.transform(), values); + let stacking_ctx = StackingContext::new( + draw_ctx.session(), + acquired_nodes, + &elt, + values.transform(), + values, + ); draw_ctx.with_discrete_layer( &stacking_ctx, @@ -327,7 +346,7 @@ impl Default for Use { } impl SetAttributes for Use { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { ref a if is_href(a) => set_href( @@ -368,6 +387,7 @@ impl Draw for Use { values.color().0, cascaded.context_fill.clone(), cascaded.context_stroke.clone(), + draw_ctx.session(), ); let fill_paint = values.fill().0.resolve( @@ -376,6 +396,7 @@ impl Draw for Use { values.color().0, cascaded.context_fill.clone(), cascaded.context_stroke.clone(), + draw_ctx.session(), ); draw_ctx.draw_from_use_node( @@ -411,7 +432,7 @@ impl Symbol { } impl SetAttributes for Symbol { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "preserveAspectRatio") => { @@ -442,7 +463,7 @@ impl ClipPath { } impl SetAttributes for ClipPath { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { let result = attrs .iter() .find(|(attr, _)| attr.expanded() == expanded_name!("", "clipPathUnits")) @@ -505,7 +526,7 @@ impl Mask { } impl SetAttributes for Mask { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { expanded_name!("", "x") => self.x = attr.parse(value)?, @@ -530,7 +551,7 @@ pub struct Link { } impl SetAttributes for Link { - fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult { + fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) -> ElementResult { for (attr, value) in attrs.iter() { match attr.expanded() { ref a if is_href(a) => set_href(a, &mut self.link, value.to_owned()), @@ -573,6 +594,7 @@ impl Draw for Link { }; let stacking_ctx = StackingContext::new_with_link( + draw_ctx.session(), acquired_nodes, &elt, values.transform(), diff --git a/src/style.rs b/src/style.rs index 0d566c5bebecac882d3612f5c223edf4f6888463..8c3028e92766bb6f343fb99c3373942d1dc1eb1b 100644 --- a/src/style.rs +++ b/src/style.rs @@ -4,6 +4,7 @@ use markup5ever::{expanded_name, local_name, namespace_url, ns}; use crate::element::{Draw, ElementResult, SetAttributes}; use crate::error::*; +use crate::session::Session; use crate::xml::Attributes; /// Represents the syntax used in the