rsvg/
api.rs

1//! Public Rust API for librsvg.
2//!
3//! This gets re-exported from the toplevel `lib.rs`.
4
5#![warn(missing_docs)]
6
7use std::fmt;
8
9// Here we only re-export stuff in the public API.
10pub use crate::{
11    accept_language::{AcceptLanguage, Language},
12    drawing_ctx::Viewport,
13    error::{DefsLookupErrorKind, ImplementationLimit, LoadingError},
14    length::{LengthUnit, RsvgLength as Length},
15};
16
17// Don't merge these in the "pub use" above!  They are not part of the public API!
18use crate::{
19    accept_language::{LanguageTags, UserLanguage},
20    css::{Origin, Stylesheet},
21    document::{Document, LoadOptions, NodeId, RenderingOptions},
22    dpi::Dpi,
23    drawing_ctx::SvgNesting,
24    error::InternalRenderingError,
25    length::NormalizeParams,
26    node::{CascadedValues, Node},
27    rsvg_log,
28    session::Session,
29    url_resolver::UrlResolver,
30};
31
32use url::Url;
33
34use std::path::Path;
35use std::sync::Arc;
36
37use gio::prelude::*; // Re-exposes glib's prelude as well
38use gio::Cancellable;
39
40use locale_config::{LanguageRange, Locale};
41
42/// Errors that can happen while rendering or measuring an SVG document.
43#[non_exhaustive]
44#[derive(Debug, Clone)]
45pub enum RenderingError {
46    /// An error from the rendering backend.
47    Rendering(String),
48
49    /// A particular implementation-defined limit was exceeded.
50    LimitExceeded(ImplementationLimit),
51
52    /// Tried to reference an SVG element that does not exist.
53    IdNotFound,
54
55    /// Tried to reference an SVG element from a fragment identifier that is incorrect.
56    InvalidId(String),
57
58    /// Not enough memory was available for rendering.
59    OutOfMemory(String),
60
61    /// The rendering was interrupted via a [`gio::Cancellable`].
62    ///
63    /// See the documentation for [`CairoRenderer::with_cancellable`].
64    Cancelled,
65}
66
67impl std::error::Error for RenderingError {}
68
69impl From<cairo::Error> for RenderingError {
70    fn from(e: cairo::Error) -> RenderingError {
71        RenderingError::Rendering(format!("{e:?}"))
72    }
73}
74
75impl From<InternalRenderingError> for RenderingError {
76    fn from(e: InternalRenderingError) -> RenderingError {
77        // These enums are mostly the same, except for cases that should definitely
78        // not bubble up to the public API.  So, we just move each variant, and for the
79        // others, we emit a catch-all value as a safeguard.  (We ought to panic in that case,
80        // maybe.)
81        match e {
82            InternalRenderingError::Rendering(s) => RenderingError::Rendering(s),
83            InternalRenderingError::LimitExceeded(l) => RenderingError::LimitExceeded(l),
84            InternalRenderingError::InvalidTransform => {
85                RenderingError::Rendering("invalid transform".to_string())
86            }
87            InternalRenderingError::CircularReference(c) => {
88                RenderingError::Rendering(format!("circular reference in node {c}"))
89            }
90            InternalRenderingError::IdNotFound => RenderingError::IdNotFound,
91            InternalRenderingError::InvalidId(s) => RenderingError::InvalidId(s),
92            InternalRenderingError::OutOfMemory(s) => RenderingError::OutOfMemory(s),
93            InternalRenderingError::Cancelled => RenderingError::Cancelled,
94        }
95    }
96}
97
98impl fmt::Display for RenderingError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match *self {
101            RenderingError::Rendering(ref s) => write!(f, "rendering error: {s}"),
102            RenderingError::LimitExceeded(ref l) => write!(f, "{l}"),
103            RenderingError::IdNotFound => write!(f, "element id not found"),
104            RenderingError::InvalidId(ref s) => write!(f, "invalid id: {s:?}"),
105            RenderingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
106            RenderingError::Cancelled => write!(f, "rendering cancelled"),
107        }
108    }
109}
110
111/// Builder for loading an [`SvgHandle`].
112///
113/// This is the starting point for using librsvg.  This struct
114/// implements a builder pattern for configuring an [`SvgHandle`]'s
115/// options, and then loading the SVG data.  You can call the methods
116/// of `Loader` in sequence to configure how SVG data should be
117/// loaded, and finally use one of the loading functions to load an
118/// [`SvgHandle`].
119pub struct Loader {
120    unlimited_size: bool,
121    keep_image_data: bool,
122    session: Session,
123}
124
125impl Loader {
126    /// Creates a `Loader` with the default flags.
127    ///
128    /// * [`unlimited_size`](#method.with_unlimited_size) defaults to `false`, as malicious
129    ///   SVG documents could cause the XML parser to consume very large amounts of memory.
130    ///
131    /// * [`keep_image_data`](#method.keep_image_data) defaults to
132    ///   `false`.  You may only need this if rendering to Cairo
133    ///   surfaces that support including image data in compressed
134    ///   formats, like PDF.
135    ///
136    /// # Example:
137    ///
138    /// ```
139    /// use rsvg;
140    ///
141    /// let svg_handle = rsvg::Loader::new()
142    ///     .read_path("example.svg")
143    ///     .unwrap();
144    /// ```
145    #[allow(clippy::new_without_default)]
146    pub fn new() -> Self {
147        Self {
148            unlimited_size: false,
149            keep_image_data: false,
150            session: Session::default(),
151        }
152    }
153
154    /// Creates a `Loader` from a pre-created [`Session`].
155    ///
156    /// This is useful when a `Loader` must be created by the C API, which should already
157    /// have created a session for logging.
158    #[cfg(feature = "capi")]
159    pub fn new_with_session(session: Session) -> Self {
160        Self {
161            unlimited_size: false,
162            keep_image_data: false,
163            session,
164        }
165    }
166
167    /// Controls safety limits used in the XML parser.
168    ///
169    /// Internally, librsvg uses libxml2, which has set limits for things like the
170    /// maximum length of XML element names, the size of accumulated buffers
171    /// using during parsing of deeply-nested XML files, and the maximum size
172    /// of embedded XML entities.
173    ///
174    /// Set this to `true` only if loading a trusted SVG fails due to size limits.
175    ///
176    /// # Example:
177    /// ```
178    /// use rsvg;
179    ///
180    /// let svg_handle = rsvg::Loader::new()
181    ///     .with_unlimited_size(true)
182    ///     .read_path("example.svg")    // presumably a trusted huge file
183    ///     .unwrap();
184    /// ```
185    pub fn with_unlimited_size(mut self, unlimited: bool) -> Self {
186        self.unlimited_size = unlimited;
187        self
188    }
189
190    /// Controls embedding of compressed image data into the renderer.
191    ///
192    /// Normally, Cairo expects one to pass it uncompressed (decoded)
193    /// images as surfaces.  However, when using a PDF rendering
194    /// context to render SVG documents that reference raster images
195    /// (e.g. those which include a bitmap as part of the SVG image),
196    /// it may be more efficient to embed the original, compressed raster
197    /// images into the PDF.
198    ///
199    /// Set this to `true` if you are using a Cairo PDF context, or any other type
200    /// of context which allows embedding compressed images.
201    ///
202    /// # Example:
203    ///
204    /// ```
205    /// # use std::env;
206    /// let svg_handle = rsvg::Loader::new()
207    ///     .keep_image_data(true)
208    ///     .read_path("example.svg")
209    ///     .unwrap();
210    ///
211    /// let mut output = env::temp_dir();
212    /// output.push("output.pdf");
213    /// let surface = cairo::PdfSurface::new(640.0, 480.0, output)?;
214    /// let cr = cairo::Context::new(&surface).expect("Failed to create a cairo context");
215    ///
216    /// let renderer = rsvg::CairoRenderer::new(&svg_handle);
217    /// renderer.render_document(
218    ///     &cr,
219    ///     &cairo::Rectangle::new(0.0, 0.0, 640.0, 480.0),
220    /// )?;
221    /// # Ok::<(), rsvg::RenderingError>(())
222    /// ```
223    pub fn keep_image_data(mut self, keep: bool) -> Self {
224        self.keep_image_data = keep;
225        self
226    }
227
228    /// Reads an SVG document from `path`.
229    ///
230    /// # Example:
231    ///
232    /// ```
233    /// let svg_handle = rsvg::Loader::new()
234    ///     .read_path("example.svg")
235    ///     .unwrap();
236    /// ```
237    pub fn read_path<P: AsRef<Path>>(self, path: P) -> Result<SvgHandle, LoadingError> {
238        let file = gio::File::for_path(path);
239        self.read_file(&file, None::<&Cancellable>)
240    }
241
242    /// Reads an SVG document from a `gio::File`.
243    ///
244    /// The `cancellable` can be used to cancel loading from another thread.
245    ///
246    /// # Example:
247    /// ```
248    /// let svg_handle = rsvg::Loader::new()
249    ///     .read_file(&gio::File::for_path("example.svg"), None::<&gio::Cancellable>)
250    ///     .unwrap();
251    /// ```
252    pub fn read_file<F: IsA<gio::File>, P: IsA<Cancellable>>(
253        self,
254        file: &F,
255        cancellable: Option<&P>,
256    ) -> Result<SvgHandle, LoadingError> {
257        let stream = file.read(cancellable)?;
258        self.read_stream(&stream, Some(file), cancellable)
259    }
260
261    /// Reads an SVG stream from a `gio::InputStream`.
262    ///
263    /// The `base_file`, if it is not `None`, is used to extract the
264    /// [base URL][crate#the-base-file-and-resolving-references-to-external-files] for this stream.
265    ///
266    /// Reading an SVG document may involve resolving relative URLs if the
267    /// SVG references things like raster images, or other SVG files.
268    /// In this case, pass the `base_file` that correspondds to the
269    /// URL where this SVG got loaded from.
270    ///
271    /// The `cancellable` can be used to cancel loading from another thread.
272    ///
273    /// # Example
274    ///
275    /// ```
276    /// use gio::prelude::*;
277    ///
278    /// let file = gio::File::for_path("example.svg");
279    ///
280    /// let stream = file.read(None::<&gio::Cancellable>).unwrap();
281    ///
282    /// let svg_handle = rsvg::Loader::new()
283    ///     .read_stream(&stream, Some(&file), None::<&gio::Cancellable>)
284    ///     .unwrap();
285    /// ```
286    pub fn read_stream<S: IsA<gio::InputStream>, F: IsA<gio::File>, P: IsA<Cancellable>>(
287        self,
288        stream: &S,
289        base_file: Option<&F>,
290        cancellable: Option<&P>,
291    ) -> Result<SvgHandle, LoadingError> {
292        let base_file = base_file.map(|f| f.as_ref());
293
294        let base_url = if let Some(base_file) = base_file {
295            Some(url_from_file(base_file)?)
296        } else {
297            None
298        };
299
300        let load_options = LoadOptions::new(UrlResolver::new(base_url))
301            .with_unlimited_size(self.unlimited_size)
302            .keep_image_data(self.keep_image_data);
303
304        Ok(SvgHandle {
305            document: Document::load_from_stream(
306                self.session.clone(),
307                Arc::new(load_options),
308                stream.as_ref(),
309                cancellable.map(|c| c.as_ref()),
310            )?,
311            session: self.session,
312        })
313    }
314}
315
316fn url_from_file(file: &gio::File) -> Result<Url, LoadingError> {
317    Url::parse(&file.uri()).map_err(|_| LoadingError::BadUrl)
318}
319
320/// Handle used to hold SVG data in memory.
321///
322/// You can create this from one of the `read` methods in
323/// [`Loader`].
324pub struct SvgHandle {
325    session: Session,
326    pub(crate) document: Document,
327}
328
329// Public API goes here
330impl SvgHandle {
331    /// Checks if the SVG has an element with the specified `id`.
332    ///
333    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
334    /// a leading `#` character.
335    ///
336    /// The purpose of the `Err()` case in the return value is to indicate an
337    /// incorrectly-formatted `id` argument.
338    pub fn has_element_with_id(&self, id: &str) -> Result<bool, RenderingError> {
339        let node_id = self.get_node_id(id)?;
340
341        match self.lookup_node(&node_id) {
342            Ok(_) => Ok(true),
343
344            Err(InternalRenderingError::IdNotFound) => Ok(false),
345
346            Err(e) => Err(e.into()),
347        }
348    }
349
350    /// Sets a CSS stylesheet to use for an SVG document.
351    ///
352    /// During the CSS cascade, the specified stylesheet will be used
353    /// with a "User" [origin].
354    ///
355    /// Note that `@import` rules will not be resolved, except for `data:` URLs.
356    ///
357    /// [origin]: https://drafts.csswg.org/css-cascade-3/#cascading-origins
358    pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> {
359        let stylesheet = Stylesheet::from_data(
360            css,
361            &UrlResolver::new(None),
362            Origin::User,
363            self.session.clone(),
364        )?;
365        self.document.cascade(&[stylesheet], &self.session);
366        Ok(())
367    }
368}
369
370// Private methods go here
371impl SvgHandle {
372    fn get_node_id_or_root(&self, id: Option<&str>) -> Result<Option<NodeId>, RenderingError> {
373        match id {
374            None => Ok(None),
375            Some(s) => Ok(Some(self.get_node_id(s)?)),
376        }
377    }
378
379    fn get_node_id(&self, id: &str) -> Result<NodeId, RenderingError> {
380        let node_id = NodeId::parse(id).map_err(|_| RenderingError::InvalidId(id.to_string()))?;
381
382        // The public APIs to get geometries of individual elements, or to render
383        // them, should only allow referencing elements within the main handle's
384        // SVG file; that is, only plain "#foo" fragment IDs are allowed here.
385        // Otherwise, a calling program could request "another-file#foo" and cause
386        // another-file to be loaded, even if it is not part of the set of
387        // resources that the main SVG actually references.  In the future we may
388        // relax this requirement to allow lookups within that set, but not to
389        // other random files.
390        match node_id {
391            NodeId::Internal(_) => Ok(node_id),
392            NodeId::External(_, _) => {
393                rsvg_log!(
394                    self.session,
395                    "the public API is not allowed to look up external references: {}",
396                    node_id
397                );
398
399                Err(RenderingError::InvalidId(
400                    "cannot lookup references to elements in external files".to_string(),
401                ))
402            }
403        }
404    }
405
406    fn get_node_or_root(&self, node_id: &Option<NodeId>) -> Result<Node, InternalRenderingError> {
407        if let Some(ref node_id) = *node_id {
408            Ok(self.lookup_node(node_id)?)
409        } else {
410            Ok(self.document.root())
411        }
412    }
413
414    fn lookup_node(&self, node_id: &NodeId) -> Result<Node, InternalRenderingError> {
415        // The public APIs to get geometries of individual elements, or to render
416        // them, should only allow referencing elements within the main handle's
417        // SVG file; that is, only plain "#foo" fragment IDs are allowed here.
418        // Otherwise, a calling program could request "another-file#foo" and cause
419        // another-file to be loaded, even if it is not part of the set of
420        // resources that the main SVG actually references.  In the future we may
421        // relax this requirement to allow lookups within that set, but not to
422        // other random files.
423        match node_id {
424            NodeId::Internal(id) => self
425                .document
426                .lookup_internal_node(id)
427                .ok_or(InternalRenderingError::IdNotFound),
428            NodeId::External(_, _) => {
429                unreachable!("caller should already have validated internal node IDs only")
430            }
431        }
432    }
433}
434
435/// Can render an `SvgHandle` to a Cairo context.
436pub struct CairoRenderer<'a> {
437    pub(crate) handle: &'a SvgHandle,
438    pub(crate) dpi: Dpi,
439    user_language: UserLanguage,
440    cancellable: Option<gio::Cancellable>,
441    is_testing: bool,
442}
443
444// Note that these are different than the C API's default, which is 90.
445const DEFAULT_DPI_X: f64 = 96.0;
446const DEFAULT_DPI_Y: f64 = 96.0;
447
448#[derive(Debug, Copy, Clone, PartialEq)]
449/// Contains the computed values of the `<svg>` element's `width`, `height`, and `viewBox`.
450///
451/// An SVG document has a toplevel `<svg>` element, with optional attributes `width`,
452/// `height`, and `viewBox`.  This structure contains the values for those attributes; you
453/// can obtain the struct from [`CairoRenderer::intrinsic_dimensions`].
454///
455/// Since librsvg 2.54.0, there is support for [geometry
456/// properties](https://www.w3.org/TR/SVG2/geometry.html) from SVG2.  This means that
457/// `width` and `height` are no longer attributes; they are instead CSS properties that
458/// default to `auto`.  The computed value for `auto` is `100%`, so for a `<svg>` that
459/// does not have these attributes/properties, the `width`/`height` fields will be
460/// returned as a [`Length`] of 100%.
461///
462/// As an example, the following SVG element has a `width` of 100 pixels
463/// and a `height` of 400 pixels, but no `viewBox`.
464///
465/// ```xml
466/// <svg xmlns="http://www.w3.org/2000/svg" width="100" height="400">
467/// ```
468///
469/// In this case, the length fields will be set to the corresponding
470/// values with [`LengthUnit::Px`] units, and the `vbox` field will be
471/// set to to `None`.
472pub struct IntrinsicDimensions {
473    /// Computed value of the `width` property of the `<svg>`.
474    pub width: Length,
475
476    /// Computed value of the `height` property of the `<svg>`.
477    pub height: Length,
478
479    /// `viewBox` attribute of the `<svg>`, if present.
480    pub vbox: Option<cairo::Rectangle>,
481}
482
483/// Gets the user's preferred locale from the environment and
484/// translates it to a `Locale` with `LanguageRange` fallbacks.
485///
486/// The `Locale::current()` call only contemplates a single language,
487/// but glib is smarter, and `g_get_langauge_names()` can provide
488/// fallbacks, for example, when LC_MESSAGES="en_US.UTF-8:de" (USA
489/// English and German).  This function converts the output of
490/// `g_get_language_names()` into a `Locale` with appropriate
491/// fallbacks.
492fn locale_from_environment() -> Locale {
493    let mut locale = Locale::invariant();
494
495    for name in glib::language_names() {
496        let name = name.as_str();
497        if let Ok(range) = LanguageRange::from_unix(name) {
498            locale.add(&range);
499        }
500    }
501
502    locale
503}
504
505impl UserLanguage {
506    fn new(language: &Language, session: &Session) -> UserLanguage {
507        match *language {
508            Language::FromEnvironment => UserLanguage::LanguageTags(
509                LanguageTags::from_locale(&locale_from_environment())
510                    .map_err(|s| {
511                        rsvg_log!(session, "could not convert locale to language tags: {}", s);
512                    })
513                    .unwrap_or_else(|_| LanguageTags::empty()),
514            ),
515
516            Language::AcceptLanguage(ref a) => UserLanguage::AcceptLanguage(a.clone()),
517        }
518    }
519}
520
521impl<'a> CairoRenderer<'a> {
522    /// Creates a `CairoRenderer` for the specified `SvgHandle`.
523    ///
524    /// The default dots-per-inch (DPI) value is set to 96; you can change it
525    /// with the [`with_dpi`] method.
526    ///
527    /// [`with_dpi`]: #method.with_dpi
528    pub fn new(handle: &'a SvgHandle) -> Self {
529        let session = &handle.session;
530
531        CairoRenderer {
532            handle,
533            dpi: Dpi::new(DEFAULT_DPI_X, DEFAULT_DPI_Y),
534            user_language: UserLanguage::new(&Language::FromEnvironment, session),
535            cancellable: None,
536            is_testing: false,
537        }
538    }
539
540    /// Configures the dots-per-inch for resolving physical lengths.
541    ///
542    /// If an SVG document has physical units like `5cm`, they must be resolved
543    /// to pixel-based values.  The default pixel density is 96 DPI in
544    /// both dimensions.
545    pub fn with_dpi(self, dpi_x: f64, dpi_y: f64) -> Self {
546        assert!(dpi_x > 0.0);
547        assert!(dpi_y > 0.0);
548
549        CairoRenderer {
550            dpi: Dpi::new(dpi_x, dpi_y),
551            ..self
552        }
553    }
554
555    /// Configures the set of languages used for rendering.
556    ///
557    /// SVG documents can use the `<switch>` element, whose children have a
558    /// `systemLanguage` attribute; only the first child which has a `systemLanguage` that
559    /// matches the preferred languages will be rendered.
560    ///
561    /// This function sets the preferred languages.  The default is
562    /// `Language::FromEnvironment`, which means that the set of preferred languages will
563    /// be obtained from the program's environment.  To set an explicit list of languages,
564    /// you can use `Language::AcceptLanguage` instead.
565    pub fn with_language(self, language: &Language) -> Self {
566        let user_language = UserLanguage::new(language, &self.handle.session);
567
568        CairoRenderer {
569            user_language,
570            ..self
571        }
572    }
573
574    /// Sets a cancellable to be able to interrupt rendering.
575    ///
576    /// The rendering functions like [`render_document`] will normally render the whole
577    /// SVG document tree.  However, they can be interrupted if you set a `cancellable`
578    /// object with this method.  To interrupt rendering, you can call
579    /// [`gio::CancellableExt::cancel()`] from a different thread than where the rendering
580    /// is happening.
581    ///
582    /// Since rendering happens as a side-effect on the Cairo context (`cr`) that is
583    /// passed to the rendering functions, it may be that the `cr`'s target surface is in
584    /// an undefined state if the rendering is cancelled.  The surface may have not yet
585    /// been painted on, or it may contain a partially-rendered document.  For this
586    /// reason, if your application does not want to leave the target surface in an
587    /// inconsistent state, you may prefer to use a temporary surface for rendering, which
588    /// can be discarded if your code cancels the rendering.
589    ///
590    /// [`render_document`]: #method.render_document
591    pub fn with_cancellable<C: IsA<Cancellable>>(self, cancellable: &C) -> Self {
592        CairoRenderer {
593            cancellable: Some(cancellable.clone().into()),
594            ..self
595        }
596    }
597
598    /// Queries the `width`, `height`, and `viewBox` attributes in an SVG document.
599    ///
600    /// If you are calling this function to compute a scaling factor to render the SVG,
601    /// consider simply using [`render_document`] instead; it will do the scaling
602    /// computations automatically.
603    ///
604    /// See also [`intrinsic_size_in_pixels`], which does the conversion to pixels if
605    /// possible.
606    ///
607    /// [`render_document`]: #method.render_document
608    /// [`intrinsic_size_in_pixels`]: #method.intrinsic_size_in_pixels
609    pub fn intrinsic_dimensions(&self) -> IntrinsicDimensions {
610        let d = self.handle.document.get_intrinsic_dimensions();
611
612        IntrinsicDimensions {
613            width: Into::into(d.width),
614            height: Into::into(d.height),
615            vbox: d.vbox.map(|v| cairo::Rectangle::from(*v)),
616        }
617    }
618
619    /// Converts the SVG document's intrinsic dimensions to pixels, if possible.
620    ///
621    /// Returns `Some(width, height)` in pixel units if the SVG document has `width` and
622    /// `height` attributes with physical dimensions (CSS pixels, cm, in, etc.) or
623    /// font-based dimensions (em, ex).
624    ///
625    /// Note that the dimensions are floating-point numbers, so your application can know
626    /// the exact size of an SVG document.  To get integer dimensions, you should use
627    /// [`f64::ceil()`] to round up to the nearest integer (just using [`f64::round()`],
628    /// may may chop off pixels with fractional coverage).
629    ///
630    /// If the SVG document has percentage-based `width` and `height` attributes, or if
631    /// either of those attributes are not present, returns `None`.  Dimensions of that
632    /// kind require more information to be resolved to pixels; for example, the calling
633    /// application can use a viewport size to scale percentage-based dimensions.
634    pub fn intrinsic_size_in_pixels(&self) -> Option<(f64, f64)> {
635        let dim = self.intrinsic_dimensions();
636        let width = dim.width;
637        let height = dim.height;
638
639        if width.unit == LengthUnit::Percent || height.unit == LengthUnit::Percent {
640            return None;
641        }
642
643        Some(self.width_height_to_user(self.dpi))
644    }
645
646    fn rendering_options(&self) -> RenderingOptions {
647        RenderingOptions {
648            dpi: self.dpi,
649            cancellable: self.cancellable.clone(),
650            user_language: self.user_language.clone(),
651            svg_nesting: SvgNesting::Standalone,
652            testing: self.is_testing,
653        }
654    }
655
656    /// Renders the whole SVG document fitted to a viewport
657    ///
658    /// The `viewport` gives the position and size at which the whole SVG
659    /// document will be rendered.
660    ///
661    /// The `cr` must be in a `cairo::Status::Success` state, or this function
662    /// will not render anything, and instead will return
663    /// `RenderingError::Cairo` with the `cr`'s current error state.
664    pub fn render_document(
665        &self,
666        cr: &cairo::Context,
667        viewport: &cairo::Rectangle,
668    ) -> Result<(), RenderingError> {
669        Ok(self.handle.document.render_document(
670            &self.handle.session,
671            cr,
672            viewport,
673            &self.rendering_options(),
674        )?)
675    }
676
677    /// Computes the (ink_rect, logical_rect) of an SVG element, as if
678    /// the SVG were rendered to a specific viewport.
679    ///
680    /// Element IDs should look like an URL fragment identifier; for
681    /// example, pass `Some("#foo")` to get the geometry of the
682    /// element that has an `id="foo"` attribute.
683    ///
684    /// The "ink rectangle" is the bounding box that would be painted
685    /// for fully- stroked and filled elements.
686    ///
687    /// The "logical rectangle" just takes into account the unstroked
688    /// paths and text outlines.
689    ///
690    /// Note that these bounds are not minimum bounds; for example,
691    /// clipping paths are not taken into account.
692    ///
693    /// You can pass `None` for the `id` if you want to measure all
694    /// the elements in the SVG, i.e. to measure everything from the
695    /// root element.
696    ///
697    /// This operation is not constant-time, as it involves going through all
698    /// the child elements.
699    ///
700    /// FIXME: example
701    pub fn geometry_for_layer(
702        &self,
703        id: Option<&str>,
704        viewport: &cairo::Rectangle,
705    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
706        let node_id = self.handle.get_node_id_or_root(id)?;
707        let node = self.handle.get_node_or_root(&node_id)?;
708
709        Ok(self.handle.document.get_geometry_for_layer(
710            &self.handle.session,
711            node,
712            viewport,
713            &self.rendering_options(),
714        )?)
715    }
716
717    /// Renders a single SVG element in the same place as for a whole SVG document
718    ///
719    /// This is equivalent to `render_document`, but renders only a single element and its
720    /// children, as if they composed an individual layer in the SVG.  The element is
721    /// rendered with the same transformation matrix as it has within the whole SVG
722    /// document.  Applications can use this to re-render a single element and repaint it
723    /// on top of a previously-rendered document, for example.
724    ///
725    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
726    /// a leading `#` character.
727    ///
728    /// The `viewport` gives the position and size at which the whole SVG
729    /// document would be rendered.  This function will effectively place the
730    /// whole SVG within that viewport, but only render the element given by
731    /// `id`.
732    ///
733    /// The `cr` must be in a `cairo::Status::Success` state, or this function
734    /// will not render anything, and instead will return
735    /// `RenderingError::Cairo` with the `cr`'s current error state.
736    pub fn render_layer(
737        &self,
738        cr: &cairo::Context,
739        id: Option<&str>,
740        viewport: &cairo::Rectangle,
741    ) -> Result<(), RenderingError> {
742        let node_id = self.handle.get_node_id_or_root(id)?;
743        let node = self.handle.get_node_or_root(&node_id)?;
744
745        Ok(self.handle.document.render_layer(
746            &self.handle.session,
747            cr,
748            node,
749            viewport,
750            &self.rendering_options(),
751        )?)
752    }
753
754    /// Computes the (ink_rect, logical_rect) of a single SVG element
755    ///
756    /// While `geometry_for_layer` computes the geometry of an SVG element subtree with
757    /// its transformation matrix, this other function will compute the element's geometry
758    /// as if it were being rendered under an identity transformation by itself.  That is,
759    /// the resulting geometry is as if the element got extracted by itself from the SVG.
760    ///
761    /// This function is the counterpart to `render_element`.
762    ///
763    /// Element IDs should look like an URL fragment identifier; for
764    /// example, pass `Some("#foo")` to get the geometry of the
765    /// element that has an `id="foo"` attribute.
766    ///
767    /// The "ink rectangle" is the bounding box that would be painted
768    /// for fully- stroked and filled elements.
769    ///
770    /// The "logical rectangle" just takes into account the unstroked
771    /// paths and text outlines.
772    ///
773    /// Note that these bounds are not minimum bounds; for example,
774    /// clipping paths are not taken into account.
775    ///
776    /// You can pass `None` for the `id` if you want to measure all
777    /// the elements in the SVG, i.e. to measure everything from the
778    /// root element.
779    ///
780    /// This operation is not constant-time, as it involves going through all
781    /// the child elements.
782    ///
783    /// FIXME: example
784    pub fn geometry_for_element(
785        &self,
786        id: Option<&str>,
787    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
788        let node_id = self.handle.get_node_id_or_root(id)?;
789        let node = self.handle.get_node_or_root(&node_id)?;
790
791        Ok(self.handle.document.get_geometry_for_element(
792            &self.handle.session,
793            node,
794            &self.rendering_options(),
795        )?)
796    }
797
798    /// Renders a single SVG element to a given viewport
799    ///
800    /// This function can be used to extract individual element subtrees and render them,
801    /// scaled to a given `element_viewport`.  This is useful for applications which have
802    /// reusable objects in an SVG and want to render them individually; for example, an
803    /// SVG full of icons that are meant to be be rendered independently of each other.
804    ///
805    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
806    /// a leading `#` character.
807    ///
808    /// The `element_viewport` gives the position and size at which the named element will
809    /// be rendered.  FIXME: mention proportional scaling.
810    ///
811    /// The `cr` must be in a `cairo::Status::Success` state, or this function
812    /// will not render anything, and instead will return
813    /// `RenderingError::Cairo` with the `cr`'s current error state.
814    pub fn render_element(
815        &self,
816        cr: &cairo::Context,
817        id: Option<&str>,
818        element_viewport: &cairo::Rectangle,
819    ) -> Result<(), RenderingError> {
820        let node_id = self.handle.get_node_id_or_root(id)?;
821        let node = self.handle.get_node_or_root(&node_id)?;
822
823        Ok(self.handle.document.render_element(
824            &self.handle.session,
825            cr,
826            node,
827            element_viewport,
828            &self.rendering_options(),
829        )?)
830    }
831
832    #[doc(hidden)]
833    #[cfg(feature = "capi")]
834    pub fn dpi(&self) -> Dpi {
835        self.dpi
836    }
837
838    /// Normalizes the svg's width/height properties with a 0-sized viewport
839    ///
840    /// This assumes that if one of the properties is in percentage units, then
841    /// its corresponding value will not be used.  E.g. if width=100%, the caller
842    /// will ignore the resulting width value.
843    #[doc(hidden)]
844    pub fn width_height_to_user(&self, dpi: Dpi) -> (f64, f64) {
845        let dimensions = self.handle.document.get_intrinsic_dimensions();
846
847        let width = dimensions.width;
848        let height = dimensions.height;
849
850        let viewport = Viewport::new(dpi, 0.0, 0.0);
851        let root = self.handle.document.root();
852        let cascaded = CascadedValues::new_from_node(&root);
853        let values = cascaded.get();
854
855        let params = NormalizeParams::new(values, &viewport);
856
857        (width.to_user(&params), height.to_user(&params))
858    }
859
860    #[doc(hidden)]
861    #[cfg(feature = "capi")]
862    pub fn test_mode(self, is_testing: bool) -> Self {
863        CairoRenderer { is_testing, ..self }
864    }
865}