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::f64;
23
use std::ffi::{CStr, CString, OsStr};
24
use std::fmt;
25
use std::path::PathBuf;
26
use std::ptr;
27
use std::slice;
28
use std::str;
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
6
    fn from(e: T) -> RenderingError {
72
6
        RenderingError::RenderingError(e.into())
73
6
    }
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
463
#[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
55
    fn default() -> Self {
103
        Self::NONE
104
55
    }
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
44
    fn set_from_loading_result(
171
        &mut self,
172
        result: Result<SvgHandle, LoadingError>,
173
    ) -> Result<(), LoadingError> {
174
44
        match result {
175
43
            Ok(handle) => {
176
43
                *self = LoadState::ClosedOk { handle };
177
43
                Ok(())
178
43
            }
179

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

            
188
impl Default for LoadState {
189
55
    fn default() -> Self {
190
55
        Self::Start
191
55
    }
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
112
#[derive(Default)]
201
struct BaseUrl {
202
56
    inner: Option<BaseUrlInner>,
203
}
204

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

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

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

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

            
221
44
    fn get_gfile(&self) -> Option<gio::File> {
222
79
        self.get().map(|url| gio::File::for_uri(url.as_str()))
223
44
    }
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
8
    fn from(r: RsvgRectangle) -> cairo::Rectangle {
255
8
        cairo::Rectangle::new(r.x, r.y, r.width, r.height)
256
8
    }
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
798
    #[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
55
        pub(super) inner: RefCell<CHandleInner>,
273
41
        #[property(name = "base-uri", construct, set = Self::set_base_url, get = Self::get_base_url)]
274
55
        base_uri: PhantomData<Option<String>>,
275
5
        #[property(name = "width", get = |imp: &Self| imp.obj().get_dimensions_or_empty().width)]
276
55
        width: PhantomData<i32>,
277
5
        #[property(name = "height", get = |imp: &Self| imp.obj().get_dimensions_or_empty().height)]
278
55
        height: PhantomData<i32>,
279
5
        #[property(name = "em", get = |imp: &Self| imp.obj().get_dimensions_or_empty().em)]
280
55
        em: PhantomData<f64>,
281
5
        #[property(name = "ex", get = |imp: &Self| imp.obj().get_dimensions_or_empty().ex)]
282
55
        ex: PhantomData<f64>,
283
5
        #[property(name = "title", deprecated, get = |_| None)]
284
55
        title: PhantomData<Option<String>>,
285
5
        #[property(name = "desc", deprecated, get = |_| None)]
286
55
        desc: PhantomData<Option<String>>,
287
5
        #[property(name = "metadata", deprecated, get = |_| None)]
288
55
        metadata: PhantomData<Option<String>>,
289
55
        pub(super) load_state: RefCell<LoadState>,
290
55
        pub(super) session: Session,
291
    }
292

            
293
110
    #[derive(Default)]
294
    pub(super) struct CHandleInner {
295
55
        pub(super) dpi: Dpi,
296
55
        pub(super) handle_flags: HandleFlags,
297
55
        pub(super) base_url: BaseUrl,
298
55
        pub(super) cancellable: Option<gio::Cancellable>,
299
55
        pub(super) size_callback: SizeCallback,
300
55
        pub(super) is_testing: bool,
301
    }
302

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

            
307
        type Type = super::CHandle;
308

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

            
313
281
    #[glib::derived_properties]
314
    impl ObjectImpl for CHandle {}
315

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
586
38
        let state = imp.load_state.borrow_mut();
587
38
        let inner = imp.inner.borrow();
588

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

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

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

            
614
44
        load_state.set_from_loading_result(loader.read_stream(stream, base_file, cancellable))
615
44
    }
616

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

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

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

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

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

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

            
651
132
        Loader::new_with_session(session)
652
88
            .with_unlimited_size(inner.handle_flags.contains(HandleFlags::UNLIMITED))
653
88
            .keep_image_data(inner.handle_flags.contains(HandleFlags::KEEP_IMAGE_DATA))
654
44
    }
