1
//! Main API for `RsvgHandle`.
2
//!
3
//! The C API of librsvg revolves around an `RsvgHandle` GObject class, which is
4
//! implemented as follows:
5
//!
6
//! * [`RsvgHandle`] and [`RsvgHandleClass`] are derivatives of `GObject` and
7
//! `GObjectClass`.  These are coded explicitly, instead of using
8
//! [`glib::subclass::prelude::InstanceStruct<T>`] and
9
//! [`glib::subclass::prelude::ClassStruct<T>`], as the structs need need to be kept
10
//! ABI-compatible with the traditional C API/ABI.
11
//!
12
//! * The actual data for a handle (e.g. the `RsvgHandle`'s private data, in GObject
13
//! parlance) is in [`CHandle`].
14
//!
15
//! * Public C ABI functions are the `#[no_mangle]` functions with an `rsvg_` prefix.
16
//!
17
//! The C API is implemented in terms of the Rust API in `librsvg_crate`.  In effect,
18
//! [`RsvgHandle`] is a rather convoluted builder or adapter pattern that translates all the
19
//! historical idiosyncrasies of the C API into the simple Rust API.
20

            
21
use std::cell::{Cell, Ref, RefCell, RefMut};
22
use std::ffi::{CStr, CString, OsStr};
23
use std::fmt;
24
use std::path::PathBuf;
25
use std::ptr;
26
use std::slice;
27
use std::str;
28
use std::{f64, i32};
29

            
30
#[cfg(feature = "pixbuf")]
31
use gdk_pixbuf::Pixbuf;
32

            
33
use gio::prelude::*;
34
use glib::error::ErrorDomain;
35
use url::Url;
36

            
37
use glib::subclass::prelude::*;
38
use glib::translate::*;
39
use glib::types::instance_of;
40
use glib::Bytes;
41
use glib::{ffi::gpointer, gobject_ffi};
42

            
43
use rsvg::c_api_only::{rsvg_log, Session};
44
use rsvg::{CairoRenderer, IntrinsicDimensions, Length, Loader, LoadingError, SvgHandle};
45

            
46
use super::dpi::Dpi;
47
use super::messages::{rsvg_g_critical, rsvg_g_warning};
48

            
49
#[cfg(feature = "pixbuf")]
50
use {
51
    super::pixbuf_utils::{empty_pixbuf, pixbuf_from_surface},
52
    rsvg::c_api_only::{SharedImageSurface, SurfaceType},
53
};
54

            
55
use super::sizing::LegacySize;
56

            
57
// The C API exports global variables that contain the library's version number;
58
// those get autogenerated from `build.rs` and placed in this `version.rs` file.
59
include!(concat!(env!("OUT_DIR"), "/version.rs"));
60

            
61
// This is basically the same as api::RenderingError but with extra cases for
62
// the peculiarities of the C API.
63
enum RenderingError {
64
    RenderingError(rsvg::RenderingError),
65

            
66
    // The RsvgHandle is created, but hasn't been loaded yet.
67
    HandleIsNotLoaded,
68
}
69

            
70
impl<T: Into<rsvg::RenderingError>> From<T> for RenderingError {
71
5
    fn from(e: T) -> RenderingError {
72
5
        RenderingError::RenderingError(e.into())
73
5
    }
74
}
75

            
76
impl fmt::Display for RenderingError {
77
2
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78
2
        match *self {
79
2
            RenderingError::RenderingError(ref e) => e.fmt(f),
80
            RenderingError::HandleIsNotLoaded => write!(f, "SVG data is not loaded into handle"),
81
        }
82
2
    }
83
}
84

            
85
/// Rust version of the `RsvgHandleFlags` enum in C.
86
454
#[glib::flags(name = "RsvgHandleFlags")]
87
pub enum HandleFlags {
88
    #[flags_value(name = "RSVG_HANDLE_FLAGS_NONE", nick = "flags-none")]
89
    NONE = 0,
90

            
91
    #[flags_value(name = "RSVG_HANDLE_FLAG_UNLIMITED", nick = "flag-unlimited")]
92
    UNLIMITED = 1 << 0,
93

            
94
    #[flags_value(
95
        name = "RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA",
96
        nick = "flag-keep-image-data"
97
    )]
98
    KEEP_IMAGE_DATA = 1 << 1,
