1
//! Public Rust API for librsvg.
2
//!
3
//! This gets re-exported from the toplevel `lib.rs`.
4

            
5
#![warn(missing_docs)]
6

            
7
use std::fmt;
8

            
9
// Here we only re-export stuff in the public API.
10
pub 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!
18
use crate::{
19
    accept_language::{LanguageTags, UserLanguage},
20
    css::{Origin, Stylesheet},
21
    document::{Document, LoadOptions, NodeId},
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

            
32
use url::Url;
33

            
34
use std::path::Path;
35
use std::sync::Arc;
36

            
37
use gio::prelude::*; // Re-exposes glib's prelude as well
38
use gio::Cancellable;
39

            
40
use locale_config::{LanguageRange, Locale};
41

            
42
/// Errors that can happen while rendering or measuring an SVG document.
43
#[non_exhaustive]
44
#[derive(Debug, Clone)]
45
pub 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

            
62
impl std::error::Error for RenderingError {}
63

            
64
impl From<cairo::Error> for RenderingError {
65
1
    fn from(e: cairo::Error) -> RenderingError {
66
1
        RenderingError::Rendering(format!("{e:?}"))
67
1
    }
68
}
69

            
70
impl From<InternalRenderingError> for RenderingError {
71
7
    fn from(e: InternalRenderingError) -> RenderingError {
72
        // These enums are mostly the same, except for cases that should definitely
73
        // not bubble up to the public API.  So, we just move each variant, and for the
74
        // others, we emit a catch-all value as a safeguard.  (We ought to panic in that case,
75
        // maybe.)
76
7
        match e {
77
            InternalRenderingError::Rendering(s) => RenderingError::Rendering(s),
78
1
            InternalRenderingError::LimitExceeded(l) => RenderingError::LimitExceeded(l),
79
            InternalRenderingError::InvalidTransform => {
80
                RenderingError::Rendering("invalid transform".to_string())
81
            }
82
6
            InternalRenderingError::IdNotFound => RenderingError::IdNotFound,
83
            InternalRenderingError::InvalidId(s) => RenderingError::InvalidId(s),
84
            InternalRenderingError::OutOfMemory(s) => RenderingError::OutOfMemory(s),
85
        }
86
7
    }
87
}
88

            
89
impl fmt::Display for RenderingError {
90
3
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91
3
        match *self {
92
1
            RenderingError::Rendering(ref s) => write!(f, "rendering error: {s}"),
93
            RenderingError::LimitExceeded(ref l) => write!(f, "{l}"),
94
2
            RenderingError::IdNotFound => write!(f, "element id not found"),
95
            RenderingError::InvalidId(ref s) => write!(f, "invalid id: {s:?}"),
96
            RenderingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
97
        }
98
3
    }
99
}
100

            
101
/// Builder for loading an [`SvgHandle`].
102
///
103
/// This is the starting point for using librsvg.  This struct
104
/// implements a builder pattern for configuring an [`SvgHandle`]'s
105
/// options, and then loading the SVG data.  You can call the methods
106
/// of `Loader` in sequence to configure how SVG data should be
107
/// loaded, and finally use one of the loading functions to load an
108
/// [`SvgHandle`].
109
pub struct Loader {
110
    unlimited_size: bool,
111
    keep_image_data: bool,
112
    session: Session,
113
}
114

            
115
impl Loader {
116
    /// Creates a `Loader` with the default flags.
117
    ///
118
    /// * [`unlimited_size`](#method.with_unlimited_size) defaults to `false`, as malicious
119
    /// SVG documents could cause the XML parser to consume very large amounts of memory.
120
    ///
121
    /// * [`keep_image_data`](#method.keep_image_data) defaults to
122
    /// `false`.  You may only need this if rendering to Cairo
123
    /// surfaces that support including image data in compressed
124
    /// formats, like PDF.
125
    ///
126
    /// # Example:
127
    ///
128
    /// ```
129
    /// use rsvg;
130
    ///
131
    /// let svg_handle = rsvg::Loader::new()
132
    ///     .read_path("example.svg")
133
    ///     .unwrap();
134
    /// ```
135
    #[allow(clippy::new_without_default)]
136
1044
    pub fn new() -> Self {
137
1044
        Self {
138
            unlimited_size: false,
139
            keep_image_data: false,
140
1044
            session: Session::default(),
141
        }
142
1044
    }
143

            
144
    /// Creates a `Loader` from a pre-created [`Session`].
145
    ///
146
    /// This is useful when a `Loader` must be created by the C API, which should already
147
    /// have created a session for logging.
148
    #[cfg(feature = "capi")]
149
49
    pub fn new_with_session(session: Session) -> Self {
150
49
        Self {
151
            unlimited_size: false,
152
            keep_image_data: false,
153
            session,
154
        }
155
49
    }
156

            
157
    /// Controls safety limits used in the XML parser.
158
    ///
159
    /// Internally, librsvg uses libxml2, which has set limits for things like the
160
    /// maximum length of XML element names, the size of accumulated buffers
161
    /// using during parsing of deeply-nested XML files, and the maximum size
162
    /// of embedded XML entities.
163
    ///
164
    /// Set this to `true` only if loading a trusted SVG fails due to size limits.
165
    ///
166
    /// # Example:
167
    /// ```
168
    /// use rsvg;
169
    ///
170
    /// let svg_handle = rsvg::Loader::new()
171
    ///     .with_unlimited_size(true)
172
    ///     .read_path("example.svg")    // presumably a trusted huge file
173
    ///     .unwrap();
174
    /// ```
175
139
    pub fn with_unlimited_size(mut self, unlimited: bool) -> Self {
176
139
        self.unlimited_size = unlimited;
177
139
        self
178
139
    }
179

            
180
    /// Controls embedding of compressed image data into the renderer.
181
    ///
182
    /// Normally, Cairo expects one to pass it uncompressed (decoded)
183
    /// images as surfaces.  However, when using a PDF rendering
184
    /// context to render SVG documents that reference raster images
185
    /// (e.g. those which include a bitmap as part of the SVG image),
186
    /// it may be more efficient to embed the original, compressed raster
187
    /// images into the PDF.
188
    ///
189
    /// Set this to `true` if you are using a Cairo PDF context, or any other type
190
    /// of context which allows embedding compressed images.
191
    ///
192
    /// # Example:
193
    ///
194
    /// ```
195
    /// # use std::env;
196
    /// let svg_handle = rsvg::Loader::new()
197
    ///     .keep_image_data(true)
198
    ///     .read_path("example.svg")
199
    ///     .unwrap();
200
    ///
201
    /// let mut output = env::temp_dir();
202
    /// output.push("output.pdf");
203
    /// let surface = cairo::PdfSurface::new(640.0, 480.0, output)?;
204
    /// let cr = cairo::Context::new(&surface).expect("Failed to create a cairo context");
205
    ///
206
    /// let renderer = rsvg::CairoRenderer::new(&svg_handle);
207
    /// renderer.render_document(
208
    ///     &cr,
209
    ///     &cairo::Rectangle::new(0.0, 0.0, 640.0, 480.0),
210
    /// )?;
211
    /// # Ok::<(), rsvg::RenderingError>(())
212
    /// ```
213
139
    pub fn keep_image_data(mut self, keep: bool) -> Self {
214
139
        self.keep_image_data = keep;
215
139
        self
216
139
    }
217

            
218
    /// Reads an SVG document from `path`.
219
    ///
220
    /// # Example:
221
    ///
222
    /// ```
223
    /// let svg_handle = rsvg::Loader::new()
224
    ///     .read_path("example.svg")
225
    ///     .unwrap();
226
    /// ```
227
860
    pub fn read_path<P: AsRef<Path>>(self, path: P) -> Result<SvgHandle, LoadingError> {
228
860
        let file = gio::File::for_path(path);
229
860
        self.read_file(&file, None::<&Cancellable>)
230
860
    }
231

            
232
    /// Reads an SVG document from a `gio::File`.
233
    ///
234
    /// The `cancellable` can be used to cancel loading from another thread.
235
    ///
236
    /// # Example:
237
    /// ```
238
    /// let svg_handle = rsvg::Loader::new()
239
    ///     .read_file(&gio::File::for_path("example.svg"), None::<&gio::Cancellable>)
240
    ///     .unwrap();
241
    /// ```
242
862
    pub fn read_file<F: IsA<gio::File>, P: IsA<Cancellable>>(
243
        self,
244
        file: &F,
245
        cancellable: Option<&P>,
246
    ) -> Result<SvgHandle, LoadingError> {
247
862
        let stream = file.read(cancellable)?;
248
864
        self.read_stream(&stream, Some(file), cancellable)
249
864
    }
250

            
251
    /// Reads an SVG stream from a `gio::InputStream`.
252
    ///
253
    /// The `base_file`, if it is not `None`, is used to extract the
254
    /// [base URL][crate#the-base-file-and-resolving-references-to-external-files] for this stream.
255
    ///
256
    /// Reading an SVG document may involve resolving relative URLs if the
257
    /// SVG references things like raster images, or other SVG files.
258
    /// In this case, pass the `base_file` that correspondds to the
259
    /// URL where this SVG got loaded from.
260
    ///
261
    /// The `cancellable` can be used to cancel loading from another thread.
262
    ///
263
    /// # Example
264
    ///
265
    /// ```
266
    /// use gio::prelude::*;
267
    ///
268
    /// let file = gio::File::for_path("example.svg");
269
    ///
270
    /// let stream = file.read(None::<&gio::Cancellable>).unwrap();
271
    ///
272
    /// let svg_handle = rsvg::Loader::new()
273
    ///     .read_stream(&stream, Some(&file), None::<&gio::Cancellable>)
274
    ///     .unwrap();
275
    /// ```
276
1114
    pub fn read_stream<S: IsA<gio::InputStream>, F: IsA<gio::File>, P: IsA<Cancellable>>(
277
        self,
278
        stream: &S,
279
        base_file: Option<&F>,
280
        cancellable: Option<&P>,
281
    ) -> Result<SvgHandle, LoadingError> {
282
2025
        let base_file = base_file.map(|f| f.as_ref());
283

            
284
2021
        let base_url = if let Some(base_file) = base_file {
285
916
            Some(url_from_file(base_file)?)
286
        } else {
287
184
            None
288
        };
289

            
290
1105
        let load_options = LoadOptions::new(UrlResolver::new(base_url))
291
1104
            .with_unlimited_size(self.unlimited_size)
292
1104
            .keep_image_data(self.keep_image_data);
293

            
294
1086
        Ok(SvgHandle {
295
1100
            document: Document::load_from_stream(
296
1108
                self.session.clone(),
297
1108
                Arc::new(load_options),
298
1108
                stream.as_ref(),
299
1102
                cancellable.map(|c| c.as_ref()),
300
1122
            )?,
301
1086
            session: self.session,
302
        })
303
1104
    }
304
}
305

            
306
915
fn url_from_file(file: &gio::File) -> Result<Url, LoadingError> {
307
915
    Url::parse(&file.uri()).map_err(|_| LoadingError::BadUrl)
308
915
}
309

            
310
/// Handle used to hold SVG data in memory.
311
///
312
/// You can create this from one of the `read` methods in
313
/// [`Loader`].
314
pub struct SvgHandle {
315
    session: Session,
316
    pub(crate) document: Document,
317
}
318

            
319
// Public API goes here
320
impl SvgHandle {
321
    /// Checks if the SVG has an element with the specified `id`.
322
    ///
323
    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
324
    /// a leading `#` character.
325
    ///
326
    /// The purpose of the `Err()` case in the return value is to indicate an
327
    /// incorrectly-formatted `id` argument.
328
11
    pub fn has_element_with_id(&self, id: &str) -> Result<bool, RenderingError> {
329
11
        let node_id = self.get_node_id(id)?;
330

            
331
8
        match self.lookup_node(&node_id) {
332
6
            Ok(_) => Ok(true),
333

            
334
2
            Err(InternalRenderingError::IdNotFound) => Ok(false),
335

            
336
            Err(e) => Err(e.into()),
337
        }
338
11
    }
339

            
340
    /// Sets a CSS stylesheet to use for an SVG document.
341
    ///
342
    /// During the CSS cascade, the specified stylesheet will be used
343
    /// with a "User" [origin].
344
    ///
345
    /// Note that `@import` rules will not be resolved, except for `data:` URLs.
346
    ///
347
    /// [origin]: https://drafts.csswg.org/css-cascade-3/#cascading-origins
348
4
    pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> {
349
4
        let stylesheet = Stylesheet::from_data(
350
            css,
351
4
            &UrlResolver::new(None),
352
4
            Origin::User,
353
4
            self.session.clone(),
354
4
        )?;
355
4
        self.document.cascade(&[stylesheet], &self.session);
356
4
        Ok(())
357
4
    }
358
}
359

            
360
// Private methods go here
361
impl SvgHandle {
362
116
    fn get_node_id_or_root(&self, id: Option<&str>) -> Result<Option<NodeId>, RenderingError> {
363
116
        match id {
364
15
            None => Ok(None),
365
101
            Some(s) => Ok(Some(self.get_node_id(s)?)),
366
        }
367
116
    }
368

            
369
112
    fn get_node_id(&self, id: &str) -> Result<NodeId, RenderingError> {
370
116
        let node_id = NodeId::parse(id).map_err(|_| RenderingError::InvalidId(id.to_string()))?;
371

            
372
        // The public APIs to get geometries of individual elements, or to render
373
        // them, should only allow referencing elements within the main handle's
374
        // SVG file; that is, only plain "#foo" fragment IDs are allowed here.
375
        // Otherwise, a calling program could request "another-file#foo" and cause
376
        // another-file to be loaded, even if it is not part of the set of
377
        // resources that the main SVG actually references.  In the future we may
378
        // relax this requirement to allow lookups within that set, but not to
379
        // other random files.
380
108
        match node_id {
381
105
            NodeId::Internal(_) => Ok(node_id),
382
            NodeId::External(_, _) => {
383
3
                rsvg_log!(
384
3
                    self.session,
385
                    "the public API is not allowed to look up external references: {}",
386
                    node_id
387
                );
388

            
389
3
                Err(RenderingError::InvalidId(
390
3
                    "cannot lookup references to elements in external files".to_string(),
391
                ))
392
3
            }
393
        }
394
112
    }
395

            
396
112
    fn get_node_or_root(&self, node_id: &Option<NodeId>) -> Result<Node, InternalRenderingError> {
397
112
        if let Some(ref node_id) = *node_id {
398
97
            Ok(self.lookup_node(node_id)?)
399
        } else {
400
15
            Ok(self.document.root())
401
        }
402
112
    }
403

            
404
105
    fn lookup_node(&self, node_id: &NodeId) -> Result<Node, InternalRenderingError> {
405
        // The public APIs to get geometries of individual elements, or to render
406
        // them, should only allow referencing elements within the main handle's
407
        // SVG file; that is, only plain "#foo" fragment IDs are allowed here.
408
        // Otherwise, a calling program could request "another-file#foo" and cause
409
        // another-file to be loaded, even if it is not part of the set of
410
        // resources that the main SVG actually references.  In the future we may
411
        // relax this requirement to allow lookups within that set, but not to
412
        // other random files.
413
105
        match node_id {
414
315
            NodeId::Internal(id) => self
415
                .document
416
105
                .lookup_internal_node(id)
417
105
                .ok_or(InternalRenderingError::IdNotFound),
418
            NodeId::External(_, _) => {
419
                unreachable!("caller should already have validated internal node IDs only")
420
            }
421
        }
422
105
    }
423
}
424

            
425
/// Can render an `SvgHandle` to a Cairo context.
426
pub struct CairoRenderer<'a> {
427
    pub(crate) handle: &'a SvgHandle,