655

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

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

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

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

            
682
29
        inner.size_callback.start_loop();
683

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

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

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

            
704
29
        inner.size_callback.end_loop();
705

            
706
29
        res
707
29
    }
708

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

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

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

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

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

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

            
736
174
        let renderer = CairoRenderer::new(handle_ref)
737
116
            .with_dpi(inner.dpi.x(), inner.dpi.y())
738
58
            .test_mode(inner.is_testing);
739

            
740
58
        if let Some(ref cancellable) = inner.cancellable {
741
1
            renderer.with_cancellable(cancellable)
742
        } else {
743
57
            renderer
744
        }
745
58
    }
746

            
747
35
    fn get_geometry_sub(
748
        &self,
749
        id: Option<&str>,
750
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
751
35
        let handle = self.get_handle_ref()?;
752
35
        let renderer = self.make_renderer(&handle);
753

            
754
35
        Ok(renderer.legacy_layer_geometry(id)?)
755
35
    }
756

            
757
1
    fn set_stylesheet(&self, css: &str) -> Result<(), LoadingError> {
758
1
        match *self.imp().load_state.borrow_mut() {
759
1
            LoadState::ClosedOk { ref mut handle } => handle.set_stylesheet(css),
760

            
761
            _ => {
762
                rsvg_g_critical(
763
                    "handle must already be loaded in order to call \
764
                     rsvg_handle_set_stylesheet()",
765
                );
766
                Err(LoadingError::Other(String::from("API ordering")))
767
            }
768
        }
769
1
    }
770

            
771
1
    fn set_cancellable_for_rendering(&self, cancellable: Option<&gio::Cancellable>) {
772
1
        let mut inner = self.imp().inner.borrow_mut();
773
1
        inner.cancellable = cancellable.cloned();
774
1
    }
775

            
776
8
    fn render_cairo_sub(
777
        &self,
778
        cr: *mut cairo::ffi::cairo_t,
779
        id: Option<&str>,
780
    ) -> Result<(), RenderingError> {
781
8
        let dimensions = self.get_dimensions_sub(None)?;
782
8
        if dimensions.width == 0 || dimensions.height == 0 {
783
            // nothing to render
784
            return Ok(());
785
        }
786

            
787
8
        let viewport = cairo::Rectangle::new(
788
            0.0,
789
            0.0,
790
8
            f64::from(dimensions.width),
791
8
            f64::from(dimensions.height),
792
        );
793

            
794
8
        self.render_layer(cr, id, &viewport)
795
8
    }
796

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

            
801
5
        if dimensions.width == 0 || dimensions.height == 0 {
802
1
            return Ok(empty_pixbuf()?);
803
        }
804

            
805
4
        let surface = cairo::ImageSurface::create(
806
4
            cairo::Format::ARgb32,
807
4
            dimensions.width,
808
4
            dimensions.height,
809
        )?;
810

            
811
        {
812
4
            let cr = cairo::Context::new(&surface)?;
813
4
            let cr_raw = cr.to_raw_none();
814
9
            self.render_cairo_sub(cr_raw, id)?;
815
4
        }
816

            
817
4
        let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
818

            
819
4
        Ok(pixbuf_from_surface(&surface)?)
820
5
    }
821

            
822
3
    fn render_document(
823
        &self,
824
        cr: *mut cairo::ffi::cairo_t,
825
        viewport: &cairo::Rectangle,
826
    ) -> Result<(), RenderingError> {
827
3
        let cr = check_cairo_context(cr)?;
828

            
829
3
        let handle = self.get_handle_ref()?;
830

            
831
3
        let renderer = self.make_renderer(&handle);
832
6
        Ok(renderer.render_document(&cr, viewport)?)
833
3
    }