99
}
100

            
101
impl Default for HandleFlags {
102
54
    fn default() -> Self {
103
        Self::NONE
104
54
    }
105
}
106

            
107
/// Type alias used to pass flags in the C API functions.
108
pub type RsvgHandleFlags = u32;
109

            
110
/// GObject class struct for RsvgHandle.
111
///
112
/// This is not done through [`glib::subclass::prelude::ClassStruct<T>`] because we need
113
/// to include the `_abi_padding` field for ABI compatibility with the C headers, and
114
/// `simple::ClassStruct` does not allow that.
115
#[repr(C)]
116
pub struct RsvgHandleClass {
117
    // Keep this in sync with rsvg.h:RsvgHandleClass
118
    parent: gobject_ffi::GObjectClass,
119

            
120
    _abi_padding: [gpointer; 15],
121
}
122

            
123
unsafe impl ClassStruct for RsvgHandleClass {
124
    type Type = imp::CHandle;
125
}
126

            
127
/// GObject instance struct for RsvgHandle.
128
///
129
/// This is not done through [`glib::subclass::prelude::InstanceStruct<T>`] because we need
130
/// to include the `_abi_padding` field for ABI compatibility with the C headers, and
131
/// `simple::InstanceStruct` does not allow that.
132
#[repr(C)]
133
pub struct RsvgHandle {
134
    // Keep this in sync with rsvg.h:RsvgHandle
135
    parent: gobject_ffi::GObject,
136

            
137
    _abi_padding: [gpointer; 16],
138
}
139

            
140
unsafe impl InstanceStruct for RsvgHandle {
141
    type Type = imp::CHandle;
142
}
143

            
144
/// State machine for `RsvgHandle`.
145
///
146
/// When an `RsvgHandled` is created it is empty / not loaded yet, and it does not know
147
/// whether the caller will feed it data gradually with the legacy `write()/close()` API,
148
/// or whether it will be given a `GInputStream` to read in a blocking fashion.  After the
149
/// handle is loaded (e.g. the SVG document is finished parsing), we make sure that no
150
/// further loading operations can be done.
151
#[allow(clippy::large_enum_variant)]
152
enum LoadState {
153
    /// Just created the CHandle; nothing loaded yet.
154
    Start,
155

            
156
    /// Being loaded using the legacy write()/close() API.
157
    ///
158
    /// We buffer all the data from `write()` calls until the time `close()` is called;
159
    /// then we run the buffer through a decompressor in case this is an SVGZ file.
160
    Loading { buffer: Vec<u8> },
161

            
162
    /// Loading finished successfully; the document is in the `SvgHandle`.
163
    ClosedOk { handle: SvgHandle },
164

            
165
    /// Loaded unsuccessfully.
166
    ClosedError,
167
}
168

            
169
impl LoadState {
170
43
    fn set_from_loading_result(
171
        &mut self,
172
        result: Result<SvgHandle, LoadingError>,
173
    ) -> Result<(), LoadingError> {
174
43
        match result {
175
42
            Ok(handle) => {
176
42
                *self = LoadState::ClosedOk { handle };
177
42
                Ok(())
178
42
            }
179

            
180
1
            Err(e) => {
181
1
                *self = LoadState::ClosedError;
182
1
                Err(e)
183
1
            }
184
        }
185
43
    }
186
}
187

            
188
impl Default for LoadState {
189
54
    fn default() -> Self {
190
54
        Self::Start
191
54
    }
192
}
193

            
194
/// Holds the base URL for loading a handle, and the C-accessible version of it
195
///
196
/// There is a public API to query the base URL, and we need to
197
/// produce a CString with it.  However, that API returns a borrowed
198
/// *const char, so we need to maintain a long-lived CString along with the
199
/// internal Url.
200
110
#[derive(Default)]
201
struct BaseUrl {
202
55
    inner: Option<BaseUrlInner>,
203
}
204

            
205
struct BaseUrlInner {
206
    url: Url,
207
    cstring: CString,
208
}
209

            
210
impl BaseUrl {
211
38
    fn set(&mut self, url: Url) {
212
38
        let cstring = CString::new(url.as_str()).unwrap();
213

            
214
38
        self.inner = Some(BaseUrlInner { url, cstring });
215
38
    }
216

            
217
46
    fn get(&self) -> Option<&Url> {
218
82
        self.inner.as_ref().map(|b| &b.url)
219
46
    }
220

            
221
43
    fn get_gfile(&self) -> Option<gio::File> {
222
77
        self.get().map(|url| gio::File::for_uri(url.as_str()))
223
43
    }
224

            
225
6
    fn get_ptr(&self) -> *const libc::c_char {
226
6
        self.inner
227
            .as_ref()
228
3
            .map(|b| b.cstring.as_ptr())
229
            .unwrap_or_else(ptr::null)
230
6
    }
231
}
232

            
233
#[derive(Clone, Copy)]
234
#[repr(C)]
235
pub struct RsvgRectangle {
236
    pub x: f64,
237
    pub y: f64,
238
    pub width: f64,
239
    pub height: f64,
240
}
241

            
242
impl From<cairo::Rectangle> for RsvgRectangle {
243
5
    fn from(r: cairo::Rectangle) -> RsvgRectangle {
244
5
        RsvgRectangle {
245
5
            x: r.x(),
246
5
            y: r.y(),
247
5
            width: r.width(),
248
5
            height: r.height(),
249
        }
250
5
    }
251
}
252

            
253
impl From<RsvgRectangle> for cairo::Rectangle {
254
7
    fn from(r: RsvgRectangle) -> cairo::Rectangle {
255
7
        cairo::Rectangle::new(r.x, r.y, r.width, r.height)
256
7
    }
257
}
258

            
259
mod imp {
260
    use std::marker::PhantomData;
261

            
262
    use super::*;
263

            
264
    /// Contains all the interior mutability for a RsvgHandle to be called
265
    /// from the C API.
266
784
    #[derive(Default, glib::Properties)]
267
    #[properties(wrapper_type = super::CHandle)]
268
    pub struct CHandle {
269
8
        #[property(name = "dpi-x", construct, type = f64, set = Self::set_dpi_x, get = Self::get_dpi_x)]
270
8
        #[property(name = "dpi-y", construct, type = f64, set = Self::set_dpi_y, get = Self::get_dpi_y)]
271
4
        #[property(name = "flags",  construct_only, get, set, type = HandleFlags, member = handle_flags)]
272
54
        pub(super) inner: RefCell<CHandleInner>,
273
40
        #[property(name = "base-uri", construct, set = Self::set_base_url, get = Self::get_base_url)]
274
54
        base_uri: PhantomData<Option<String>>,
275
5
        #[property(name = "width", get = |imp: &Self| imp.obj().get_dimensions_or_empty().width)]
276
54
        width: PhantomData<i32>,
277
5
        #[property(name = "height", get = |imp: &Self| imp.obj().get_dimensions_or_empty().height)]
278
54
        height: PhantomData<i32>,
279
5
        #[property(name = "em", get = |imp: &Self| imp.obj().get_dimensions_or_empty().em)]
280
54
        em: PhantomData<f64>,
281
5
        #[property(name = "ex", get = |imp: &Self| imp.obj().get_dimensions_or_empty().ex)]
282
54
        ex: PhantomData<f64>,
283
5
        #[property(name = "title", deprecated, get = |_| None)]
284
54
        title: PhantomData<Option<String>>,
285
5
        #[property(name = "desc", deprecated, get = |_| None)]
286
54
        desc: PhantomData<Option<String>>,
287
5
        #[property(name = "metadata", deprecated, get = |_| None)]
288
54
        metadata: PhantomData<Option<String>>,
289
54
        pub(super) load_state: RefCell<LoadState>,
290
54
        pub(super) session: Session,
291
    }
292

            
293
108
    #[derive(Default)]
294
    pub(super) struct CHandleInner {
295
54
        pub(super) dpi: Dpi,
296
54
        pub(super) handle_flags: HandleFlags,
297
54
        pub(super) base_url: BaseUrl,
298
54
        pub(super) size_callback: SizeCallback,
299
54
        pub(super) is_testing: bool,
300
    }
301

            
302
142080
    #[glib::object_subclass]
303
    impl ObjectSubclass for CHandle {
304
        const NAME: &'static str = "RsvgHandle";
305

            
306
        type Type = super::CHandle;
307

            
308
        type Instance = RsvgHandle;
309
        type Class = RsvgHandleClass;
310
    }
311

            
312
276
    #[glib::derived_properties]
313
    impl ObjectImpl for CHandle {}
314

            
315
    impl CHandle {
316
1
        fn get_base_url(&self) -> Option<String> {
317
1
            let inner = self.inner.borrow();
318
2
            inner.base_url.get().map(|url| url.as_str().to_string())
319
1
        }
320

            
321
58
        fn set_dpi_x(&self, dpi_x: f64) {
322
58
            let mut inner = self.inner.borrow_mut();
323
58
            let dpi = inner.dpi;
324
58
            inner.dpi = Dpi::new(dpi_x, dpi.y());
325
58
        }
326

            
327
58
        fn set_dpi_y(&self, dpi_y: f64) {
328
58
            let mut inner = self.inner.borrow_mut();
329
58
            let dpi = inner.dpi;
330
58
            inner.dpi = Dpi::new(dpi.x(), dpi_y);
331
58
        }
332

            
333
1
        fn get_dpi_x(&self) -> f64 {
334
1
            let inner = self.inner.borrow();
335
1
            inner.dpi.x()
336
1
        }
337

            
338
1
        fn get_dpi_y(&self) -> f64 {
339
1
            let inner = self.inner.borrow();
340
1
            inner.dpi.y()
341
1
        }
342

            
343
90
        fn set_base_url(&self, url: Option<&str>) {
344
90
            let Some(url) = url else {
345
                return;
346
            };
347
37
            let session = &self.session;
348
37
            let state = self.load_state.borrow();
349

            
350
37
            match *state {
351
                LoadState::Start => (),
352
                _ => {
353
                    rsvg_g_critical(
354
                        "Please set the base file or URI before loading any data into RsvgHandle",
355
                    );
356
                    return;
357
                }
358
            }
359

            
360
37
            match Url::parse(url) {
361
37
                Ok(u) => {
362
37
                    rsvg_log!(session, "setting base_uri to \"{}\"", u.as_str());
363
37
                    let mut inner = self.inner.borrow_mut();
364
37
                    inner.base_url.set(u);
365
37
                }
366

            
367
                Err(e) => {
368
                    rsvg_log!(
369
                        session,
370
                        "not setting base_uri to \"{}\" since it is invalid: {}",
371
                        url,
372
                        e
373
                    );
374
                }
375
            }
376
90
        }
377
    }
378
}
379

            
380
glib::wrapper! {
381
    // We don't use subclass:simple::InstanceStruct and ClassStruct
382
    // because we need to maintain the respective _abi_padding of each
383
    // of RsvgHandleClass and RsvgHandle.
384
    pub struct CHandle(ObjectSubclass<imp::CHandle>);
385
}
386

            
387
// Keep in sync with tests/src/reference.rs
388
84
pub(crate) fn checked_i32(x: f64) -> Result<i32, cairo::Error> {
389
84
    cast::i32(x).map_err(|_| cairo::Error::InvalidSize)
390
84
}
391

            
392
// Keep in sync with rsvg.h:RsvgPositionData
393
#[repr(C)]
394
pub struct RsvgPositionData {
395
    pub x: libc::c_int,
396
    pub y: libc::c_int,
397
}
398

            
399
// Keep in sync with rsvg.h:RsvgDimensionData
400
#[repr(C)]
401
pub struct RsvgDimensionData {
402
    pub width: libc::c_int,
403
    pub height: libc::c_int,
404
    pub em: f64,
405
    pub ex: f64,
406
}
407

            
408
impl RsvgDimensionData {
409
    // This is not #[derive(Default)] to make it clear that it
410
    // shouldn't be the default value for anything; it is actually a
411
    // special case we use to indicate an error to the public API.
412
1
    pub fn empty() -> RsvgDimensionData {
413
1
        RsvgDimensionData {
414
            width: 0,
415
            height: 0,
416
            em: 0.0,
417
            ex: 0.0,
418
        }
419
1
    }
420
}
421

            
422
// Keep in sync with rsvg.h:RsvgSizeFunc
423
pub type RsvgSizeFunc = Option<
424
    unsafe extern "C" fn(
425
        inout_width: *mut libc::c_int,
426
        inout_height: *mut libc::c_int,
427
        user_data: gpointer,
428
    ),
