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::{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, LoadingDepthLimiter, 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 LoadingDepthLimiter::new(),
307 stream.as_ref(),
308 cancellable.map(|c| c.as_ref()),
309 )?,
310 session: self.session,
311 })
312 }
313}
314
315fn url_from_file(file: &gio::File) -> Result<Url, LoadingError> {
316 Url::parse(&file.uri()).map_err(|_| LoadingError::BadUrl)
317}
318
319/// Handle used to hold SVG data in memory.
320///
321/// You can create this from one of the `read` methods in
322/// [`Loader`].
323pub struct SvgHandle {
324 session: Session,
325 pub(crate) document: Document,
326}
327
328// Public API goes here
329impl SvgHandle {
330 /// Checks if the SVG has an element with the specified `id`.
331 ///
332 /// Note that the `id` must be a plain fragment identifier like `#foo`, with
333 /// a leading `#` character.
334 ///
335 /// The purpose of the `Err()` case in the return value is to indicate an
336 /// incorrectly-formatted `id` argument.
337 pub fn has_element_with_id(&self, id: &str) -> Result<bool, RenderingError> {
338 let node_id = self.get_node_id(id)?;
339
340 match self.lookup_node(&node_id) {
341 Ok(_) => Ok(true),
342
343 Err(InternalRenderingError::IdNotFound) => Ok(false),
344
345 Err(e) => Err(e.into()),
346 }
347 }
348
349 /// Sets a CSS stylesheet to use for an SVG document.
350 ///
351 /// During the CSS cascade, the specified stylesheet will be used
352 /// with a "User" [origin].
353 ///
354 /// Note that `@import` rules will not be resolved, except for `data:` URLs.
355 ///
356 /// [origin]: https://drafts.csswg.org/css-cascade-3/#cascading-origins
357 pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> {
358 let stylesheet = Stylesheet::from_data(
359 css,
360 &UrlResolver::new(None),
361 Origin::User,
362 LoadingDepthLimiter::new(),
363 self.session.clone(),
364 )?;
365 self.document.cascade(&[stylesheet]);
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
483impl<'a> CairoRenderer<'a> {
484 /// Creates a `CairoRenderer` for the specified `SvgHandle`.
485 ///
486 /// The default dots-per-inch (DPI) value is set to 96; you can change it
487 /// with the [`with_dpi`] method.
488 ///
489 /// [`with_dpi`]: #method.with_dpi
490 pub fn new(handle: &'a SvgHandle) -> Self {
491 CairoRenderer {
492 handle,
493 dpi: Dpi::new(DEFAULT_DPI_X, DEFAULT_DPI_Y),
494 user_language: UserLanguage::new(&Language::FromEnvironment),
495 cancellable: None,
496 is_testing: false,
497 }
498 }
499
500 /// Configures the dots-per-inch for resolving physical lengths.
501 ///
502 /// If an SVG document has physical units like `5cm`, they must be resolved
503 /// to pixel-based values. The default pixel density is 96 DPI in
504 /// both dimensions.
505 pub fn with_dpi(self, dpi_x: f64, dpi_y: f64) -> Self {
506 assert!(dpi_x > 0.0);
507 assert!(dpi_y > 0.0);
508
509 CairoRenderer {
510 dpi: Dpi::new(dpi_x, dpi_y),
511 ..self
512 }
513 }
514
515 /// Configures the set of languages used for rendering.
516 ///
517 /// SVG documents can use the `<switch>` element, whose children have a
518 /// `systemLanguage` attribute; only the first child which has a `systemLanguage` that
519 /// matches the preferred languages will be rendered.
520 ///
521 /// This function sets the preferred languages. The default is
522 /// `Language::FromEnvironment`, which means that the set of preferred languages will
523 /// be obtained from the program's environment. To set an explicit list of languages,
524 /// you can use `Language::AcceptLanguage` instead.
525 pub fn with_language(self, language: &Language) -> Self {
526 let user_language = UserLanguage::new(language);
527
528 CairoRenderer {
529 user_language,
530 ..self
531 }
532 }
533
534 /// Sets a cancellable to be able to interrupt rendering.
535 ///
536 /// The rendering functions like [`render_document`] will normally render the whole
537 /// SVG document tree. However, they can be interrupted if you set a `cancellable`
538 /// object with this method. To interrupt rendering, you can call
539 /// [`gio::prelude::CancellableExt::cancel()`] from a different thread than where the rendering
540 /// is happening.
541 ///
542 /// Since rendering happens as a side-effect on the Cairo context (`cr`) that is
543 /// passed to the rendering functions, it may be that the `cr`'s target surface is in
544 /// an undefined state if the rendering is cancelled. The surface may have not yet
545 /// been painted on, or it may contain a partially-rendered document. For this
546 /// reason, if your application does not want to leave the target surface in an
547 /// inconsistent state, you may prefer to use a temporary surface for rendering, which
548 /// can be discarded if your code cancels the rendering.
549 ///
550 /// [`render_document`]: #method.render_document
551 pub fn with_cancellable<C: IsA<Cancellable>>(self, cancellable: &C) -> Self {
552 CairoRenderer {
553 cancellable: Some(cancellable.clone().into()),
554 ..self
555 }
556 }
557
558 /// Queries the `width`, `height`, and `viewBox` attributes in an SVG document.
559 ///
560 /// If you are calling this function to compute a scaling factor to render the SVG,
561 /// consider simply using [`render_document`] instead; it will do the scaling
562 /// computations automatically.
563 ///
564 /// See also [`intrinsic_size_in_pixels`], which does the conversion to pixels if
565 /// possible.
566 ///
567 /// [`render_document`]: #method.render_document
568 /// [`intrinsic_size_in_pixels`]: #method.intrinsic_size_in_pixels
569 pub fn intrinsic_dimensions(&self) -> IntrinsicDimensions {
570 let d = self.handle.document.get_intrinsic_dimensions();
571
572 IntrinsicDimensions {
573 width: Into::into(d.width),
574 height: Into::into(d.height),
575 vbox: d.vbox.map(|v| cairo::Rectangle::from(*v)),
576 }
577 }
578
579 /// Converts the SVG document's intrinsic dimensions to pixels, if possible.
580 ///
581 /// Returns `Some(width, height)` in pixel units if the SVG document has `width` and
582 /// `height` attributes with physical dimensions (CSS pixels, cm, in, etc.) or
583 /// font-based dimensions (em, ex).
584 ///
585 /// Note that the dimensions are floating-point numbers, so your application can know
586 /// the exact size of an SVG document. To get integer dimensions, you should use
587 /// [`f64::ceil()`] to round up to the nearest integer (just using [`f64::round()`],
588 /// may may chop off pixels with fractional coverage).
589 ///
590 /// If the SVG document has percentage-based `width` and `height` attributes, or if
591 /// either of those attributes are not present, returns `None`. Dimensions of that
592 /// kind require more information to be resolved to pixels; for example, the calling
593 /// application can use a viewport size to scale percentage-based dimensions.
594 pub fn intrinsic_size_in_pixels(&self) -> Option<(f64, f64)> {
595 let dim = self.intrinsic_dimensions();
596 let width = dim.width;
597 let height = dim.height;
598
599 if width.unit == LengthUnit::Percent || height.unit == LengthUnit::Percent {
600 return None;
601 }
602
603 Some(self.width_height_to_user(self.dpi))
604 }
605
606 fn rendering_options(&self) -> RenderingOptions {
607 RenderingOptions {
608 dpi: self.dpi,
609 cancellable: self.cancellable.clone(),
610 user_language: self.user_language.clone(),
611 svg_nesting: SvgNesting::Standalone,
612 testing: self.is_testing,
613 }
614 }
615
616 /// Renders the whole SVG document fitted to a viewport
617 ///
618 /// The `viewport` gives the position and size at which the whole SVG
619 /// document will be rendered.
620 ///
621 /// The `cr` must be in a `cairo::Status::Success` state, or this function
622 /// will not render anything, and instead will return
623 /// `RenderingError::Cairo` with the `cr`'s current error state.
624 pub fn render_document(
625 &self,
626 cr: &cairo::Context,
627 viewport: &cairo::Rectangle,
628 ) -> Result<(), RenderingError> {
629 Ok(self
630 .handle
631 .document
632 .render_document(cr, viewport, &self.rendering_options())?)
633 }
634
635 /// Computes the (ink_rect, logical_rect) of an SVG element, as if
636 /// the SVG were rendered to a specific viewport.
637 ///
638 /// Element IDs should look like an URL fragment identifier; for
639 /// example, pass `Some("#foo")` to get the geometry of the
640 /// element that has an `id="foo"` attribute.
641 ///
642 /// The "ink rectangle" is the bounding box that would be painted
643 /// for fully- stroked and filled elements.
644 ///
645 /// The "logical rectangle" just takes into account the unstroked
646 /// paths and text outlines.
647 ///
648 /// Note that these bounds are not minimum bounds; for example,
649 /// clipping paths are not taken into account.
650 ///
651 /// You can pass `None` for the `id` if you want to measure all
652 /// the elements in the SVG, i.e. to measure everything from the
653 /// root element.
654 ///
655 /// This operation is not constant-time, as it involves going through all
656 /// the child elements.
657 ///
658 /// FIXME: example
659 pub fn geometry_for_layer(
660 &self,
661 id: Option<&str>,
662 viewport: &cairo::Rectangle,
663 ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
664 let node_id = self.handle.get_node_id_or_root(id)?;
665 let node = self.handle.get_node_or_root(&node_id)?;
666
667 Ok(self.handle.document.get_geometry_for_layer(
668 node,
669 viewport,
670 &self.rendering_options(),
671 )?)
672 }
673
674 /// Renders a single SVG element in the same place as for a whole SVG document
675 ///
676 /// This is equivalent to `render_document`, but renders only a single element and its
677 /// children, as if they composed an individual layer in the SVG. The element is
678 /// rendered with the same transformation matrix as it has within the whole SVG
679 /// document. Applications can use this to re-render a single element and repaint it
680 /// on top of a previously-rendered document, for example.
681 ///
682 /// Note that the `id` must be a plain fragment identifier like `#foo`, with
683 /// a leading `#` character.
684 ///
685 /// The `viewport` gives the position and size at which the whole SVG
686 /// document would be rendered. This function will effectively place the
687 /// whole SVG within that viewport, but only render the element given by
688 /// `id`.
689 ///
690 /// The `cr` must be in a `cairo::Status::Success` state, or this function
691 /// will not render anything, and instead will return
692 /// `RenderingError::Cairo` with the `cr`'s current error state.
693 pub fn render_layer(
694 &self,
695 cr: &cairo::Context,
696 id: Option<&str>,
697 viewport: &cairo::Rectangle,
698 ) -> Result<(), RenderingError> {
699 let node_id = self.handle.get_node_id_or_root(id)?;
700 let node = self.handle.get_node_or_root(&node_id)?;
701
702 Ok(self
703 .handle
704 .document
705 .render_layer(cr, node, viewport, &self.rendering_options())?)
706 }
707
708 /// Computes the (ink_rect, logical_rect) of a single SVG element
709 ///
710 /// While `geometry_for_layer` computes the geometry of an SVG element subtree with
711 /// its transformation matrix, this other function will compute the element's geometry
712 /// as if it were being rendered under an identity transformation by itself. That is,
713 /// the resulting geometry is as if the element got extracted by itself from the SVG.
714 ///
715 /// This function is the counterpart to `render_element`.
716 ///
717 /// Element IDs should look like an URL fragment identifier; for
718 /// example, pass `Some("#foo")` to get the geometry of the
719 /// element that has an `id="foo"` attribute.
720 ///
721 /// The "ink rectangle" is the bounding box that would be painted
722 /// for fully- stroked and filled elements.
723 ///
724 /// The "logical rectangle" just takes into account the unstroked
725 /// paths and text outlines.
726 ///
727 /// Note that these bounds are not minimum bounds; for example,
728 /// clipping paths are not taken into account.
729 ///
730 /// You can pass `None` for the `id` if you want to measure all
731 /// the elements in the SVG, i.e. to measure everything from the
732 /// root element.
733 ///
734 /// This operation is not constant-time, as it involves going through all
735 /// the child elements.
736 ///
737 /// FIXME: example
738 pub fn geometry_for_element(
739 &self,
740 id: Option<&str>,
741 ) -> Result<(cairo::Rectangle, cairo::Rectangle), 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
746 .handle
747 .document
748 .get_geometry_for_element(node, &self.rendering_options())?)
749 }
750
751 /// Renders a single SVG element to a given viewport
752 ///
753 /// This function can be used to extract individual element subtrees and render them,
754 /// scaled to a given `element_viewport`. This is useful for applications which have
755 /// reusable objects in an SVG and want to render them individually; for example, an
756 /// SVG full of icons that are meant to be be rendered independently of each other.
757 ///
758 /// Note that the `id` must be a plain fragment identifier like `#foo`, with
759 /// a leading `#` character.
760 ///
761 /// The `element_viewport` gives the position and size at which the named element will
762 /// be rendered. FIXME: mention proportional scaling.
763 ///
764 /// The `cr` must be in a `cairo::Status::Success` state, or this function
765 /// will not render anything, and instead will return
766 /// `RenderingError::Cairo` with the `cr`'s current error state.
767 pub fn render_element(
768 &self,
769 cr: &cairo::Context,
770 id: Option<&str>,
771 element_viewport: &cairo::Rectangle,
772 ) -> Result<(), RenderingError> {
773 let node_id = self.handle.get_node_id_or_root(id)?;
774 let node = self.handle.get_node_or_root(&node_id)?;
775
776 Ok(self.handle.document.render_element(
777 cr,
778 node,
779 element_viewport,
780 &self.rendering_options(),
781 )?)
782 }
783
784 #[doc(hidden)]
785 #[cfg(feature = "capi")]
786 pub fn dpi(&self) -> Dpi {
787 self.dpi
788 }
789
790 /// Normalizes the svg's width/height properties with a 0-sized viewport
791 ///
792 /// This assumes that if one of the properties is in percentage units, then
793 /// its corresponding value will not be used. E.g. if width=100%, the caller
794 /// will ignore the resulting width value.
795 #[doc(hidden)]
796 pub fn width_height_to_user(&self, dpi: Dpi) -> (f64, f64) {
797 let dimensions = self.handle.document.get_intrinsic_dimensions();
798
799 let width = dimensions.width;
800 let height = dimensions.height;
801
802 let viewport = Viewport::new(dpi, 0.0, 0.0);
803 let root = self.handle.document.root();
804 let cascaded = CascadedValues::new_from_node(&root);
805 let values = cascaded.get();
806
807 let params = NormalizeParams::new(values, &viewport);
808
809 (width.to_user(¶ms), height.to_user(¶ms))
810 }
811
812 #[doc(hidden)]
813 #[cfg(feature = "capi")]
814 pub fn test_mode(self, is_testing: bool) -> Self {
815 CairoRenderer { is_testing, ..self }
816 }
817}