834

            
835
2
    fn get_geometry_for_layer(
836
        &self,
837
        id: Option<&str>,
838
        viewport: &cairo::Rectangle,
839
    ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> {
840
2
        let handle = self.get_handle_ref()?;
841
2
        let renderer = self.make_renderer(&handle);
842

            
843
2
        Ok(renderer
844
            .geometry_for_layer(id, viewport)
845
2
            .map(|(i, l)| (RsvgRectangle::from(i), RsvgRectangle::from(l)))?)
846
2
    }
847

            
848
10
    fn render_layer(
849
        &self,
850
        cr: *mut cairo::ffi::cairo_t,
851
        id: Option<&str>,
852
        viewport: &cairo::Rectangle,
853
    ) -> Result<(), RenderingError> {
854
10
        let cr = check_cairo_context(cr)?;
855

            
856
10
        let handle = self.get_handle_ref()?;
857

            
858
10
        let renderer = self.make_renderer(&handle);
859

            
860
20
        Ok(renderer.render_layer(&cr, id, viewport)?)
861
10
    }
862

            
863
2
    fn get_geometry_for_element(
864
        &self,
865
        id: Option<&str>,
866
    ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> {
867
2
        let handle = self.get_handle_ref()?;
868

            
869
2
        let renderer = self.make_renderer(&handle);
870

            
871
2
        Ok(renderer
872
            .geometry_for_element(id)
873
2
            .map(|(i, l)| (RsvgRectangle::from(i), RsvgRectangle::from(l)))?)
874
2
    }
875

            
876
1
    fn render_element(
877
        &self,
878
        cr: *mut cairo::ffi::cairo_t,
879
        id: Option<&str>,
880
        element_viewport: &cairo::Rectangle,
881
    ) -> Result<(), RenderingError> {
882
1
        let cr = check_cairo_context(cr)?;
883

            
884
1
        let handle = self.get_handle_ref()?;
885

            
886
1
        let renderer = self.make_renderer(&handle);
887

            
888
2
        Ok(renderer.render_element(&cr, id, element_viewport)?)
889
1
    }
890

            
891
2
    fn get_intrinsic_dimensions(&self) -> Result<IntrinsicDimensions, RenderingError> {
892
2
        let handle = self.get_handle_ref()?;
893
2
        let renderer = self.make_renderer(&handle);
894
2
        Ok(renderer.intrinsic_dimensions())
895
2
    }
896

            
897
3
    fn get_intrinsic_size_in_pixels(&self) -> Result<Option<(f64, f64)>, RenderingError> {
898
3
        let handle = self.get_handle_ref()?;
899
3
        let renderer = self.make_renderer(&handle);
900
3
        Ok(renderer.intrinsic_size_in_pixels())
901
3
    }
902

            
903
    fn set_testing(&self, is_testing: bool) {
904
        let mut inner = self.imp().inner.borrow_mut();
905
        inner.is_testing = is_testing;
906
    }
907
}
908

            
909
23270
fn is_rsvg_handle(obj: *const RsvgHandle) -> bool {
910
23270
    unsafe { instance_of::<CHandle>(obj as *const _) }
911
23270
}
912

            
913
4
fn is_input_stream(obj: *mut gio::ffi::GInputStream) -> bool {
914
4
    unsafe { instance_of::<gio::InputStream>(obj as *const _) }
915
4
}
916

            
917
36
fn is_gfile(obj: *const gio::ffi::GFile) -> bool {
918
36
    unsafe { instance_of::<gio::File>(obj as *const _) }
919
36
}
920

            
921
1
fn is_cancellable(obj: *mut gio::ffi::GCancellable) -> bool {
922
1
    unsafe { instance_of::<gio::Cancellable>(obj as *const _) }
923
1
}
924

            
925
23301
fn get_rust_handle(handle: *const RsvgHandle) -> CHandle {
926
23301
    let handle = unsafe { &*handle };
927
23301
    handle.imp().obj().to_owned()
928
23301
}
929

            
930
#[no_mangle]
931
10
pub unsafe extern "C" fn rsvg_handle_get_type() -> glib::ffi::GType {
932
10
    CHandle::static_type().into_glib()
933
10
}
934

            
935
#[no_mangle]
936
1
pub unsafe extern "C" fn rsvg_error_get_type() -> glib::ffi::GType {
937
1
    Error::static_type().into_glib()
938
1
}
939

            
940
#[no_mangle]
941
2
pub unsafe extern "C" fn rsvg_handle_flags_get_type() -> glib::ffi::GType {
942
2
    HandleFlags::static_type().into_glib()
943
2
}
944

            
945
#[no_mangle]
946
1
pub unsafe extern "C" fn rsvg_handle_set_base_uri(
947
    handle: *const RsvgHandle,
948
    uri: *const libc::c_char,
949
) {
950
    rsvg_return_if_fail! {
951
        rsvg_handle_set_base_uri;
952

            
953
1
        is_rsvg_handle(handle),
954
1
        !uri.is_null(),
955
    }
956

            
957
1
    let rhandle = get_rust_handle(handle);
958

            
959
1
    assert!(!uri.is_null());
960
1
    let uri: String = from_glib_none(uri);
961

            
962
1
    rhandle.set_base_uri(&*uri);
963
1
}
964

            
965
#[no_mangle]
966
1
pub unsafe extern "C" fn rsvg_handle_set_base_gfile(
967
    handle: *const RsvgHandle,
968
    raw_gfile: *mut gio::ffi::GFile,
969
) {
970
    rsvg_return_if_fail! {
971
        rsvg_handle_set_base_gfile;
972

            
973
1
        is_rsvg_handle(handle),
974
1
        is_gfile(raw_gfile),
975
    }
976

            
977
1
    let rhandle = get_rust_handle(handle);
978

            
979
1
    assert!(!raw_gfile.is_null());
980

            
981
1
    let file: gio::File = from_glib_none(raw_gfile);
982

            
983
1
    rhandle.set_base_gfile(&file);
984
1
}
985

            
986
#[no_mangle]
987
4
pub unsafe extern "C" fn rsvg_handle_get_base_uri(
988
    handle: *const RsvgHandle,
989
) -> *const libc::c_char {
990
    rsvg_return_val_if_fail! {
991
        rsvg_handle_get_base_uri => ptr::null();
992

            
993
4
        is_rsvg_handle(handle),
994
    }
995

            
996
4
    let rhandle = get_rust_handle(handle);
997

            
998
4
    rhandle.get_base_url_as_ptr()
999
4
}
#[no_mangle]
3
pub unsafe extern "C" fn rsvg_handle_set_dpi(handle: *const RsvgHandle, dpi: libc::c_double) {
    rsvg_return_if_fail! {
        rsvg_handle_set_dpi;
3
        is_rsvg_handle(handle),
    }
3
    let rhandle = get_rust_handle(handle);
3
    rhandle.set_dpi_x(dpi);
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 {
    fn into_gerror(
        self,
        session: &Session,
        error: *mut *mut glib::ffi::GError,
    ) -> glib::ffi::gboolean;
    fn into_g_warning(self) -> glib::ffi::gboolean;
}
impl IntoGError for Result<(), LoadingError> {
    /// Use this one when the public API actually uses a GError.
11
    fn into_gerror(
        self,
        session: &Session,
        error: *mut *mut glib::ffi::GError,
    ) -> glib::ffi::gboolean {
11
        match self {
9
            Ok(()) => true.into_glib(),
2
            Err(e) => {
2
                set_gerror(session, error, 0, &format!("{e}"));
2
                false.into_glib()
2
            }
        }
11
    }
    /// Use this one when the public API doesn't use a GError.
    fn into_g_warning(self) -> glib::ffi::gboolean {
        match self {
            Ok(()) => true.into_glib(),
            Err(e) => {
                rsvg_g_warning(&format!("{e}"));
                false.into_glib()
            }
        }
    }
}
impl IntoGError for Result<(), RenderingError> {
    /// Use this one when the public API actually uses a GError.
10
    fn into_gerror(
        self,
        session: &Session,
        error: *mut *mut glib::ffi::GError,
    ) -> glib::ffi::gboolean {
10
        match self {
7
            Ok(()) => true.into_glib(),
            Err(RenderingError::RenderingError(rsvg::RenderingError::Cancelled)) => {
                unsafe {
1
                    glib::ffi::g_set_error_literal(
                        error,
1
                        gio::ffi::g_io_error_quark(),
                        gio::ffi::G_IO_ERROR_CANCELLED,
1
                        "rendering cancelled".to_glib_none().0,
1
                    );
                }
1
                false.into_glib()
            }
2
            Err(e) => {
2
                set_gerror(session, error, 0, &format!("{e}"));
2
                false.into_glib()
2
            }
        }
10
    }
    /// Use this one when the public API doesn't use a GError.
4
    fn into_g_warning(self) -> glib::ffi::gboolean {
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]
18
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")]
12
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()
        }
    }
}
#[no_mangle]
#[cfg(feature = "pixbuf")]
5
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()
        }
    }
}
#[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]
16
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
        }
    }