429
>;
430

            
431
struct SizeCallback {
432
    size_func: RsvgSizeFunc,
433
    user_data: gpointer,
434
    destroy_notify: glib::ffi::GDestroyNotify,
435
    in_loop: Cell<bool>,
436
}
437

            
438
impl SizeCallback {
439
6
    fn new(
440
        size_func: RsvgSizeFunc,
441
        user_data: gpointer,
442
        destroy_notify: glib::ffi::GDestroyNotify,
443
    ) -> Self {
444
6
        SizeCallback {
445
            size_func,
446
            user_data,
447
            destroy_notify,
448
6
            in_loop: Cell::new(false),
449
        }
450
6
    }
451

            
452
32
    fn call(&self, width: libc::c_int, height: libc::c_int) -> (libc::c_int, libc::c_int) {
453
        unsafe {
454
32
            let mut w = width;
455
32
            let mut h = height;
456

            
457
32
            if let Some(ref f) = self.size_func {
458
4
                f(&mut w, &mut h, self.user_data);
459
            };
460

            
461
32
            (w, h)
462
        }
463
32
    }
464

            
465
29
    fn start_loop(&self) {
466
29
        assert!(!self.in_loop.get());
467
29
        self.in_loop.set(true);
468
29
    }
469

            
470
29
    fn end_loop(&self) {
471
29
        assert!(self.in_loop.get());
472
29
        self.in_loop.set(false);
473
29
    }
474

            
475
29
    fn get_in_loop(&self) -> bool {
476
29
        self.in_loop.get()
477
29
    }
478
}
479

            
480
impl Default for SizeCallback {
481
54
    fn default() -> SizeCallback {
482
54
        SizeCallback {
483
54
            size_func: None,
484
54
            user_data: ptr::null_mut(),
485
54
            destroy_notify: None,
486
54
            in_loop: Cell::new(false),
487
        }
488
54
    }
489
}
490

            
491
impl Drop for SizeCallback {
492
60
    fn drop(&mut self) {
493
        unsafe {
494
60
            if let Some(ref f) = self.destroy_notify {
495
3
                f(self.user_data);
496
            };
497
        }
498
60
    }
499
}
500
pub trait CairoRectangleExt {
501
    fn from_size(width: f64, height: f64) -> Self;
502
}
503

            
504
impl CairoRectangleExt for cairo::Rectangle {
505
138
    fn from_size(width: f64, height: f64) -> Self {
506
138
        Self::new(0.0, 0.0, width, height)
507
138
    }
508
}
509

            
510
impl CHandle {
511
35
    fn set_base_gfile(&self, file: &gio::File) {
512
35
        self.set_base_uri(&*file.uri());
513
35
    }
514

            
515
4
    fn get_base_url_as_ptr(&self) -> *const libc::c_char {
516
4
        let inner = self.imp().inner.borrow();
517
4
        inner.base_url.get_ptr()
518
4
    }
519

            
520
6
    fn set_size_callback(
521
        &self,
522
        size_func: RsvgSizeFunc,
523
        user_data: gpointer,
524
        destroy_notify: glib::ffi::GDestroyNotify,
525
    ) {
526
6
        let mut inner = self.imp().inner.borrow_mut();
527
6
        inner.size_callback = SizeCallback::new(size_func, user_data, destroy_notify);
528
6
    }
529

            
530
23187
    fn write(&self, buf: &[u8]) {
531
23187
        let mut state = self.imp().load_state.borrow_mut();
532

            
533
23187
        match *state {
534
            LoadState::Start => {
535
6
                *state = LoadState::Loading {
536
6
                    buffer: Vec::from(buf),
537
6
                }
538
6
            }
539

            
540
23181
            LoadState::Loading { ref mut buffer } => {
541
23181
                buffer.extend_from_slice(buf);
542
            }
543

            
544
            _ => {
545
                rsvg_g_critical("Handle must not be closed in order to write to it");
546
            }
547
        }
548
23187
    }
549

            
550
9
    fn close(&self) -> Result<(), LoadingError> {
551
9
        let imp = self.imp();
552

            
553
9
        let inner = imp.inner.borrow();
554
9
        let mut state = imp.load_state.borrow_mut();
555

            
556
9
        match *state {
557
            LoadState::Start => {
558
1
                *state = LoadState::ClosedError;
559
1
                Err(LoadingError::XmlParseError(String::from(
560
                    "caller did not write any data",
561
1
                )))
562
            }
563

            
564
6
            LoadState::Loading { ref buffer } => {
565
6
                let bytes = Bytes::from(buffer);
566
6
                let stream = gio::MemoryInputStream::from_bytes(&bytes);
567

            
568
6
                let base_file = inner.base_url.get_gfile();
569
6
                self.read_stream(state, &stream.upcast(), base_file.as_ref(), None)
570
6
            }
571

            
572
            // Closing is idempotent
573
1
            LoadState::ClosedOk { .. } => Ok(()),
574
1
            LoadState::ClosedError => Ok(()),
575
        }
576
9
    }
577

            
578
37
    fn read_stream_sync(
579
        &self,
580
        stream: &gio::InputStream,
581
        cancellable: Option<&gio::Cancellable>,
582
    ) -> Result<(), LoadingError> {
583
37
        let imp = self.imp();
584

            
585
37
        let state = imp.load_state.borrow_mut();
586
37
        let inner = imp.inner.borrow();
587

            
588
37
        match *state {
589
            LoadState::Start => {
590
37
                let base_file = inner.base_url.get_gfile();
591
37
                self.read_stream(state, stream, base_file.as_ref(), cancellable)
592
37
            }
593

            
594
            LoadState::Loading { .. } | LoadState::ClosedOk { .. } | LoadState::ClosedError => {
595
                rsvg_g_critical(
596
                    "handle must not be already loaded in order to call \
597
                     rsvg_handle_read_stream_sync()",
598
                );
599
                Err(LoadingError::Other(String::from("API ordering")))
600
            }
601
        }
602
37
    }
603

            
604
43
    fn read_stream(
605
        &self,
606
        mut load_state: RefMut<'_, LoadState>,
607
        stream: &gio::InputStream,
608
        base_file: Option<&gio::File>,
609
        cancellable: Option<&gio::Cancellable>,
610
    ) -> Result<(), LoadingError> {
611
43
        let loader = self.make_loader();
612

            
613
43
        load_state.set_from_loading_result(loader.read_stream(stream, base_file, cancellable))
614
43
    }
615

            
616
63
    fn get_handle_ref(&self) -> Result<Ref<'_, SvgHandle>, RenderingError> {
617
63
        let state = self.imp().load_state.borrow();
618

            
619
63
        match *state {
620
            LoadState::Start => {
621
                rsvg_g_critical("Handle has not been loaded");
622
                Err(RenderingError::HandleIsNotLoaded)
623
            }
624

            
625
            LoadState::Loading { .. } => {
626
                rsvg_g_critical("Handle is still loading; call rsvg_handle_close() first");
627
                Err(RenderingError::HandleIsNotLoaded)
628
            }
629

            
630
            LoadState::ClosedError => {
631
                rsvg_g_critical(
632
                    "Handle could not read or parse the SVG; did you check for errors during the \
633
                     loading stage?",
634
                );
635
                Err(RenderingError::HandleIsNotLoaded)
636
            }
637

            
638
126
            LoadState::ClosedOk { .. } => Ok(Ref::map(state, |s| match *s {
639
63
                LoadState::ClosedOk { ref handle } => handle,
640
                _ => unreachable!(),
641
126
            })),
642
        }
643
63
    }
644

            
645
43
    fn make_loader(&self) -> Loader {
646
43
        let imp = self.imp();
647
43
        let inner = imp.inner.borrow();
648
43
        let session = imp.session.clone();
649

            
650
129
        Loader::new_with_session(session)
651
86
            .with_unlimited_size(inner.handle_flags.contains(HandleFlags::UNLIMITED))
652
86
            .keep_image_data(inner.handle_flags.contains(HandleFlags::KEEP_IMAGE_DATA))
653
43
    }
654

            
655
6
    fn has_sub(&self, id: &str) -> Result<bool, RenderingError> {
656
6
        let handle = self.get_handle_ref()?;
657
6
        Ok(handle.has_element_with_id(id)?)
658
6
    }
659

            
660
4
    fn get_dimensions_or_empty(&self) -> RsvgDimensionData {
661
4
        self.get_dimensions_sub(None)
662
            .unwrap_or_else(|_| RsvgDimensionData::empty())
663
4
    }
664

            
665
29
    fn get_dimensions_sub(&self, id: Option<&str>) -> Result<RsvgDimensionData, RenderingError> {
666
29
        let inner = self.imp().inner.borrow();
667

            
668
        // This function is probably called from the cairo_render functions,
669
        // or is being erroneously called within the size_func.
670
        // To prevent an infinite loop we are saving the state, and
671
        // returning a meaningless size.
672
29
        if inner.size_callback.get_in_loop() {
673
            return Ok(RsvgDimensionData {
674
                width: 1,
675
                height: 1,
676
                em: 1.0,
677
                ex: 1.0,
678
            });
679
        }
680

            
681
29
        inner.size_callback.start_loop();
682

            
683
29
        let res = self
684
            .get_geometry_sub(id)
685
28
            .and_then(|(ink_r, _)| {
686
                // Keep these in sync with tests/src/reference.rs
687
28
                let width = checked_i32(ink_r.width().round())?;
688
28
                let height = checked_i32(ink_r.height().round())?;
689

            
690
28
                Ok((ink_r, width, height))
691
28
            })
692
57
            .map(|(ink_r, width, height)| {
693
28
                let (w, h) = inner.size_callback.call(width, height);
694

            
695
28
                RsvgDimensionData {
696
                    width: w,
697
                    height: h,
698
28
                    em: ink_r.width(),
699
28
                    ex: ink_r.height(),
700
                }
701
28
            });
702

            
703
29
        inner.size_callback.end_loop();
704

            
705
29
        res
706
29
    }
