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, 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

            
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
    /// The rendering was interrupted via a [`gio::Cancellable`].
62
    ///
63
    /// See the documentation for [`CairoRenderer::with_cancellable`].
64
    Cancelled,
65
}
66

            
67
impl std::error::Error for RenderingError {}
68

            
69
impl From<cairo::Error> for RenderingError {
70
    fn from(e: cairo::Error) -> RenderingError {
71
        RenderingError::Rendering(format!("{e:?}"))
72
    }
73
}
74

            
75
impl From<InternalRenderingError> for RenderingError {
76
133
    fn from(e: InternalRenderingError) -> RenderingError {
77
133
        // These enums are mostly the same, except for cases that should definitely
78
133
        // not bubble up to the public API.  So, we just move each variant, and for the
79
133
        // others, we emit a catch-all value as a safeguard.  (We ought to panic in that case,
80
133
        // maybe.)
81
133
        match e {
82
            InternalRenderingError::Rendering(s) => RenderingError::Rendering(s),
83
76
            InternalRenderingError::LimitExceeded(l) => RenderingError::LimitExceeded(l),
84
            InternalRenderingError::InvalidTransform => {
85
                RenderingError::Rendering("invalid transform".to_string())
86
            }
87
            InternalRenderingError::CircularReference(c) => {
88
                RenderingError::Rendering(format!("circular reference in node {c}"))
89
            }
90
38
            InternalRenderingError::IdNotFound => RenderingError::IdNotFound,
91
            InternalRenderingError::InvalidId(s) => RenderingError::InvalidId(s),
92
            InternalRenderingError::OutOfMemory(s) => RenderingError::OutOfMemory(s),
93
19
            InternalRenderingError::Cancelled => RenderingError::Cancelled,
94
        }
95
133
    }
96
}
97

            
98
impl fmt::Display for RenderingError {
99
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100
        match *self {
101
            RenderingError::Rendering(ref s) => write!(f, "rendering error: {s}"),
102
            RenderingError::LimitExceeded(ref l) => write!(f, "{l}"),
103
            RenderingError::IdNotFound => write!(f, "element id not found"),
104
            RenderingError::InvalidId(ref s) => write!(f, "invalid id: {s:?}"),
105
            RenderingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
106
            RenderingError::Cancelled => write!(f, "rendering cancelled"),
107
        }
108
    }
109
}
110

            
111
/// Builder for loading an [`SvgHandle`].
112
///
113
/// This is the starting point for using librsvg.  This struct
114
/// implements a builder pattern for configuring an [`SvgHandle`]'s
115
/// options, and then loading the SVG data.  You can call the methods
116
/// of `Loader` in sequence to configure how SVG data should be
117
/// loaded, and finally use one of the loading functions to load an
118
/// [`SvgHandle`].
119
pub struct Loader {
120
    unlimited_size: bool,
121
    keep_image_data: bool,
122
    session: Session,
123
}
124

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

            
154
    /// Creates a `Loader` from a pre-created [`Session`].
155
    ///
156
    /// This is useful when a `Loader` must be created by the C API, which should already
157
    /// have created a session for logging.
158
    #[cfg(feature = "capi")]
159
    pub fn new_with_session(session: Session) -> Self {
160
        Self {
161
            unlimited_size: false,
162
            keep_image_data: false,
163
            session,
164
        }
165
    }
166

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

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

            
228
    /// Reads an SVG document from `path`.
229
    ///
230
    /// # Example:
231
    ///
232
    /// ```
233
    /// let svg_handle = rsvg::Loader::new()
234
    ///     .read_path("example.svg")
235
    ///     .unwrap();
236
    /// ```
237
2309
    pub fn read_path<P: AsRef<Path>>(self, path: P) -> Result<SvgHandle, LoadingError> {
238
2309
        let file = gio::File::for_path(path);
239
2309
        self.read_file(&file, None::<&Cancellable>)
240
2309
    }
241

            
242
    /// Reads an SVG document from a `gio::File`.
243
    ///
244
    /// The `cancellable` can be used to cancel loading from another thread.
245
    ///
246
    /// # Example:
247
    /// ```
248
    /// let svg_handle = rsvg::Loader::new()
249
    ///     .read_file(&gio::File::for_path("example.svg"), None::<&gio::Cancellable>)
250
    ///     .unwrap();
251
    /// ```