11
}
#[no_mangle]
15
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
        }
    }
5
}
#[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]
41
pub unsafe extern "C" fn rsvg_handle_new_with_flags(flags: RsvgHandleFlags) -> *const RsvgHandle {
82
    let obj = glib::Object::builder::<CHandle>()
41
        .property("flags", HandleFlags::from_bits_truncate(flags))
        .build();
41
    obj.to_glib_full()
41
}
#[no_mangle]
99
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();
33
        !filename.is_null(),
33
        error.is_null() || (*error).is_null(),
    }
33
    let file = match PathOrUrl::new(filename) {
33
        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();
        }
    };
33
    rsvg_handle_new_from_gfile_sync(file.to_glib_none().0, 0, ptr::null_mut(), error)
66
}
#[no_mangle]
34
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();
34
        is_gfile(file),
34
        cancellable.is_null() || is_cancellable(cancellable),
34
        error.is_null() || (*error).is_null(),
    }
34
    let raw_handle = rsvg_handle_new_with_flags(flags);
34
    let rhandle = get_rust_handle(raw_handle);
34
    let session = rhandle.imp().session.clone();
34
    let file = gio::File::from_glib_none(file);
34
    rhandle.set_base_gfile(&file);
34
    let cancellable: Option<gio::Cancellable> = from_glib_none(cancellable);