707

            
708
7
    fn get_position_sub(&self, id: Option<&str>) -> Result<RsvgPositionData, RenderingError> {
709
7
        let inner = self.imp().inner.borrow();
710

            
711
7
        if id.is_none() {
712
1
            return Ok(RsvgPositionData { x: 0, y: 0 });
713
        }
714

            
715
6
        self.get_geometry_sub(id)
716
4
            .and_then(|(ink_r, _)| {
717
4
                let width = checked_i32(ink_r.width().round())?;
718
4
                let height = checked_i32(ink_r.height().round())?;
719

            
720
4
                Ok((ink_r, width, height))
721
4
            })
722
10
            .and_then(|(ink_r, width, height)| {
723
4
                inner.size_callback.call(width, height);
724

            
725
4
                Ok(RsvgPositionData {
726
4
                    x: checked_i32(ink_r.x())?,
727
4
                    y: checked_i32(ink_r.y())?,
728
                })
729
4
            })
730
7
    }
731

            
732
57
    fn make_renderer<'a>(&self, handle_ref: &'a Ref<'_, SvgHandle>) -> CairoRenderer<'a> {
733
57
        let inner = self.imp().inner.borrow();
734

            
735
171
        CairoRenderer::new(handle_ref)
736
114
            .with_dpi(inner.dpi.x(), inner.dpi.y())
737
57
            .test_mode(inner.is_testing)
738
57
    }
739

            
740
35
    fn get_geometry_sub(
741
        &self,
742
        id: Option<&str>,
743
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
744
35
        let handle = self.get_handle_ref()?;
745
35
        let renderer = self.make_renderer(&handle);
746

            
747
35
        Ok(renderer.legacy_layer_geometry(id)?)
748
35
    }
749

            
750
1
    fn set_stylesheet(&self, css: &str) -> Result<(), LoadingError> {
751
1
        match *self.imp().load_state.borrow_mut() {
752
1
            LoadState::ClosedOk { ref mut handle } => handle.set_stylesheet(css),
753

            
754
            _ => {
755
                rsvg_g_critical(
756
                    "handle must already be loaded in order to call \
757
                     rsvg_handle_set_stylesheet()",
758
                );
759
                Err(LoadingError::Other(String::from("API ordering")))
760
            }
761
        }
762
1
    }
763

            
764
8
    fn render_cairo_sub(
765
        &self,
766
        cr: *mut cairo::ffi::cairo_t,
767
        id: Option<&str>,
768
    ) -> Result<(), RenderingError> {
769
8
        let dimensions = self.get_dimensions_sub(None)?;
770
8
        if dimensions.width == 0 || dimensions.height == 0 {
771
            // nothing to render
772
            return Ok(());
773
        }
774

            
775
8
        let viewport = cairo::Rectangle::new(
776
            0.0,
777
            0.0,
778
8
            f64::from(dimensions.width),
779
8
            f64::from(dimensions.height),
780
        );
781

            
782
8
        self.render_layer(cr, id, &viewport)
783
8
    }
784

            
785
    #[cfg(feature = "pixbuf")]
786
5
    fn get_pixbuf_sub(&self, id: Option<&str>) -> Result<Pixbuf, RenderingError> {
787
5
        let dimensions = self.get_dimensions_sub(None)?;
788

            
789
5
        if dimensions.width == 0 || dimensions.height == 0 {
790
1
            return Ok(empty_pixbuf()?);
791
        }
792

            
793
4
        let surface = cairo::ImageSurface::create(
794
4
            cairo::Format::ARgb32,
795
4
            dimensions.width,
796
4
            dimensions.height,
797
        )?;
798

            
799
        {
800
4
            let cr = cairo::Context::new(&surface)?;
801
4
            let cr_raw = cr.to_raw_none();
802
9
            self.render_cairo_sub(cr_raw, id)?;
803
4
        }
804

            
805
4
        let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
806

            
807
4
        Ok(pixbuf_from_surface(&surface)?)
808
5
    }
809

            
810
3
    fn render_document(
811
        &self,
812
        cr: *mut cairo::ffi::cairo_t,
813
        viewport: &cairo::Rectangle,
814
    ) -> Result<(), RenderingError> {
815
3
        let cr = check_cairo_context(cr)?;
816

            
817
3
        let handle = self.get_handle_ref()?;
818

            
819
3
        let renderer = self.make_renderer(&handle);
820
6
        Ok(renderer.render_document(&cr, viewport)?)
821
3
    }