252
16530
    pub fn read_file<F: IsA<gio::File>, P: IsA<Cancellable>>(
253
16530
        self,
254
16530
        file: &F,
255
16530
        cancellable: Option<&P>,
256
16530
    ) -> Result<SvgHandle, LoadingError> {
257
16530
        let stream = file.read(cancellable)?;
258
16530
        self.read_stream(&stream, Some(file), cancellable)
259
16530
    }
260

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

            
294
18639
        let base_url = if let Some(base_file) = base_file {
295
16615
            Some(url_from_file(base_file)?)
296
        } else {
297
2024
            None
298
        };
299

            
300
18639
        let load_options = LoadOptions::new(UrlResolver::new(base_url))
301
18639
            .with_unlimited_size(self.unlimited_size)
302
18639
            .keep_image_data(self.keep_image_data);
303
18639

            
304
18639
        Ok(SvgHandle {
305
18639
            document: Document::load_from_stream(
306
18639
                self.session.clone(),
307
18639
                Arc::new(load_options),
308
18639
                stream.as_ref(),
309
18639
                cancellable.map(|c| c.as_ref()),
310
18639
            )?,
311
18332
            session: self.session,
312
        })
313
18639
    }
314
}
315

            
316
16967
fn url_from_file(file: &gio::File) -> Result<Url, LoadingError> {
317
16967
    Url::parse(&file.uri()).map_err(|_| LoadingError::BadUrl)
318
16967
}
319

            
320
/// Handle used to hold SVG data in memory.
321
///
322
/// You can create this from one of the `read` methods in
323
/// [`Loader`].
324
pub struct SvgHandle {
325
    session: Session,
326
    pub(crate) document: Document,
327
}
328

            
329
// Public API goes here
330
impl SvgHandle {
331
    /// Checks if the SVG has an element with the specified `id`.
332
    ///
333
    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
334
    /// a leading `#` character.
335
    ///
336
    /// The purpose of the `Err()` case in the return value is to indicate an
337
    /// incorrectly-formatted `id` argument.
338
95
    pub fn has_element_with_id(&self, id: &str) -> Result<bool, RenderingError> {
339
95
        let node_id = self.get_node_id(id)?;
340

            
341
38
        match self.lookup_node(&node_id) {
342
19
            Ok(_) => Ok(true),
343

            
344
19
            Err(InternalRenderingError::IdNotFound) => Ok(false),
345

            
346
            Err(e) => Err(e.into()),
347
        }
348
95
    }
349

            
350
    /// Sets a CSS stylesheet to use for an SVG document.
351
    ///
352
    /// During the CSS cascade, the specified stylesheet will be used
353
    /// with a "User" [origin].
354
    ///
355
    /// Note that `@import` rules will not be resolved, except for `data:` URLs.
356
    ///
357
    /// [origin]: https://drafts.csswg.org/css-cascade-3/#cascading-origins
358
57
    pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> {
359
57
        let stylesheet = Stylesheet::from_data(
360
57
            css,
361
57
            &UrlResolver::new(None),
362
57
            Origin::User,
363
57
            self.session.clone(),
364
57
        )?;
365
57
        self.document.cascade(&[stylesheet]);
366
57
        Ok(())
367
57
    }
368
}
369

            
370
// Private methods go here
371
impl SvgHandle {
372
1672
    fn get_node_id_or_root(&self, id: Option<&str>) -> Result<Option<NodeId>, RenderingError> {
373
1672
        match id {
374
133
            None => Ok(None),
375
1539
            Some(s) => Ok(Some(self.get_node_id(s)?)),
376
        }
377
1672
    }
378

            
379
1634
    fn get_node_id(&self, id: &str) -> Result<NodeId, RenderingError> {
380
1634
        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
1558
        match node_id {
391
1520
            NodeId::Internal(_) => Ok(node_id),
392
            NodeId::External(_, _) => {
393
38
                rsvg_log!(
394
38
                    self.session,
395
38
                    "the public API is not allowed to look up external references: {}",
396
38
                    node_id
397
38
                );
398

            
399
38
                Err(RenderingError::InvalidId(
400
38
                    "cannot lookup references to elements in external files".to_string(),
401
38
                ))
402
            }
403
        }
404
1634
    }
405

            
406
1615
    fn get_node_or_root(&self, node_id: &Option<NodeId>) -> Result<Node, InternalRenderingError> {
407
1615
        if let Some(ref node_id) = *node_id {
408
1482
            Ok(self.lookup_node(node_id)?)
409
        } else {
410
133
            Ok(self.document.root())
411
        }
412
1615
    }
413

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

            
435
/// Can render an `SvgHandle` to a Cairo context.
436
pub 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.
445
const DEFAULT_DPI_X: f64 = 96.0;
446
const 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`.
472
pub struct IntrinsicDimensions {
473
    /// Computed value of the `width` property of the `<svg>`.
474
    pub width: Length,
475

            
476
    /// Computed value of the `height` property of the `<svg>`.
477
    pub height: Length,
478

            
479
    /// `viewBox` attribute of the `<svg>`, if present.
480
    pub vbox: Option<cairo::Rectangle>,
481
}
482

            
483
/// Gets the user's preferred locale from the environment and
484
/// translates it to a `Locale` with `LanguageRange` fallbacks.
485
///
486
/// The `Locale::current()` call only contemplates a single language,
487
/// but glib is smarter, and `g_get_langauge_names()` can provide
488
/// fallbacks, for example, when LC_MESSAGES="en_US.UTF-8:de" (USA
489
/// English and German).  This function converts the output of
490
/// `g_get_language_names()` into a `Locale` with appropriate
491
/// fallbacks.
492
22401
fn locale_from_environment() -> Locale {
493
22401
    let mut locale = Locale::invariant();
494

            
495
124659
    for name in glib::language_names() {
496
102258
        let name = name.as_str();
497
102258
        if let Ok(range) = LanguageRange::from_unix(name) {
498
87894
            locale.add(&range);
499
87894
        }
500
    }
501

            
502
22401
    locale
503
22401
}
504

            
505
impl UserLanguage {
506
22458
    fn new(language: &Language, session: &Session) -> UserLanguage {
507
22458
        match *language {
508
22401
            Language::FromEnvironment => UserLanguage::LanguageTags(
509
22401
                LanguageTags::from_locale(&locale_from_environment())
510
22401
                    .map_err(|s| {
511
                        rsvg_log!(session, "could not convert locale to language tags: {}", s);
512
22401
                    })
513
22401
                    .unwrap_or_else(|_| LanguageTags::empty()),
514
22401
            ),
515

            
516
57
            Language::AcceptLanguage(ref a) => UserLanguage::AcceptLanguage(a.clone()),
517
        }
518
22458
    }
519
}
520

            
521
impl<'a> CairoRenderer<'a> {
522
    /// Creates a `CairoRenderer` for the specified `SvgHandle`.
523
    ///
524
    /// The default dots-per-inch (DPI) value is set to 96; you can change it
525
    /// with the [`with_dpi`] method.
526
    ///
527
    /// [`with_dpi`]: #method.with_dpi
528
20672
    pub fn new(handle: &'a SvgHandle) -> Self {
529
20672
        let session = &handle.session;
530
20672

            
531
20672
        CairoRenderer {
532
20672
            handle,
533
20672
            dpi: Dpi::new(DEFAULT_DPI_X, DEFAULT_DPI_Y),
534
20672
            user_language: UserLanguage::new(&Language::FromEnvironment, session),
535
20672
            cancellable: None,
536
20672
            is_testing: false,
537
20672
        }
538
20672
    }
539

            
540
    /// Configures the dots-per-inch for resolving physical lengths.
541
    ///
542
    /// If an SVG document has physical units like `5cm`, they must be resolved
543
    /// to pixel-based values.  The default pixel density is 96 DPI in
544
    /// both dimensions.
545
15523
    pub fn with_dpi(self, dpi_x: f64, dpi_y: f64) -> Self {
546
15523
        assert!(dpi_x > 0.0);
547
15523
        assert!(dpi_y > 0.0);
548

            
549
15523
        CairoRenderer {
550
15523
            dpi: Dpi::new(dpi_x, dpi_y),
551
15523
            ..self
552
15523
        }
553
15523
    }
554

            
555
    /// Configures the set of languages used for rendering.
556
    ///
557
    /// SVG documents can use the `<switch>` element, whose children have a
558
    /// `systemLanguage` attribute; only the first child which has a `systemLanguage` that
559
    /// matches the preferred languages will be rendered.
560
    ///
561
    /// This function sets the preferred languages.  The default is
562
    /// `Language::FromEnvironment`, which means that the set of preferred languages will
563
    /// be obtained from the program's environment.  To set an explicit list of languages,
564
    /// you can use `Language::AcceptLanguage` instead.
565
1786
    pub fn with_language(self, language: &Language) -> Self {
566
1786
        let user_language = UserLanguage::new(language, &self.handle.session);
567
1786

            
568
1786
        CairoRenderer {
569
1786
            user_language,
570
1786
            ..self
571
1786
        }
572
1786
    }
573

            
574
    /// Sets a cancellable to be able to interrupt rendering.
575
    ///
576
    /// The rendering functions like [`render_document`] will normally render the whole
577
    /// SVG document tree.  However, they can be interrupted if you set a `cancellable`
578
    /// object with this method.  To interrupt rendering, you can call
579
    /// [`gio::CancellableExt::cancel()`] from a different thread than where the rendering
580
    /// is happening.
581
    ///
582
    /// Since rendering happens as a side-effect on the Cairo context (`cr`) that is
583
    /// passed to the rendering functions, it may be that the `cr`'s target surface is in
584
    /// an undefined state if the rendering is cancelled.  The surface may have not yet
585
    /// been painted on, or it may contain a partially-rendered document.  For this
586
    /// reason, if your application does not want to leave the target surface in an
587
    /// inconsistent state, you may prefer to use a temporary surface for rendering, which
588
    /// can be discarded if your code cancels the rendering.
589
    ///
590
    /// [`render_document`]: #method.render_document
591
1
    pub fn with_cancellable<C: IsA<Cancellable>>(self, cancellable: &C) -> Self {
592
1
        CairoRenderer {
593
1
            cancellable: Some(cancellable.clone().into()),
594
1
            ..self
595
1
        }
596
1
    }
597

            
598
    /// Queries the `width`, `height`, and `viewBox` attributes in an SVG document.
599
    ///
600
    /// If you are calling this function to compute a scaling factor to render the SVG,
601
    /// consider simply using [`render_document`] instead; it will do the scaling
602
    /// computations automatically.
603
    ///
604
    /// See also [`intrinsic_size_in_pixels`], which does the conversion to pixels if
605
    /// possible.
606
    ///
607
    /// [`render_document`]: #method.render_document
608
    /// [`intrinsic_size_in_pixels`]: #method.intrinsic_size_in_pixels
609
17708
    pub fn intrinsic_dimensions(&self) -> IntrinsicDimensions {
610
17708
        let d = self.handle.document.get_intrinsic_dimensions();
611
17708

            
612
17708
        IntrinsicDimensions {
613
17708
            width: Into::into(d.width),
614
17708
            height: Into::into(d.height),
615
17708
            vbox: d.vbox.map(|v| cairo::Rectangle::from(*v)),
616
17708
        }
617
17708
    }
618

            
619
    /// Converts the SVG document's intrinsic dimensions to pixels, if possible.
620
    ///
621
    /// Returns `Some(width, height)` in pixel units if the SVG document has `width` and
622
    /// `height` attributes with physical dimensions (CSS pixels, cm, in, etc.) or
623
    /// font-based dimensions (em, ex).
624
    ///
625
    /// Note that the dimensions are floating-point numbers, so your application can know
626
    /// the exact size of an SVG document.  To get integer dimensions, you should use
627
    /// [`f64::ceil()`] to round up to the nearest integer (just using [`f64::round()`],
628
    /// may may chop off pixels with fractional coverage).
629
    ///
630
    /// If the SVG document has percentage-based `width` and `height` attributes, or if
631
    /// either of those attributes are not present, returns `None`.  Dimensions of that
632
    /// kind require more information to be resolved to pixels; for example, the calling
633
    /// application can use a viewport size to scale percentage-based dimensions.
634
2508
    pub fn intrinsic_size_in_pixels(&self) -> Option<(f64, f64)> {
635
2508
        let dim = self.intrinsic_dimensions();
636
2508
        let width = dim.width;
637
2508
        let height = dim.height;
638
2508

            
639
2508
        if width.unit == LengthUnit::Percent || height.unit == LengthUnit::Percent {
640
114
            return None;
641
2394
        }
642
2394

            
643
2394
        Some(self.width_height_to_user(self.dpi))
644
2508
    }
645

            
646
20273
    fn rendering_options(&self) -> RenderingOptions {
647
20273
        RenderingOptions {
648
20273
            dpi: self.dpi,
649
20273
            cancellable: self.cancellable.clone(),
650
20273
            user_language: self.user_language.clone(),
651
20273
            svg_nesting: SvgNesting::Standalone,
652
20273
            testing: self.is_testing,
653
20273
        }
654
20273
    }
655

            
656
    /// Renders the whole SVG document fitted to a viewport
657
    ///
658
    /// The `viewport` gives the position and size at which the whole SVG
659
    /// document will be rendered.
660
    ///
661
    /// The `cr` must be in a `cairo::Status::Success` state, or this function
662
    /// will not render anything, and instead will return
663
    /// `RenderingError::Cairo` with the `cr`'s current error state.
664
18696
    pub fn render_document(
665
18696
        &self,
666
18696
        cr: &cairo::Context,
667
18696
        viewport: &cairo::Rectangle,
668
18696
    ) -> Result<(), RenderingError> {
669
18696
        Ok(self
670
18696
            .handle
671
18696
            .document
672
18696
            .render_document(cr, viewport, &self.rendering_options())?)
673
18696
    }
674

            
675
    /// Computes the (ink_rect, logical_rect) of an SVG element, as if
676
    /// the SVG were rendered to a specific viewport.
677
    ///
678
    /// Element IDs should look like an URL fragment identifier; for
679
    /// example, pass `Some("#foo")` to get the geometry of the
680
    /// element that has an `id="foo"` attribute.
681
    ///
682
    /// The "ink rectangle" is the bounding box that would be painted
683
    /// for fully- stroked and filled elements.
684
    ///
685
    /// The "logical rectangle" just takes into account the unstroked
686
    /// paths and text outlines.
687
    ///
688
    /// Note that these bounds are not minimum bounds; for example,
689
    /// clipping paths are not taken into account.
690
    ///
691
    /// You can pass `None` for the `id` if you want to measure all
692
    /// the elements in the SVG, i.e. to measure everything from the
693
    /// root element.
694
    ///
695
    /// This operation is not constant-time, as it involves going through all
696
    /// the child elements.
697
    ///
698
    /// FIXME: example
699
1444
    pub fn geometry_for_layer(
700
1444
        &self,
701
1444
        id: Option<&str>,
702
1444
        viewport: &cairo::Rectangle,
703
1444
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
704
1444
        let node_id = self.handle.get_node_id_or_root(id)?;
705
1387
        let node = self.handle.get_node_or_root(&node_id)?;
706

            
707
1368
        Ok(self.handle.document.get_geometry_for_layer(
708
1368
            node,
709
1368
            viewport,
710
1368
            &self.rendering_options(),
711
1368
        )?)
712
1444
    }
713

            
714
    /// Renders a single SVG element in the same place as for a whole SVG document
715
    ///
716
    /// This is equivalent to `render_document`, but renders only a single element and its
717
    /// children, as if they composed an individual layer in the SVG.  The element is
718
    /// rendered with the same transformation matrix as it has within the whole SVG
719
    /// document.  Applications can use this to re-render a single element and repaint it
720
    /// on top of a previously-rendered document, for example.
721
    ///
722
    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
723
    /// a leading `#` character.
724
    ///
725
    /// The `viewport` gives the position and size at which the whole SVG
726
    /// document would be rendered.  This function will effectively place the
727
    /// whole SVG within that viewport, but only render the element given by
728
    /// `id`.
729
    ///
730
    /// The `cr` must be in a `cairo::Status::Success` state, or this function
731
    /// will not render anything, and instead will return
732
    /// `RenderingError::Cairo` with the `cr`'s current error state.
733
19
    pub fn render_layer(
734
19
        &self,
735
19
        cr: &cairo::Context,
736
19
        id: Option<&str>,
737
19
        viewport: &cairo::Rectangle,
738
19
    ) -> Result<(), RenderingError> {
739
19
        let node_id = self.handle.get_node_id_or_root(id)?;
740
19
        let node = self.handle.get_node_or_root(&node_id)?;
741

            
742
19
        Ok(self
743
19
            .handle
744
19
            .document
745
19
            .render_layer(cr, node, viewport, &self.rendering_options())?)
746
19
    }
747

            
748
    /// Computes the (ink_rect, logical_rect) of a single SVG element
749
    ///
750
    /// While `geometry_for_layer` computes the geometry of an SVG element subtree with
751
    /// its transformation matrix, this other function will compute the element's geometry
752
    /// as if it were being rendered under an identity transformation by itself.  That is,
753
    /// the resulting geometry is as if the element got extracted by itself from the SVG.
754
    ///
755
    /// This function is the counterpart to `render_element`.
756
    ///
757
    /// Element IDs should look like an URL fragment identifier; for
758
    /// example, pass `Some("#foo")` to get the geometry of the
759
    /// element that has an `id="foo"` attribute.
760
    ///
761
    /// The "ink rectangle" is the bounding box that would be painted
762
    /// for fully- stroked and filled elements.
763
    ///
764
    /// The "logical rectangle" just takes into account the unstroked
765
    /// paths and text outlines.
766
    ///
767
    /// Note that these bounds are not minimum bounds; for example,
768
    /// clipping paths are not taken into account.
769
    ///
770
    /// You can pass `None` for the `id` if you want to measure all
771
    /// the elements in the SVG, i.e. to measure everything from the
772
    /// root element.
773
    ///
774
    /// This operation is not constant-time, as it involves going through all
775
    /// the child elements.
776
    ///
777
    /// FIXME: example
778
114
    pub fn geometry_for_element(
779
114
        &self,
780
114
        id: Option<&str>,
781
114
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
782
114
        let node_id = self.handle.get_node_id_or_root(id)?;
783
114
        let node = self.handle.get_node_or_root(&node_id)?;
784

            
785
95
        Ok(self
786
95
            .handle
787
95
            .document
788
95
            .get_geometry_for_element(node, &self.rendering_options())?)
789
114
    }
790

            
791
    /// Renders a single SVG element to a given viewport
792
    ///
793
    /// This function can be used to extract individual element subtrees and render them,
794
    /// scaled to a given `element_viewport`.  This is useful for applications which have
795
    /// reusable objects in an SVG and want to render them individually; for example, an
796
    /// SVG full of icons that are meant to be be rendered independently of each other.
797
    ///
798
    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
799
    /// a leading `#` character.
800
    ///
801
    /// The `element_viewport` gives the position and size at which the named element will
802
    /// be rendered.  FIXME: mention proportional scaling.
803
    ///
804
    /// The `cr` must be in a `cairo::Status::Success` state, or this function
805
    /// will not render anything, and instead will return
806
    /// `RenderingError::Cairo` with the `cr`'s current error state.
807
95
    pub fn render_element(
808
95
        &self,
809
95
        cr: &cairo::Context,
810
95
        id: Option<&str>,
811
95
        element_viewport: &cairo::Rectangle,
812
95
    ) -> Result<(), RenderingError> {
813
95
        let node_id = self.handle.get_node_id_or_root(id)?;
814
95
        let node = self.handle.get_node_or_root(&node_id)?;
815

            
816
95
        Ok(self.handle.document.render_element(
817
95
            cr,
818
95
            node,
819
95
            element_viewport,
820
95
            &self.rendering_options(),
821
95
        )?)
822
95
    }
823

            
824
    #[doc(hidden)]
825
    #[cfg(feature = "capi")]
826
38
    pub fn dpi(&self) -> Dpi {
827
38
        self.dpi
828
38
    }
829

            
830
    /// Normalizes the svg's width/height properties with a 0-sized viewport
831
    ///
832
    /// This assumes that if one of the properties is in percentage units, then
833
    /// its corresponding value will not be used.  E.g. if width=100%, the caller
834
    /// will ignore the resulting width value.
835
    #[doc(hidden)]
836
2413
    pub fn width_height_to_user(&self, dpi: Dpi) -> (f64, f64) {
837
2413
        let dimensions = self.handle.document.get_intrinsic_dimensions();
838
2413

            
839
2413
        let width = dimensions.width;
840
2413
        let height = dimensions.height;
841
2413

            
842
2413
        let viewport = Viewport::new(dpi, 0.0, 0.0);
843
2413
        let root = self.handle.document.root();
844
2413
        let cascaded = CascadedValues::new_from_node(&root);
845
2413
        let values = cascaded.get();
846
2413

            
847
2413
        let params = NormalizeParams::new(values, &viewport);
848
2413

            
849
2413
        (width.to_user(&params), height.to_user(&params))
850
2413
    }
851

            
852
    #[doc(hidden)]
853
    #[cfg(feature = "capi")]
854
15561
    pub fn test_mode(self, is_testing: bool) -> Self {
855
15561
        CairoRenderer { is_testing, ..self }
856
15561
    }
857
}