428
    pub(crate) dpi: Dpi,
429
    user_language: UserLanguage,
430
    is_testing: bool,
431
}
432

            
433
// Note that these are different than the C API's default, which is 90.
434
const DEFAULT_DPI_X: f64 = 96.0;
435
const DEFAULT_DPI_Y: f64 = 96.0;
436

            
437
60
#[derive(Debug, Copy, Clone, PartialEq)]
438
/// Contains the computed values of the `<svg>` element's `width`, `height`, and `viewBox`.
439
///
440
/// An SVG document has a toplevel `<svg>` element, with optional attributes `width`,
441
/// `height`, and `viewBox`.  This structure contains the values for those attributes; you
442
/// can obtain the struct from [`CairoRenderer::intrinsic_dimensions`].
443
///
444
/// Since librsvg 2.54.0, there is support for [geometry
445
/// properties](https://www.w3.org/TR/SVG2/geometry.html) from SVG2.  This means that
446
/// `width` and `height` are no longer attributes; they are instead CSS properties that
447
/// default to `auto`.  The computed value for `auto` is `100%`, so for a `<svg>` that
448
/// does not have these attributes/properties, the `width`/`height` fields will be
449
/// returned as a [`Length`] of 100%.
450
///
451
/// As an example, the following SVG element has a `width` of 100 pixels
452
/// and a `height` of 400 pixels, but no `viewBox`.
453
///
454
/// ```xml
455
/// <svg xmlns="http://www.w3.org/2000/svg" width="100" height="400">
456
/// ```
457
///
458
/// In this case, the length fields will be set to the corresponding
459
/// values with [`LengthUnit::Px`] units, and the `vbox` field will be
460
/// set to to `None`.
461
pub struct IntrinsicDimensions {
462
    /// Computed value of the `width` property of the `<svg>`.
463
30
    pub width: Length,
464

            
465
    /// Computed value of the `height` property of the `<svg>`.
466
31
    pub height: Length,
467

            
468
    /// `viewBox` attribute of the `<svg>`, if present.
469
32
    pub vbox: Option<cairo::Rectangle>,
470
}
471

            
472
/// Gets the user's preferred locale from the environment and
473
/// translates it to a `Locale` with `LanguageRange` fallbacks.
474
///
475
/// The `Locale::current()` call only contemplates a single language,
476
/// but glib is smarter, and `g_get_langauge_names()` can provide
477
/// fallbacks, for example, when LC_MESSAGES="en_US.UTF-8:de" (USA
478
/// English and German).  This function converts the output of
479
/// `g_get_language_names()` into a `Locale` with appropriate
480
/// fallbacks.
481
1229
fn locale_from_environment() -> Locale {
482
1229
    let mut locale = Locale::invariant();
483

            
484
1229
    for name in glib::language_names() {
485
5426
        let name = name.as_str();
486
5429
        if let Ok(range) = LanguageRange::from_unix(name) {
487
4686
            locale.add(&range);
488
4686
        }
489
5418
    }
490

            
491
9621
    locale
492
9621
}
493

            
494
impl UserLanguage {
495
1234
    fn new(language: &Language, session: &Session) -> UserLanguage {
496
1234
        match *language {
497
1227
            Language::FromEnvironment => UserLanguage::LanguageTags(
498
1231
                LanguageTags::from_locale(&locale_from_environment())
499
1229
                    .map_err(|s| {
500
                        rsvg_log!(session, "could not convert locale to language tags: {}", s);
501
                    })
502
                    .unwrap_or_else(|_| LanguageTags::empty()),
503
1227
            ),
504

            
505
3
            Language::AcceptLanguage(ref a) => UserLanguage::AcceptLanguage(a.clone()),
506
        }
507
1230
    }
508
}
509

            
510
impl<'a> CairoRenderer<'a> {
511
    /// Creates a `CairoRenderer` for the specified `SvgHandle`.
512
    ///
513
    /// The default dots-per-inch (DPI) value is set to 96; you can change it
514
    /// with the [`with_dpi`] method.
515
    ///
516
    /// [`with_dpi`]: #method.with_dpi
517
1137
    pub fn new(handle: &'a SvgHandle) -> Self {
518
1137
        let session = &handle.session;
519

            
520
1137
        CairoRenderer {
521
            handle,
522
1137
            dpi: Dpi::new(DEFAULT_DPI_X, DEFAULT_DPI_Y),
523
1137
            user_language: UserLanguage::new(&Language::FromEnvironment, session),
524
            is_testing: false,
525
        }
526
1137
    }
527

            
528
    /// Configures the dots-per-inch for resolving physical lengths.
529
    ///
530
    /// If an SVG document has physical units like `5cm`, they must be resolved
531
    /// to pixel-based values.  The default pixel density is 96 DPI in
532
    /// both dimensions.
533
878
    pub fn with_dpi(self, dpi_x: f64, dpi_y: f64) -> Self {
534
878
        assert!(dpi_x > 0.0);
535
878
        assert!(dpi_y > 0.0);
536

            
537
878
        CairoRenderer {
538
878
            dpi: Dpi::new(dpi_x, dpi_y),
539
            ..self
540
        }
541
878
    }
542

            
543
    /// Configures the set of languages used for rendering.
544
    ///
545
    /// SVG documents can use the `<switch>` element, whose children have a
546
    /// `systemLanguage` attribute; only the first child which has a `systemLanguage` that
547
    /// matches the preferred languages will be rendered.
548
    ///
549
    /// This function sets the preferred languages.  The default is
550
    /// `Language::FromEnvironment`, which means that the set of preferred languages will
551
    /// be obtained from the program's environment.  To set an explicit list of languages,
552
    /// you can use `Language::AcceptLanguage` instead.
553
94
    pub fn with_language(self, language: &Language) -> Self {
554
94
        let user_language = UserLanguage::new(language, &self.handle.session);
555

            
556
94
        CairoRenderer {
557
            user_language,
558
            ..self
559
        }
560
94
    }
561

            
562
    /// Queries the `width`, `height`, and `viewBox` attributes in an SVG document.
563
    ///
564
    /// If you are calling this function to compute a scaling factor to render the SVG,
565
    /// consider simply using [`render_document`] instead; it will do the scaling
566
    /// computations automatically.
567
    ///
568
    /// See also [`intrinsic_size_in_pixels`], which does the conversion to pixels if
569
    /// possible.
570
    ///
571
    /// [`render_document`]: #method.render_document
572
    /// [`intrinsic_size_in_pixels`]: #method.intrinsic_size_in_pixels
573
974
    pub fn intrinsic_dimensions(&self) -> IntrinsicDimensions {
574
974
        let d = self.handle.document.get_intrinsic_dimensions();
575

            
576
974
        IntrinsicDimensions {
577
974
            width: Into::into(d.width),
578
974
            height: Into::into(d.height),
579
1369
            vbox: d.vbox.map(|v| cairo::Rectangle::from(*v)),
580
        }
581
974
    }
582

            
583
    /// Converts the SVG document's intrinsic dimensions to pixels, if possible.
584
    ///
585
    /// Returns `Some(width, height)` in pixel units if the SVG document has `width` and
586
    /// `height` attributes with physical dimensions (CSS pixels, cm, in, etc.) or
587
    /// font-based dimensions (em, ex).
588
    ///
589
    /// Note that the dimensions are floating-point numbers, so your application can know
590
    /// the exact size of an SVG document.  To get integer dimensions, you should use
591
    /// [`f64::ceil()`] to round up to the nearest integer (just using [`f64::round()`],
592
    /// may may chop off pixels with fractional coverage).
593
    ///
594
    /// If the SVG document has percentage-based `width` and `height` attributes, or if
595
    /// either of those attributes are not present, returns `None`.  Dimensions of that
596
    /// kind require more information to be resolved to pixels; for example, the calling
597
    /// application can use a viewport size to scale percentage-based dimensions.
598
170
    pub fn intrinsic_size_in_pixels(&self) -> Option<(f64, f64)> {
599
170
        let dim = self.intrinsic_dimensions();
600
170
        let width = dim.width;
601
170
        let height = dim.height;
602

            
603
170
        if width.unit == LengthUnit::Percent || height.unit == LengthUnit::Percent {
604
17
            return None;
605
        }
606

            
607
153
        Some(self.width_height_to_user(self.dpi))
608
170
    }
609

            
610
    /// Renders the whole SVG document fitted to a viewport
611
    ///
612
    /// The `viewport` gives the position and size at which the whole SVG
613
    /// document will be rendered.
614
    ///
615
    /// The `cr` must be in a `cairo::Status::Success` state, or this function
616
    /// will not render anything, and instead will return
617
    /// `RenderingError::Cairo` with the `cr`'s current error state.
618
979
    pub fn render_document(
619
        &self,
620
        cr: &cairo::Context,
621
        viewport: &cairo::Rectangle,
622
    ) -> Result<(), RenderingError> {
623
1958
        Ok(self.handle.document.render_document(
624
979
            &self.handle.session,
625
            cr,
626
            viewport,
627
            &self.user_language,
628
979
            self.dpi,
629
979
            SvgNesting::Standalone,
630
979
            self.is_testing,
631
1
        )?)
632
979
    }
633

            
634
    /// Computes the (ink_rect, logical_rect) of an SVG element, as if
635
    /// the SVG were rendered to a specific viewport.
636
    ///
637
    /// Element IDs should look like an URL fragment identifier; for
638
    /// example, pass `Some("#foo")` to get the geometry of the
639
    /// element that has an `id="foo"` attribute.
640
    ///
641
    /// The "ink rectangle" is the bounding box that would be painted
642
    /// for fully- stroked and filled elements.
643
    ///
644
    /// The "logical rectangle" just takes into account the unstroked
645
    /// paths and text outlines.
646
    ///
647
    /// Note that these bounds are not minimum bounds; for example,
648
    /// clipping paths are not taken into account.
649
    ///
650
    /// You can pass `None` for the `id` if you want to measure all
651
    /// the elements in the SVG, i.e. to measure everything from the
652
    /// root element.
653
    ///
654
    /// This operation is not constant-time, as it involves going through all
655
    /// the child elements.
656
    ///
657
    /// FIXME: example
658
92
    pub fn geometry_for_layer(
659
        &self,
660
        id: Option<&str>,
661
        viewport: &cairo::Rectangle,
662
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
663
92
        let node_id = self.handle.get_node_id_or_root(id)?;
664
88
        let node = self.handle.get_node_or_root(&node_id)?;
665

            
666
168
        Ok(self.handle.document.get_geometry_for_layer(
667
84
            &self.handle.session,
668
84
            node,
669
            viewport,
670
            &self.user_language,
671
84
            self.dpi,
672
84
            self.is_testing,
673
        )?)
674
92
    }
675

            
676
    /// Renders a single SVG element in the same place as for a whole SVG document
677
    ///
678
    /// This is equivalent to `render_document`, but renders only a single element and its
679
    /// children, as if they composed an individual layer in the SVG.  The element is
680
    /// rendered with the same transformation matrix as it has within the whole SVG
681
    /// document.  Applications can use this to re-render a single element and repaint it
682
    /// on top of a previously-rendered document, for example.
683
    ///
684
    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
685
    /// a leading `#` character.
686
    ///
687
    /// The `viewport` gives the position and size at which the whole SVG
688
    /// document would be rendered.  This function will effectively place the
689
    /// whole SVG within that viewport, but only render the element given by
690
    /// `id`.
691
    ///
692
    /// The `cr` must be in a `cairo::Status::Success` state, or this function
693
    /// will not render anything, and instead will return
694
    /// `RenderingError::Cairo` with the `cr`'s current error state.
695
10
    pub fn render_layer(
696
        &self,
697
        cr: &cairo::Context,
698
        id: Option<&str>,
699
        viewport: &cairo::Rectangle,
700
    ) -> Result<(), RenderingError> {
701
10
        let node_id = self.handle.get_node_id_or_root(id)?;
702
10
        let node = self.handle.get_node_or_root(&node_id)?;
703

            
704
30
        Ok(self.handle.document.render_layer(
705
10
            &self.handle.session,
706
            cr,
707
10
            node,
708
            viewport,
709
            &self.user_language,
710
10
            self.dpi,
711
10
            SvgNesting::Standalone,
712
10
            self.is_testing,
713
        )?)
714
10
    }
715

            
716
    /// Computes the (ink_rect, logical_rect) of a single SVG element
717
    ///
718
    /// While `geometry_for_layer` computes the geometry of an SVG element subtree with
719
    /// its transformation matrix, this other function will compute the element's geometry
720
    /// as if it were being rendered under an identity transformation by itself.  That is,
721
    /// the resulting geometry is as if the element got extracted by itself from the SVG.
722
    ///
723
    /// This function is the counterpart to `render_element`.
724
    ///
725
    /// Element IDs should look like an URL fragment identifier; for
726
    /// example, pass `Some("#foo")` to get the geometry of the
727
    /// element that has an `id="foo"` attribute.
728
    ///
729
    /// The "ink rectangle" is the bounding box that would be painted
730
    /// for fully- stroked and filled elements.
731
    ///
732
    /// The "logical rectangle" just takes into account the unstroked
733
    /// paths and text outlines.
734
    ///
735
    /// Note that these bounds are not minimum bounds; for example,
736
    /// clipping paths are not taken into account.
737
    ///
738
    /// You can pass `None` for the `id` if you want to measure all
739
    /// the elements in the SVG, i.e. to measure everything from the
740
    /// root element.
741
    ///
742
    /// This operation is not constant-time, as it involves going through all
743
    /// the child elements.
744
    ///
745
    /// FIXME: example
746
8
    pub fn geometry_for_element(
747
        &self,
748
        id: Option<&str>,
749
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
750
8
        let node_id = self.handle.get_node_id_or_root(id)?;
751
8
        let node = self.handle.get_node_or_root(&node_id)?;
752

            
753
12
        Ok(self.handle.document.get_geometry_for_element(
754
6
            &self.handle.session,
755
6
            node,
756
            &self.user_language,
757
6
            self.dpi,
758
6
            self.is_testing,
759
        )?)
760
8
    }
761

            
762
    /// Renders a single SVG element to a given viewport
763
    ///
764
    /// This function can be used to extract individual element subtrees and render them,
765
    /// scaled to a given `element_viewport`.  This is useful for applications which have
766
    /// reusable objects in an SVG and want to render them individually; for example, an
767
    /// SVG full of icons that are meant to be be rendered independently of each other.
768
    ///
769
    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
770
    /// a leading `#` character.
771
    ///
772
    /// The `element_viewport` gives the position and size at which the named element will
773
    /// be rendered.  FIXME: mention proportional scaling.
774
    ///
775
    /// The `cr` must be in a `cairo::Status::Success` state, or this function
776
    /// will not render anything, and instead will return
777
    /// `RenderingError::Cairo` with the `cr`'s current error state.
778
6
    pub fn render_element(
779
        &self,
780
        cr: &cairo::Context,
781
        id: Option<&str>,
782
        element_viewport: &cairo::Rectangle,
783
    ) -> Result<(), RenderingError> {
784
6
        let node_id = self.handle.get_node_id_or_root(id)?;
785
6
        let node = self.handle.get_node_or_root(&node_id)?;
786

            
787
18
        Ok(self.handle.document.render_element(
788
6
            &self.handle.session,
789
            cr,
790
6
            node,
791
            element_viewport,
792
            &self.user_language,
793
6
            self.dpi,
794
6
            self.is_testing,
795
        )?)
796
6
    }
797

            
798
    #[doc(hidden)]
799
    #[cfg(feature = "capi")]
800
13
    pub fn dpi(&self) -> Dpi {
801
13
        self.dpi
802
13
    }
803

            
804
    /// Normalizes the svg's width/height properties with a 0-sized viewport
805
    ///
806
    /// This assumes that if one of the properties is in percentage units, then
807
    /// its corresponding value will not be used.  E.g. if width=100%, the caller
808
    /// will ignore the resulting width value.
809
    #[doc(hidden)]
810
162
    pub fn width_height_to_user(&self, dpi: Dpi) -> (f64, f64) {
811
162
        let dimensions = self.handle.document.get_intrinsic_dimensions();
812

            
813
162
        let width = dimensions.width;
814
162
        let height = dimensions.height;
815

            
816
162
        let view_params = Viewport::new(dpi, 0.0, 0.0);
817
162
        let root = self.handle.document.root();
818
162
        let cascaded = CascadedValues::new_from_node(&root);
819
162
        let values = cascaded.get();
820

            
821
162
        let params = NormalizeParams::new(values, &view_params);
822

            
823
162
        (width.to_user(&params), height.to_user(&params))
824
162
    }
825

            
826
    #[doc(hidden)]
827
    #[cfg(feature = "capi")]
828
874
    pub fn test_mode(self, is_testing: bool) -> Self {
829
874
        CairoRenderer { is_testing, ..self }
830
874
    }
831
}