822

            
823
2
    fn get_geometry_for_layer(
824
        &self,
825
        id: Option<&str>,
826
        viewport: &cairo::Rectangle,
827
    ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> {
828
2
        let handle = self.get_handle_ref()?;
829
2
        let renderer = self.make_renderer(&handle);
830

            
831
2
        Ok(renderer
832
            .geometry_for_layer(id, viewport)
833
2
            .map(|(i, l)| (RsvgRectangle::from(i), RsvgRectangle::from(l)))?)
834
2
    }
835

            
836
9
    fn render_layer(
837
        &self,
838
        cr: *mut cairo::ffi::cairo_t,
839
        id: Option<&str>,
840
        viewport: &cairo::Rectangle,
841
    ) -> Result<(), RenderingError> {
842
9
        let cr = check_cairo_context(cr)?;
843

            
844
9
        let handle = self.get_handle_ref()?;
845

            
846
9
        let renderer = self.make_renderer(&handle);
847

            
848
18
        Ok(renderer.render_layer(&cr, id, viewport)?)
849
9
    }
850

            
851
2
    fn get_geometry_for_element(
852
        &self,
853
        id: Option<&str>,
854
    ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> {
855
2
        let handle = self.get_handle_ref()?;
856

            
857
2
        let renderer = self.make_renderer(&handle);
858

            
859
2
        Ok(renderer
860
            .geometry_for_element(id)
861
2
            .map(|(i, l)| (RsvgRectangle::from(i), RsvgRectangle::from(l)))?)
862
2
    }
863

            
864
1
    fn render_element(
865
        &self,
866
        cr: *mut cairo::ffi::cairo_t,
867
        id: Option<&str>,
868
        element_viewport: &cairo::Rectangle,
869
    ) -> Result<(), RenderingError> {
870
1
        let cr = check_cairo_context(cr)?;
871

            
872
1
        let handle = self.get_handle_ref()?;
873

            
874
1
        let renderer = self.make_renderer(&handle);
875

            
876
2
        Ok(renderer.render_element(&cr, id, element_viewport)?)
877
1
    }
878

            
879
2
    fn get_intrinsic_dimensions(&self) -> Result<IntrinsicDimensions, RenderingError> {
880
2
        let handle = self.get_handle_ref()?;
881
2
        let renderer = self.make_renderer(&handle);
882
2
        Ok(renderer.intrinsic_dimensions())
883
2
    }
884

            
885
3
    fn get_intrinsic_size_in_pixels(&self) -> Result<Option<(f64, f64)>, RenderingError> {
886
3
        let handle = self.get_handle_ref()?;
887
3
        let renderer = self.make_renderer(&handle);
888
3
        Ok(renderer.intrinsic_size_in_pixels())
889
3
    }
890

            
891
    fn set_testing(&self, is_testing: bool) {
892
        let mut inner = self.imp().inner.borrow_mut();
893
        inner.is_testing = is_testing;
894
    }
895
}
896

            
897
23268
fn is_rsvg_handle(obj: *const RsvgHandle) -> bool {
898
23268
    unsafe { instance_of::<CHandle>(obj as *const _) }
899
23268
}
900

            
901
4
fn is_input_stream(obj: *mut gio::ffi::GInputStream) -> bool {
902
4
    unsafe { instance_of::<gio::InputStream>(obj as *const _) }
903
4
}
904

            
905
35
fn is_gfile(obj: *const gio::ffi::GFile) -> bool {
906
35
    unsafe { instance_of::<gio::File>(obj as *const _) }
907
35
}
908

            
909
fn is_cancellable(obj: *mut gio::ffi::GCancellable) -> bool {
910
    unsafe { instance_of::<gio::Cancellable>(obj as *const _) }
911
}
912

            
913
23298
fn get_rust_handle(handle: *const RsvgHandle) -> CHandle {
914
23298
    let handle = unsafe { &*handle };
915
23298
    handle.imp().obj().to_owned()
916
23298
}
917

            
918
#[no_mangle]
919
10
pub unsafe extern "C" fn rsvg_handle_get_type() -> glib::ffi::GType {
920
10
    CHandle::static_type().into_glib()
921
10
}
922

            
923
#[no_mangle]
924
1
pub unsafe extern "C" fn rsvg_error_get_type() -> glib::ffi::GType {
925
1
    Error::static_type().into_glib()
926
1
}
927

            
928
#[no_mangle]
929
2
pub unsafe extern "C" fn rsvg_handle_flags_get_type() -> glib::ffi::GType {
930
2
    HandleFlags::static_type().into_glib()
931
2
}
932

            
933
#[no_mangle]
934
1
pub unsafe extern "C" fn rsvg_handle_set_base_uri(
935
    handle: *const RsvgHandle,
936
    uri: *const libc::c_char,
937
) {
938
    rsvg_return_if_fail! {
939
        rsvg_handle_set_base_uri;
940

            
941
1
        is_rsvg_handle(handle),
942
1
        !uri.is_null(),
943
    }
944

            
945
1
    let rhandle = get_rust_handle(handle);
946

            
947
1
    assert!(!uri.is_null());
948
1
    let uri: String = from_glib_none(uri);
949

            
950
1
    rhandle.set_base_uri(&*uri);
951
1
}
952

            
953
#[no_mangle]
954
1
pub unsafe extern "C" fn rsvg_handle_set_base_gfile(
955
    handle: *const RsvgHandle,
956
    raw_gfile: *mut gio::ffi::GFile,
957
) {
958
    rsvg_return_if_fail! {
959
        rsvg_handle_set_base_gfile;
960

            
961
1
        is_rsvg_handle(handle),
962
1
        is_gfile(raw_gfile),
963
    }
964

            
965
1
    let rhandle = get_rust_handle(handle);
966

            
967
1
    assert!(!raw_gfile.is_null());
968

            
969
1
    let file: gio::File = from_glib_none(raw_gfile);
970

            
971
1
    rhandle.set_base_gfile(&file);
972
1
}
973

            
974
#[no_mangle]
975
4
pub unsafe extern "C" fn rsvg_handle_get_base_uri(
976
    handle: *const RsvgHandle,
977
) -> *const libc::c_char {
978
    rsvg_return_val_if_fail! {
979
        rsvg_handle_get_base_uri => ptr::null();
980

            
981
4
        is_rsvg_handle(handle),
982
    }
983

            
984
4
    let rhandle = get_rust_handle(handle);
985

            
986
4
    rhandle.get_base_url_as_ptr()
987
4
}
988

            
989
#[no_mangle]
990
3
pub unsafe extern "C" fn rsvg_handle_set_dpi(handle: *const RsvgHandle, dpi: libc::c_double) {
991
    rsvg_return_if_fail! {
992
        rsvg_handle_set_dpi;
993

            
994
3
        is_rsvg_handle(handle),
995
    }
996

            
997
3
    let rhandle = get_rust_handle(handle);
998
3
    rhandle.set_dpi_x(dpi);
999
3
    rhandle.set_dpi_y(dpi);
3
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_set_dpi_x_y(
    handle: *const RsvgHandle,
    dpi_x: libc::c_double,
    dpi_y: libc::c_double,
) {
    rsvg_return_if_fail! {
        rsvg_handle_set_dpi_x_y;
1
        is_rsvg_handle(handle),
    }
1
    let rhandle = get_rust_handle(handle);
1
    rhandle.set_dpi_x(dpi_x);
1
    rhandle.set_dpi_y(dpi_y);
1
}
#[no_mangle]
6
pub unsafe extern "C" fn rsvg_handle_set_size_callback(
    handle: *const RsvgHandle,
    size_func: RsvgSizeFunc,
    user_data: gpointer,
    destroy_notify: glib::ffi::GDestroyNotify,
) {
    rsvg_return_if_fail! {
        rsvg_handle_set_size_callback;
6
        is_rsvg_handle(handle),
    }
6
    let rhandle = get_rust_handle(handle);
6
    rhandle.set_size_callback(size_func, user_data, destroy_notify);
6
}
#[no_mangle]
pub unsafe extern "C" fn rsvg_handle_internal_set_testing(
    handle: *const RsvgHandle,
    testing: glib::ffi::gboolean,
) {
    rsvg_return_if_fail! {
        rsvg_handle_internal_set_testing;
        is_rsvg_handle(handle),
    }
    let rhandle = get_rust_handle(handle);
    rhandle.set_testing(from_glib(testing));
}
trait IntoGError {
    type GlibResult;
    fn into_gerror(self, session: &Session, error: *mut *mut glib::ffi::GError)
        -> Self::GlibResult;
    fn into_g_warning(self) -> Self::GlibResult;
}
impl<E: fmt::Display> IntoGError for Result<(), E> {
    type GlibResult = glib::ffi::gboolean;
    /// Use this one when the public API actually uses a GError.
20
    fn into_gerror(
        self,
        session: &Session,
        error: *mut *mut glib::ffi::GError,
    ) -> Self::GlibResult {
20
        match self {
16
            Ok(()) => true.into_glib(),
4
            Err(e) => {
8
                set_gerror(session, error, 0, &format!("{e}"));
4
                false.into_glib()
4
            }
        }
20
    }
    /// Use this one when the public API doesn't use a GError.
4
    fn into_g_warning(self) -> Self::GlibResult {
4
        match self {
4
            Ok(()) => true.into_glib(),
            Err(e) => {
                rsvg_g_warning(&format!("{e}"));
                false.into_glib()
            }
        }
4
    }
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_read_stream_sync(
    handle: *const RsvgHandle,
    stream: *mut gio::ffi::GInputStream,
    cancellable: *mut gio::ffi::GCancellable,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_read_stream_sync => false.into_glib();
1
        is_rsvg_handle(handle),
1
        is_input_stream(stream),
1
        cancellable.is_null() || is_cancellable(cancellable),
1
        error.is_null() || (*error).is_null(),
    }
1
    let rhandle = get_rust_handle(handle);
1
    let session = rhandle.imp().session.clone();
1
    let stream = gio::InputStream::from_glib_none(stream);
1
    let cancellable: Option<gio::Cancellable> = from_glib_none(cancellable);
1
    rhandle
1
        .read_stream_sync(&stream, cancellable.as_ref())
        .into_gerror(&session, error)
1
}
#[no_mangle]
23187
pub unsafe extern "C" fn rsvg_handle_write(
    handle: *const RsvgHandle,
    buf: *const u8,
    count: usize,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_write => false.into_glib();
23187
        is_rsvg_handle(handle),
23187
        error.is_null() || (*error).is_null(),
23187
        !buf.is_null() || count == 0,
    }
23187
    let rhandle = get_rust_handle(handle);
23187
    let buffer = slice::from_raw_parts(buf, count);
23187
    rhandle.write(buffer);
23187
    true.into_glib()
23187
}
#[no_mangle]
9
pub unsafe extern "C" fn rsvg_handle_close(
    handle: *const RsvgHandle,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_close => false.into_glib();
9
        is_rsvg_handle(handle),
9
        error.is_null() || (*error).is_null(),
    }
9
    let rhandle = get_rust_handle(handle);
9
    let session = rhandle.imp().session.clone();
9
    rhandle.close().into_gerror(&session, error)
9
}
#[no_mangle]
6
pub unsafe extern "C" fn rsvg_handle_has_sub(
    handle: *const RsvgHandle,
    id: *const libc::c_char,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_has_sub => false.into_glib();
6
        is_rsvg_handle(handle),
    }
6
    let rhandle = get_rust_handle(handle);
6
    if id.is_null() {
        return false.into_glib();
    }
6
    let id: String = from_glib_none(id);
6
    rhandle.has_sub(&id).unwrap_or(false).into_glib()
6
}
#[no_mangle]
2
pub unsafe extern "C" fn rsvg_handle_render_cairo(
    handle: *const RsvgHandle,
    cr: *mut cairo::ffi::cairo_t,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_render_cairo => false.into_glib();
2
        is_rsvg_handle(handle),
2
        !cr.is_null(),
    }
2
    let rhandle = get_rust_handle(handle);
2
    rhandle.render_cairo_sub(cr, None).into_g_warning()
2
}
#[no_mangle]
2
pub unsafe extern "C" fn rsvg_handle_render_cairo_sub(
    handle: *const RsvgHandle,
    cr: *mut cairo::ffi::cairo_t,
    id: *const libc::c_char,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_render_cairo_sub => false.into_glib();
2
        is_rsvg_handle(handle),
2
        !cr.is_null(),
    }
2
    let rhandle = get_rust_handle(handle);
2
    let id: Option<String> = from_glib_none(id);
2
    rhandle.render_cairo_sub(cr, id.as_deref()).into_g_warning()
2
}
#[no_mangle]
#[cfg(feature = "pixbuf")]
3
pub unsafe extern "C" fn rsvg_handle_get_pixbuf(
    handle: *const RsvgHandle,
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_pixbuf => ptr::null_mut();
3
        is_rsvg_handle(handle),
    }
3
    let mut error = ptr::null_mut();