34
    let res = file
34
        .read(cancellable.as_ref())
        .map_err(LoadingError::from)
34
        .and_then(|stream| rhandle.read_stream_sync(&stream.upcast(), cancellable.as_ref()));
34
    match res {
34
        Ok(()) => raw_handle,
        Err(e) => {
            set_gerror(&session, error, 0, &format!("{e}"));
            gobject_ffi::g_object_unref(raw_handle as *mut _);
            ptr::null_mut()
        }
    }
34
}
#[no_mangle]
7
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 {
    rsvg_return_val_if_fail! {
        rsvg_handle_new_from_stream_sync => ptr::null();
3
        is_input_stream(input_stream),
3
        base_file.is_null() || is_gfile(base_file),
3
        cancellable.is_null() || is_cancellable(cancellable),
3
        error.is_null() || (*error).is_null(),
    }
3
    let raw_handle = rsvg_handle_new_with_flags(flags);
3
    let rhandle = get_rust_handle(raw_handle);
3
    let session = rhandle.imp().session.clone();
3
    let base_file: Option<gio::File> = from_glib_none(base_file);
3
    if let Some(base_file) = base_file {
1
        rhandle.set_base_gfile(&base_file);
1
    }
2
    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 <= 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 <= 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]
3
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]
1
pub unsafe extern "C" fn rsvg_handle_set_cancellable_for_rendering(
    handle: *const RsvgHandle,
    cancellable: *mut gio::ffi::GCancellable,
) {
    rsvg_return_if_fail! {
        rsvg_handle_set_cancellable_for_rendering;
1
        is_rsvg_handle(handle),
1
        cancellable.is_null() || is_cancellable(cancellable),
    }
1
    let rhandle = get_rust_handle(handle);
1
    let cancellable: Option<gio::Cancellable> = from_glib_none(cancellable);
1
    rhandle.set_cancellable_for_rendering(cancellable.as_ref());
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())
1
        .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]
