Skip to main content

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