3
    let pixbuf = rsvg_handle_get_pixbuf_and_error(handle, &mut error);
3
    if !error.is_null() {
        let rhandle = get_rust_handle(handle);
        let session = &rhandle.imp().session;
        let msg = format!("could not render: {:?}", *error);
        rsvg_log!(session, "{}", msg);
        rsvg_g_warning(&msg);
        return ptr::null_mut();
    }
3
    pixbuf
3
}
#[no_mangle]
#[cfg(feature = "pixbuf")]
4
pub unsafe extern "C" fn rsvg_handle_get_pixbuf_and_error(
    handle: *const RsvgHandle,
    error: *mut *mut glib::ffi::GError,
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_pixbuf_and_error => ptr::null_mut();
4
        is_rsvg_handle(handle),
4
        error.is_null() || (*error).is_null(),
    }
4
    let rhandle = get_rust_handle(handle);
    // into_gerror but returning the Ok value
4
    match rhandle.get_pixbuf_sub(None) {
4
        Ok(pixbuf) => pixbuf.to_glib_full(),
        Err(e) => {
            let session = &rhandle.imp().session;
            set_gerror(session, error, 0, &format!("{e}"));
            ptr::null_mut()
        }
    }
4
}
#[no_mangle]
#[cfg(feature = "pixbuf")]
1
pub unsafe extern "C" fn rsvg_handle_get_pixbuf_sub(
    handle: *const RsvgHandle,
    id: *const libc::c_char,
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_pixbuf_sub => ptr::null_mut();
1
        is_rsvg_handle(handle),
    }
1
    let rhandle = get_rust_handle(handle);
1
    let id: Option<String> = from_glib_none(id);
1
    match rhandle.get_pixbuf_sub(id.as_deref()) {
1
        Ok(pixbuf) => pixbuf.to_glib_full(),
        Err(e) => {
            let session = &rhandle.imp().session;
            let msg = format!("could not render: {}", e);
            rsvg_log!(session, "{}", msg);
            rsvg_g_warning(&msg);
            ptr::null_mut()
        }
    }
1
}
#[no_mangle]
7
pub unsafe extern "C" fn rsvg_handle_get_dimensions(
    handle: *const RsvgHandle,
    dimension_data: *mut RsvgDimensionData,
) {
7
    rsvg_handle_get_dimensions_sub(handle, dimension_data, ptr::null());
7
}
#[no_mangle]
12
pub unsafe extern "C" fn rsvg_handle_get_dimensions_sub(
    handle: *const RsvgHandle,
    dimension_data: *mut RsvgDimensionData,
    id: *const libc::c_char,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_dimensions_sub => false.into_glib();
12
        is_rsvg_handle(handle),
12
        !dimension_data.is_null(),
    }
12
    let rhandle = get_rust_handle(handle);
12
    let id: Option<String> = from_glib_none(id);
12
    match rhandle.get_dimensions_sub(id.as_deref()) {
11
        Ok(dimensions) => {
11
            *dimension_data = dimensions;
11
            true.into_glib()
        }
1
        Err(e) => {
1
            let session = &rhandle.imp().session;
1
            rsvg_log!(session, "could not get dimensions: {}", e);
1
            *dimension_data = RsvgDimensionData::empty();
1
            false.into_glib()
1
        }
    }
12
}
#[no_mangle]
7
pub unsafe extern "C" fn rsvg_handle_get_position_sub(
    handle: *const RsvgHandle,
    position_data: *mut RsvgPositionData,
    id: *const libc::c_char,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_position_sub => false.into_glib();
7
        is_rsvg_handle(handle),
7
        !position_data.is_null(),
    }
7
    let rhandle = get_rust_handle(handle);
7
    let id: Option<String> = from_glib_none(id);
7
    match rhandle.get_position_sub(id.as_deref()) {
5
        Ok(position) => {
5
            *position_data = position;
5
            true.into_glib()
        }
2
        Err(e) => {
2
            let p = &mut *position_data;
2
            p.x = 0;
2
            p.y = 0;
2
            let session = &rhandle.imp().session;
2
            rsvg_log!(session, "could not get position: {}", e);
2
            false.into_glib()
2
        }
    }