2
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();
2
        is_rsvg_handle(handle),
2
        !cr.is_null(),
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
        .render_layer(cr, id.as_deref(), &(*viewport).into())
        .into_gerror(&session, error)
2
}
#[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())
1
        .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 {
43
    unsafe fn new(s: *const libc::c_char) -> Result<PathOrUrl, String> {
43
        let cstr = CStr::from_ptr(s);
43
        if cstr.to_bytes().is_empty() {
1
            return Err("invalid empty filename".to_string());
        }
42
        Ok(cstr
            .to_str()
            .map_err(|_| ())
            .and_then(Self::try_from_str)
36
            .unwrap_or_else(|_| PathOrUrl::Path(PathBuf::from_glib_none(s))))
43
    }
69
    fn try_from_str(s: &str) -> Result<PathOrUrl, ()> {
69
        assert!(!s.is_empty());
138
        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
        })
69
    }
28
    pub fn from_os_str(osstr: &OsStr) -> Result<PathOrUrl, String> {
28
        if osstr.is_empty() {
1
            return Err("invalid empty filename".to_string());
        }
27
        Ok(osstr
            .to_str()
            .ok_or(())
            .and_then(Self::try_from_str)
26
            .unwrap_or_else(|_| PathOrUrl::Path(PathBuf::from(osstr.to_os_string()))))
28
    }
55
    pub fn get_gfile(&self) -> gio::File {
55
        match *self {
53
            PathOrUrl::Path(ref p) => gio::File::for_path(p),
2
            PathOrUrl::Url(ref u) => gio::File::for_uri(u.as_str()),
        }
55
    }
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),
        }
    }
}
14
fn check_cairo_context(cr: *mut cairo::ffi::cairo_t) -> Result<cairo::Context, RenderingError> {
14
    let status = unsafe { cairo::ffi::cairo_status(cr) };
14
    if status == cairo::ffi::STATUS_SUCCESS {
14
        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))
    }
14
}
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(c"/foo/bar".as_ptr()).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("unix filename should be a PathOrUrl::Path"),
1
            }
1
            match PathOrUrl::new(c"foo/bar".as_ptr()).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("unix filename should be a PathOrUrl::Path"),
            }
        }
2
    }
    #[test]
2
    fn path_or_url_windows() {
        unsafe {
1
            match PathOrUrl::new(c"c:/foo/bar".as_ptr()).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("windows filename should be a PathOrUrl::Path"),
1
            }
1
            match PathOrUrl::new(c"C:/foo/bar".as_ptr()).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("windows filename should be a PathOrUrl::Path"),
1
            }
1
            match PathOrUrl::new(c"c:\\foo\\bar".as_ptr()).unwrap() {
                PathOrUrl::Path(_) => (),
                _ => panic!("windows filename should be a PathOrUrl::Path"),
1
            }
1
            match PathOrUrl::new(c"C:\\foo\\bar".as_ptr()).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(c"file:///foo/bar".as_ptr()).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(c"file://c:/foo/bar".as_ptr()).unwrap() {
                PathOrUrl::Url(_) => (),
                _ => panic!("file:// windows filename should be a PathOrUrl::Url"),
1
            }
1
            match PathOrUrl::new(c"file://C:/foo/bar".as_ptr()).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(c"".as_ptr()).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
    }
}