7
}
#[no_mangle]
8
pub unsafe extern "C" fn rsvg_handle_new() -> *const RsvgHandle {
8
    let obj = glib::Object::new::<CHandle>();
8
    obj.to_glib_full()
8
}
#[no_mangle]
40
pub unsafe extern "C" fn rsvg_handle_new_with_flags(flags: RsvgHandleFlags) -> *const RsvgHandle {
40
    let obj = glib::Object::builder::<CHandle>()
80
        .property("flags", HandleFlags::from_bits_truncate(flags))
        .build();
40
    obj.to_glib_full()
40
}
#[no_mangle]
32
pub unsafe extern "C" fn rsvg_handle_new_from_file(
    filename: *const libc::c_char,
    error: *mut *mut glib::ffi::GError,
) -> *const RsvgHandle {
    rsvg_return_val_if_fail! {
        rsvg_handle_new_from_file => ptr::null();
32
        !filename.is_null(),
32
        error.is_null() || (*error).is_null(),
    }
32
    let file = match PathOrUrl::new(filename) {
32
        Ok(p) => p.get_gfile(),
        Err(s) => {
            // Here we don't have a handle created yet, so it's fine to create a session
            // to log the error message.  We'll need to change this when we start logging
            // API calls, so that we can log the call to rsvg_handle_new_from_file() and
            // then pass *that* session to rsvg_handle_new_from_gfile_sync() below.
            let session = Session::default();
            set_gerror(&session, error, 0, &s);
            return ptr::null_mut();
        }
    };
32
    rsvg_handle_new_from_gfile_sync(file.to_glib_none().0, 0, ptr::null_mut(), error)
32
}
#[no_mangle]
33
pub unsafe extern "C" fn rsvg_handle_new_from_gfile_sync(
    file: *mut gio::ffi::GFile,
    flags: RsvgHandleFlags,
    cancellable: *mut gio::ffi::GCancellable,
    error: *mut *mut glib::ffi::GError,
) -> *const RsvgHandle {
    rsvg_return_val_if_fail! {
        rsvg_handle_new_from_gfile_sync => ptr::null();
33
        is_gfile(file),
33
        cancellable.is_null() || is_cancellable(cancellable),
33
        error.is_null() || (*error).is_null(),
    }
33
    let raw_handle = rsvg_handle_new_with_flags(flags);
33
    let rhandle = get_rust_handle(raw_handle);
33
    let session = rhandle.imp().session.clone();
33
    let file = gio::File::from_glib_none(file);
33
    rhandle.set_base_gfile(&file);
33
    let cancellable: Option<gio::Cancellable> = from_glib_none(cancellable);
33
    let res = file
33
        .read(cancellable.as_ref())
        .map_err(LoadingError::from)
66
        .and_then(|stream| rhandle.read_stream_sync(&stream.upcast(), cancellable.as_ref()));
33
    match res {
33
        Ok(()) => raw_handle,
        Err(e) => {
            set_gerror(&session, error, 0, &format!("{e}"));
            gobject_ffi::g_object_unref(raw_handle as *mut _);
            ptr::null_mut()
        }
    }
33
}
#[no_mangle]
5
pub unsafe extern "C" fn rsvg_handle_new_from_stream_sync(
    input_stream: *mut gio::ffi::GInputStream,
    base_file: *mut gio::ffi::GFile,
    flags: RsvgHandleFlags,
    cancellable: *mut gio::ffi::GCancellable,
    error: *mut *mut glib::ffi::GError,
) -> *const RsvgHandle {
5
    rsvg_return_val_if_fail! {
        rsvg_handle_new_from_stream_sync => ptr::null();
5
        is_input_stream(input_stream),
5
        base_file.is_null() || is_gfile(base_file),
5
        cancellable.is_null() || is_cancellable(cancellable),
5
        error.is_null() || (*error).is_null(),
    }
5
    let raw_handle = rsvg_handle_new_with_flags(flags);
5
    let rhandle = get_rust_handle(raw_handle);
5
    let session = rhandle.imp().session.clone();
5
    let base_file: Option<gio::File> = from_glib_none(base_file);
5
    if let Some(base_file) = base_file {
1
        rhandle.set_base_gfile(&base_file);
1
    }
4
    let stream: gio::InputStream = from_glib_none(input_stream);
3
    let cancellable: Option<gio::Cancellable> = from_glib_none(cancellable);
3
    match rhandle.read_stream_sync(&stream, cancellable.as_ref()) {
3
        Ok(()) => raw_handle,
        Err(e) => {
            set_gerror(&session, error, 0, &format!("{e}"));
            gobject_ffi::g_object_unref(raw_handle as *mut _);
            ptr::null_mut()
        }
    }
3
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_new_from_data(
    data: *const u8,
    data_len: usize,
    error: *mut *mut glib::ffi::GError,
) -> *const RsvgHandle {
    rsvg_return_val_if_fail! {
        rsvg_handle_new_from_data => ptr::null();
1
        !data.is_null() || data_len == 0,
1
        data_len <= std::isize::MAX as usize,
1
        error.is_null() || (*error).is_null(),
    }
    // We create the MemoryInputStream without the gtk-rs binding because of this:
    //
    // - The binding doesn't provide _new_from_data().  All of the binding's ways to
    // put data into a MemoryInputStream involve copying the data buffer.
    //
    // - We can't use glib::Bytes from the binding either, for the same reason.
    //
    // - For now, we are using the other C-visible constructor, so we need a raw pointer to the
    //   stream, anyway.
1
    assert!(data_len <= std::isize::MAX as usize);
1
    let data_len = data_len as isize;
1
    let raw_stream = gio::ffi::g_memory_input_stream_new_from_data(data as *mut u8, data_len, None);
1
    let ret = rsvg_handle_new_from_stream_sync(
        raw_stream,
1
        ptr::null_mut(), // base_file
        0,
1
        ptr::null_mut(), // cancellable
        error,
    );
1
    gobject_ffi::g_object_unref(raw_stream as *mut _);
1
    ret
1
}
6
unsafe fn set_out_param<T: Copy>(
    out_has_param: *mut glib::ffi::gboolean,
    out_param: *mut T,
    value: &Option<T>,
) {
11
    let has_value = if let Some(ref v) = *value {
10
        if !out_param.is_null() {
5
            *out_param = *v;
        }
5
        true
    } else {
1
        false
    };
12
    if !out_has_param.is_null() {
6
        *out_has_param = has_value.into_glib();
    }
6
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_free(handle: *mut RsvgHandle) {
1
    gobject_ffi::g_object_unref(handle as *mut _);
1
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_set_stylesheet(
    handle: *const RsvgHandle,
    css: *const u8,
    css_len: usize,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_set_stylesheet => false.into_glib();
1
        is_rsvg_handle(handle),
1
        !css.is_null() || (css.is_null() && css_len == 0),
1
        error.is_null() || (*error).is_null(),
    }
1
    let rhandle = get_rust_handle(handle);
1
    let session = rhandle.imp().session.clone();
1
    let css = match (css, css_len) {
        (p, 0) if p.is_null() => "",
        (_, _) => {
1
            let s = slice::from_raw_parts(css, css_len);
1
            match str::from_utf8(s) {
1
                Ok(s) => s,
                Err(e) => {
                    set_gerror(&session, error, 0, &format!("CSS is not valid UTF-8: {e}"));
                    return false.into_glib();
                }
            }
1
        }
    };
1
    rhandle.set_stylesheet(css).into_gerror(&session, error)
1
}
#[no_mangle]
2
pub unsafe extern "C" fn rsvg_handle_get_intrinsic_dimensions(
    handle: *const RsvgHandle,
    out_has_width: *mut glib::ffi::gboolean,
    out_width: *mut Length,
    out_has_height: *mut glib::ffi::gboolean,
    out_height: *mut Length,
    out_has_viewbox: *mut glib::ffi::gboolean,
    out_viewbox: *mut RsvgRectangle,
) {
    rsvg_return_if_fail! {
        rsvg_handle_get_intrinsic_dimensions;
2
        is_rsvg_handle(handle),
    }
2
    let rhandle = get_rust_handle(handle);
2
    let d = rhandle
        .get_intrinsic_dimensions()
        .unwrap_or_else(|_| panic!("API called out of order"));
2
    let w = d.width;
2
    let h = d.height;
2
    let r = d.vbox.map(RsvgRectangle::from);
2
    set_out_param(out_has_width, out_width, &Into::into(w));
2
    set_out_param(out_has_height, out_height, &Into::into(h));
2
    set_out_param(out_has_viewbox, out_viewbox, &r);
2
}
#[no_mangle]
3
pub unsafe extern "C" fn rsvg_handle_get_intrinsic_size_in_pixels(
    handle: *const RsvgHandle,
    out_width: *mut f64,
    out_height: *mut f64,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_intrinsic_size_in_pixels => false.into_glib();
3
        is_rsvg_handle(handle),
    }
3
    let rhandle = get_rust_handle(handle);
3
    let dim = rhandle
        .get_intrinsic_size_in_pixels()
        .unwrap_or_else(|_| panic!("API called out of order"));
3
    let (w, h) = dim.unwrap_or((0.0, 0.0));
5
    if !out_width.is_null() {
2
        *out_width = w;
    }
5
    if !out_height.is_null() {
2
        *out_height = h;
    }
3
    dim.is_some().into_glib()
3
}
#[no_mangle]
3
pub unsafe extern "C" fn rsvg_handle_render_document(
    handle: *const RsvgHandle,
    cr: *mut cairo::ffi::cairo_t,
    viewport: *const RsvgRectangle,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_render_document => false.into_glib();
3
        is_rsvg_handle(handle),
3
        !cr.is_null(),
3
        !viewport.is_null(),
3
        error.is_null() || (*error).is_null(),
    }
3
    let rhandle = get_rust_handle(handle);
3
    let session = rhandle.imp().session.clone();
3
    rhandle
3
        .render_document(cr, &(*viewport).into())
        .into_gerror(&session, error)
3
}
#[no_mangle]
2
pub unsafe extern "C" fn rsvg_handle_get_geometry_for_layer(
    handle: *mut RsvgHandle,
    id: *const libc::c_char,
    viewport: *const RsvgRectangle,
    out_ink_rect: *mut RsvgRectangle,
    out_logical_rect: *mut RsvgRectangle,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_geometry_for_layer => false.into_glib();
2
        is_rsvg_handle(handle),
2
        !viewport.is_null(),
2
        error.is_null() || (*error).is_null(),
    }
2
    let rhandle = get_rust_handle(handle);
2
    let session = rhandle.imp().session.clone();
2
    let id: Option<String> = from_glib_none(id);
2
    rhandle
2
        .get_geometry_for_layer(id.as_deref(), &(*viewport).into())
3
        .map(|(ink_rect, logical_rect)| {
2
            if !out_ink_rect.is_null() {
1
                *out_ink_rect = ink_rect;
            }
2
            if !out_logical_rect.is_null() {
1
                *out_logical_rect = logical_rect;
            }
1
        })
        .into_gerror(&session, error)
2
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_render_layer(
    handle: *const RsvgHandle,
    cr: *mut cairo::ffi::cairo_t,
    id: *const libc::c_char,
    viewport: *const RsvgRectangle,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_render_layer => false.into_glib();
1
        is_rsvg_handle(handle),
1
        !cr.is_null(),
1
        !viewport.is_null(),
1
        error.is_null() || (*error).is_null(),
    }
1
    let rhandle = get_rust_handle(handle);
1
    let session = rhandle.imp().session.clone();
1
    let id: Option<String> = from_glib_none(id);
1
    rhandle
1
        .render_layer(cr, id.as_deref(), &(*viewport).into())
        .into_gerror(&session, error)
1
}
#[no_mangle]
2
pub unsafe extern "C" fn rsvg_handle_get_geometry_for_element(
    handle: *const RsvgHandle,
    id: *const libc::c_char,
    out_ink_rect: *mut RsvgRectangle,
    out_logical_rect: *mut RsvgRectangle,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_geometry_for_element => false.into_glib();
2
        is_rsvg_handle(handle),
2
        error.is_null() || (*error).is_null(),
    }
2
    let rhandle = get_rust_handle(handle);
2
    let session = rhandle.imp().session.clone();
2
    let id: Option<String> = from_glib_none(id);
2
    rhandle
2
        .get_geometry_for_element(id.as_deref())
3
        .map(|(ink_rect, logical_rect)| {
2
            if !out_ink_rect.is_null() {
1
                *out_ink_rect = ink_rect;
            }
2
            if !out_logical_rect.is_null() {
1
                *out_logical_rect = logical_rect;
            }
1
        })
        .into_gerror(&session, error)
2
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_render_element(
    handle: *const RsvgHandle,
    cr: *mut cairo::ffi::cairo_t,
    id: *const libc::c_char,
    element_viewport: *const RsvgRectangle,
    error: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    rsvg_return_val_if_fail! {
        rsvg_handle_render_element => false.into_glib();
1
        is_rsvg_handle(handle),
1
        !cr.is_null(),
1
        !element_viewport.is_null(),
1
        error.is_null() || (*error).is_null(),
    }
1
    let rhandle = get_rust_handle(handle);
1
    let session = rhandle.imp().session.clone();
1
    let id: Option<String> = from_glib_none(id);
1
    rhandle
1
        .render_element(cr, id.as_deref(), &(*element_viewport).into())
        .into_gerror(&session, error)
1
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_get_desc(handle: *const RsvgHandle) -> *mut libc::c_char {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_desc => ptr::null_mut();
1
        is_rsvg_handle(handle),
    }
1
    ptr::null_mut()
1
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_get_metadata(handle: *const RsvgHandle) -> *mut libc::c_char {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_metadata => ptr::null_mut();
1
        is_rsvg_handle(handle),
    }
1
    ptr::null_mut()
1
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_handle_get_title(handle: *const RsvgHandle) -> *mut libc::c_char {
    rsvg_return_val_if_fail! {
        rsvg_handle_get_title => ptr::null_mut();
1
        is_rsvg_handle(handle),
    }
1
    ptr::null_mut()
1
}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_init() {}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_term() {}
#[no_mangle]
1
pub unsafe extern "C" fn rsvg_cleanup() {}
/// Detects whether a `*const libc::c_char` is a path or a URI
///
/// `rsvg_handle_new_from_file()` takes a `filename` argument, and advertises
/// that it will detect either a file system path, or a proper URI.  It will then use
/// `gio::File::for_path()` or `gio::File::for_uri()` as appropriate.
///
/// This enum does the magic heuristics to figure this out.
///
/// The `from_os_str` version is for using the same logic on rsvg-convert's command-line
/// arguments: we want `rsvg-convert http://example.com/foo.svg` to go to a URL, not to a
/// local file with that name.
#[derive(Clone, Debug)]
pub enum PathOrUrl {
    Path(PathBuf),
    Url(Url),
}
impl PathOrUrl {
42
    unsafe fn new(s: *const libc::c_char) -> Result<PathOrUrl, String> {
42
        let cstr = CStr::from_ptr(s);
42
        if cstr.to_bytes().is_empty() {
1
            return Err("invalid empty filename".to_string());
        }
82
        Ok(cstr
            .to_str()
            .map_err(|_| ())
            .and_then(Self::try_from_str)
76
            .unwrap_or_else(|_| PathOrUrl::Path(PathBuf::from_glib_none(s))))
42
    }
68
    fn try_from_str(s: &str) -> Result<PathOrUrl, ()> {
68
        assert!(!s.is_empty());
136
        Url::parse(s).map_err(|_| ()).and_then(|url| {
11
            if url.origin().is_tuple() || url.scheme() == "file" {
5
                Ok(PathOrUrl::Url(url))
            } else {
4
                Ok(PathOrUrl::Path(url.to_file_path()?))
            }
9
        })
68
    }
28
    pub fn from_os_str(osstr: &OsStr) -> Result<PathOrUrl, String> {
28
        if osstr.is_empty() {
1
            return Err("invalid empty filename".to_string());
        }
54
        Ok(osstr
            .to_str()
            .ok_or(())
            .and_then(Self::try_from_str)
53
            .unwrap_or_else(|_| PathOrUrl::Path(PathBuf::from(osstr.to_os_string()))))
28
    }
54
    pub fn get_gfile(&self) -> gio::File {
54
        match *self {
52
            PathOrUrl::Path(ref p) => gio::File::for_path(p),
2
            PathOrUrl::Url(ref u) => gio::File::for_uri(u.as_str()),
        }
54
    }
27
    pub fn is_stdin_alias(&self) -> bool {
27
        match *self {
26
            PathOrUrl::Path(ref p) => matches!(p.to_str(), Some("-")),
1
            PathOrUrl::Url(ref u) => u.as_str() == "-",
        }
27
    }
}
impl fmt::Display for PathOrUrl {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            PathOrUrl::Path(ref p) => p.display().fmt(f),
            PathOrUrl::Url(ref u) => u.fmt(f),
        }
    }
}
13
fn check_cairo_context(cr: *mut cairo::ffi::cairo_t) -> Result<cairo::Context, RenderingError> {
13
    let status = unsafe { cairo::ffi::cairo_status(cr) };
13
    if status == cairo::ffi::STATUS_SUCCESS {
13
        Ok(unsafe { from_glib_none(cr) })
    } else {
        let status: cairo::Error = status.into();
        let msg = format!("cannot render on a cairo_t with a failure status (status={status:?})");
        rsvg_g_warning(&msg);
        Err(RenderingError::from(status))
    }
13
}
5
pub(crate) fn set_gerror(
    session: &Session,
    err: *mut *mut glib::ffi::GError,
    code: u32,
    msg: &str,
) {
    unsafe {
        // this is RSVG_ERROR_FAILED, the only error code available in RsvgError
5
        assert!(code == 0);
        // Log this, in case the calling program passes a NULL GError, so we can at least
        // diagnose things by asking for RSVG_LOG.
        //
        // See https://gitlab.gnome.org/GNOME/gtk/issues/2294 for an example of code that
        // passed a NULL GError and so we had no easy way to see what was wrong.
5
        rsvg_log!(session, "{}", msg);
5
        glib::ffi::g_set_error_literal(
            err,
5
            rsvg_error_quark(),
            code as libc::c_int,
5
            msg.to_glib_none().0,
5
        );
    }
5
}
3
#[derive(Debug, Eq, PartialEq, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "RsvgError")]
enum Error {
    #[enum_value(name = "RSVG_ERROR_FAILED", nick = "failed")]
    // Keep in sync with rsvg.h:RsvgError
    Failed = 0,
}
/// Used as a generic error to translate to glib::Error
///
/// This type implements `glib::error::ErrorDomain`, so it can be used
/// to obtain the error code while calling `glib::Error::new()`.  Unfortunately
/// the public librsvg API does not have detailed error codes yet, so we use
/// this single value as the only possible error code to return.
#[derive(Copy, Clone)]
struct RsvgError;
impl ErrorDomain for RsvgError {
9
    fn domain() -> glib::Quark {
9
        glib::Quark::from_str("rsvg-error-quark")
9
    }
    fn code(self) -> i32 {
        Error::Failed as i32
    }
    fn from(_code: i32) -> Option<Self> {
        // We don't have enough information from glib error codes
        Some(RsvgError)
    }
}
#[no_mangle]
9
pub extern "C" fn rsvg_error_quark() -> glib::ffi::GQuark {
9
    RsvgError::domain().into_glib()
9
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
2
    fn path_or_url_unix() {
        unsafe {
1
            match PathOrUrl::new(rsvg_c_str!("/foo/bar")).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("unix filename should be a PathOrUrl::Path"),
1
            }
1
            match PathOrUrl::new(rsvg_c_str!("foo/bar")).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("unix filename should be a PathOrUrl::Path"),
            }
        }
2
    }
    #[test]
2
    fn path_or_url_windows() {
        unsafe {
1
            match PathOrUrl::new(rsvg_c_str!("c:/foo/bar")).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("windows filename should be a PathOrUrl::Path"),
1
            }
1
            match PathOrUrl::new(rsvg_c_str!("C:/foo/bar")).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("windows filename should be a PathOrUrl::Path"),
1
            }
1
            match PathOrUrl::new(rsvg_c_str!("c:\\foo\\bar")).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("windows filename should be a PathOrUrl::Path"),
1
            }
1
            match PathOrUrl::new(rsvg_c_str!("C:\\foo\\bar")).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("windows filename should be a PathOrUrl::Path"),
            }
        }
2
    }
    #[test]
2
    fn path_or_url_unix_url() {
        unsafe {
1
            match PathOrUrl::new(rsvg_c_str!("file:///foo/bar")).unwrap() {
                PathOrUrl::Url(_) => (),
                _ => panic!("file:// unix filename should be a PathOrUrl::Url"),
            }
        }
2
    }
    #[test]
2
    fn path_or_url_windows_url() {
        unsafe {
1
            match PathOrUrl::new(rsvg_c_str!("file://c:/foo/bar")).unwrap() {
                PathOrUrl::Url(_) => (),
                _ => panic!("file:// windows filename should be a PathOrUrl::Url"),
1
            }
1
            match PathOrUrl::new(rsvg_c_str!("file://C:/foo/bar")).unwrap() {
                PathOrUrl::Url(_) => (),
                _ => panic!("file:// windows filename should be a PathOrUrl::Url"),
            }
        }
2
    }
    #[test]
2
    fn path_or_url_empty_str() {
        unsafe {
1
            assert!(PathOrUrl::new(rsvg_c_str!("")).is_err());
        }
1
        assert!(PathOrUrl::from_os_str(OsStr::new("")).is_err());
2
    }
    #[test]
2
    fn base_url_works() {
1
        let mut u = BaseUrl::default();
1
        assert!(u.get().is_none());
1
        assert_eq!(u.get_ptr(), ptr::null());
1
        u.set(Url::parse("file:///example.txt").unwrap());
1
        assert_eq!(u.get().unwrap().as_str(), "file:///example.txt");
        unsafe {
1
            let p = u.get_ptr();
1
            let cstr = CStr::from_ptr(p);
1
            assert_eq!(cstr.to_str().unwrap(), "file:///example.txt");
        }
2
    }
}