1
1
use std::ptr::null_mut;
2

            
3
use gdk_pixbuf::ffi::{
4
    GdkPixbufFormat, GdkPixbufModule, GdkPixbufModulePattern, GdkPixbufModulePreparedFunc,
5
    GdkPixbufModuleSizeFunc, GdkPixbufModuleUpdatedFunc, GDK_PIXBUF_FORMAT_SCALABLE,
6
    GDK_PIXBUF_FORMAT_THREADSAFE,
7
};
8

            
9
use std::ffi::{c_char, c_int, c_uint};
10

            
11
use glib::ffi::{gboolean, gpointer, GDestroyNotify, GError};
12
use glib::prelude::*;
13
use glib::translate::*;
14
use glib::Bytes;
15

            
16
use gio::ffi::{GCancellable, GFile, GInputStream};
17
use gio::prelude::*;
18
use gio::MemoryInputStream;
19

            
20
use glib::gstr;
21

            
22
type RSvgHandle = glib::gobject_ffi::GObject;
23

            
24
type RsvgSizeFunc = Option<
25
    unsafe extern "C" fn(inout_width: *mut c_int, inout_height: *mut c_int, user_data: gpointer),
26
>;
27

            
28
#[link(name = "rsvg-2")]
29
extern "C" {
30
    fn rsvg_handle_new_from_stream_sync(
31
        input_stream: *mut GInputStream,
32
        base_file: *mut GFile,
33
        flags: u32,
34
        cancellable: *mut GCancellable,
35
        error: *mut *mut GError,
36
    ) -> *mut RSvgHandle;
37

            
38
    fn rsvg_handle_get_pixbuf_and_error(
39
        handle: *mut RSvgHandle,
40
        error: *mut *mut GError,
41
    ) -> *mut gdk_pixbuf::ffi::GdkPixbuf;
42

            
43
    fn rsvg_handle_set_size_callback(
44
        handle: *mut RSvgHandle,
45
        size_func: RsvgSizeFunc,
46
        user_data: gpointer,
47
        destroy_notify: GDestroyNotify,
48
    );
49
}
50

            
51
struct SvgContext {
52
    size_func: GdkPixbufModuleSizeFunc,
53
    prep_func: GdkPixbufModulePreparedFunc,
54
    update_func: GdkPixbufModuleUpdatedFunc,
55
    user_data: gpointer,
56
    stream: MemoryInputStream,
57
}
58

            
59
1
unsafe extern "C" fn begin_load(
60
    size_func: GdkPixbufModuleSizeFunc,
61
    prep_func: GdkPixbufModulePreparedFunc,
62
    update_func: GdkPixbufModuleUpdatedFunc,
63
    user_data: gpointer,
64
    error: *mut *mut GError,
65
) -> gpointer {
66
1
    if !error.is_null() {
67
        *error = null_mut();
68
    }
69

            
70
1
    let stream = MemoryInputStream::new();
71
1
    let ctx = Box::new(SvgContext {
72
        size_func,
73
        prep_func,
74
        update_func,
75
        user_data,
76
        stream,
77
    });
78

            
79
1
    Box::into_raw(ctx) as gpointer
80
1
}
81

            
82
1
unsafe extern "C" fn load_increment(
83
    user_data: gpointer,
84
    buffer: *const u8,
85
    size: c_uint,
86
    error: *mut *mut GError,
87
) -> gboolean {
88
1
    if !error.is_null() {
89
        *error = null_mut();
90
    }
91

            
92
1
    let ctx = user_data as *mut SvgContext;
93

            
94
1
    let data = std::slice::from_raw_parts(buffer, size as usize);
95
1
    (*ctx).stream.add_bytes(&Bytes::from(data));
96
1
    true.into_glib()
97
1
}
98

            
99
1
unsafe extern "C" fn stop_load(user_data: gpointer, error: *mut *mut GError) -> gboolean {
100
1
    let ctx = Box::from_raw(user_data as *mut SvgContext);
101
1
    if !error.is_null() {
102
        *error = null_mut();
103
    }
104

            
105
1
    let mut local_error = null_mut::<GError>();
106

            
107
    let handle = {
108
1
        let raw_handle = rsvg_handle_new_from_stream_sync(
109
1
            ctx.stream.upcast_ref::<gio::InputStream>().to_glib_none().0,
110
1
            null_mut(), // base_file
111
            0,
112
1
            null_mut(), // cancellable
113
            &mut local_error,
114
        );
115
1
        if !local_error.is_null() {
116
            if !error.is_null() {
117
                *error = local_error;
118
            }
119
            return false.into_glib();
120
        }
121

            
122
1
        glib::Object::from_glib_full(raw_handle)
123
    };
124

            
125
1
    rsvg_handle_set_size_callback(handle.as_ptr(), ctx.size_func, ctx.user_data, None);
126

            
127
    let pixbuf = {
128
1
        let p = rsvg_handle_get_pixbuf_and_error(handle.as_ptr(), &mut local_error);
129
1
        if !local_error.is_null() {
130
            if !error.is_null() {
131
                *error = local_error;
132
            }
133
            return false.into_glib();
134
        }
135

            
136
1
        gdk_pixbuf::Pixbuf::from_glib_full(p)
137
    };
138

            
139
1
    let w = pixbuf.width();
140
1
    let h = pixbuf.height();
141

            
142
1
    if let Some(prep_func) = ctx.prep_func {
143
1
        prep_func(pixbuf.to_glib_none().0, null_mut(), ctx.user_data);
144
    }
145
1
    if let Some(update_func) = ctx.update_func {
146
        update_func(pixbuf.to_glib_none().0, 0, 0, w, h, ctx.user_data);
147
    }
148

            
149
1
    true.into_glib()
150
1
}
151

            
152
#[no_mangle]
153
extern "C" fn fill_vtable(module: &mut GdkPixbufModule) {
154
    module.begin_load = Some(begin_load);
155
    module.stop_load = Some(stop_load);
156
    module.load_increment = Some(load_increment);
157
}
158

            
159
const SIGNATURE: [GdkPixbufModulePattern; 3] = [
160
    GdkPixbufModulePattern {
161
        prefix: gstr!(" <svg").as_ptr() as *mut c_char,
162
        mask: gstr!("*    ").as_ptr() as *mut c_char,
163
        relevance: 100,
164
    },
165
    GdkPixbufModulePattern {
166
        prefix: gstr!(" <!DOCTYPE svg").as_ptr() as *mut c_char,
167
        mask: gstr!("*             ").as_ptr() as *mut c_char,
168
        relevance: 100,
169
    },
170
    GdkPixbufModulePattern {
171
        prefix: null_mut(),
172
        mask: null_mut(),
173
        relevance: 0,
174
    },
175
];
176

            
177
const MIME_TYPES: [*const c_char; 7] = [
178
    gstr!("image/svg+xml").as_ptr(),
179
    gstr!("image/svg").as_ptr(),
180
    gstr!("image/svg-xml").as_ptr(),
181
    gstr!("image/vnd.adobe.svg+xml").as_ptr(),
182
    gstr!("text/xml-svg").as_ptr(),
183
    gstr!("image/svg+xml-compressed").as_ptr(),
184
    std::ptr::null(),
185
];
186

            
187
const EXTENSIONS: [*const c_char; 4] = [
188
    gstr!("svg").as_ptr(),
189
    gstr!("svgz").as_ptr(),
190
    gstr!("svg.gz").as_ptr(),
191
    std::ptr::null(),
192
];
193

            
194
#[no_mangle]
195
2
extern "C" fn fill_info(info: &mut GdkPixbufFormat) {
196
2
    info.name = gstr!("svg").as_ptr() as *mut c_char;
197
2
    info.signature = SIGNATURE.as_ptr() as *mut GdkPixbufModulePattern;
198
2
    info.description = gstr!("Scalable Vector Graphics").as_ptr() as *mut c_char; //TODO: Gettext this
199
2
    info.mime_types = MIME_TYPES.as_ptr() as *mut *mut c_char;
200
2
    info.extensions = EXTENSIONS.as_ptr() as *mut *mut c_char;
201
2
    info.flags = GDK_PIXBUF_FORMAT_SCALABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
202
2
    info.license = gstr!("LGPL").as_ptr() as *mut c_char;
203
2
}
204

            
205
#[cfg(test)]
206
mod tests {
207
    use gdk_pixbuf::ffi::{
208
        GdkPixbufFormat, GDK_PIXBUF_FORMAT_SCALABLE, GDK_PIXBUF_FORMAT_THREADSAFE,
209
    };
210
    use glib::translate::IntoGlib;
211

            
212
    use crate::{EXTENSIONS, MIME_TYPES};
213
    use std::ffi::c_char;
214
    use std::ptr::null_mut;
215

            
216
2
    fn pb_format_new() -> GdkPixbufFormat {
217
2
        let mut info = super::GdkPixbufFormat {
218
2
            name: null_mut(),
219
2
            signature: null_mut(),
220
2
            description: null_mut(),
221
2
            mime_types: null_mut(),
222
2
            extensions: null_mut(),
223
            flags: 0,
224
2
            license: null_mut(),
225
2
            disabled: false.into_glib(),
226
2
            domain: null_mut(),
227
        };
228

            
229
2
        super::fill_info(&mut info);
230
2
        info
231
2
    }
232

            
233
    #[test]
234
2
    fn fill_info() {
235
1
        let info = pb_format_new();
236

            
237
1
        assert_ne!(info.name, null_mut());
238
1
        assert_ne!(info.signature, null_mut());
239
1
        assert_ne!(info.description, null_mut());
240
1
        assert_ne!(info.mime_types, null_mut());
241
1
        assert_ne!(info.extensions, null_mut());
242
1
        assert_eq!(
243
            info.flags,
244
            GDK_PIXBUF_FORMAT_SCALABLE | GDK_PIXBUF_FORMAT_THREADSAFE
245
        );
246
1
        assert_ne!(info.license, null_mut());
247
2
    }
248

            
249
2
    fn check_null_terminated_arr_cstrings(arr: &[*const c_char]) {
250
2
        let n_strings = arr
251
            .iter()
252
11
            .filter(|e| !e.is_null())
253
9
            .map(|e| {
254
9
                if !e.is_null() {
255
9
                    assert!(!unsafe { std::ffi::CStr::from_ptr(*e) }.is_empty())
256
                }
257
9
            })
258
            .count(); // Count all non_null items
259

            
260
        // Ensure last item is null and is the only null item
261
2
        assert_eq!(n_strings, arr.len() - 1);
262
2
        assert!(arr.last().unwrap().is_null());
263
2
    }
264

            
265
    #[test]
266
2
    fn extensions_bounds() {
267
1
        check_null_terminated_arr_cstrings(&EXTENSIONS);
268
2
    }
269

            
270
    #[test]
271
2
    fn mime_bounds() {
272
1
        check_null_terminated_arr_cstrings(&MIME_TYPES)
273
2
    }
274

            
275
    #[test]
276
2
    fn signature() {
277
1
        let info = pb_format_new();
278
        unsafe {
279
3
            for i in 0..2 {
280
2
                let ptr = info.signature.offset(i);
281
2
                if i == 2 {
282
                    assert!((*ptr).prefix.is_null());
283
                    continue;
284
                } else {
285
2
                    assert!(!(*ptr).prefix.is_null());
286
2
                    if (*ptr).mask != null_mut() {
287
                        // Mask can be null
288
2
                        let prefix = std::ffi::CStr::from_ptr((*ptr).prefix).to_bytes();
289
2
                        let mask = std::ffi::CStr::from_ptr((*ptr).mask).to_bytes();
290
2
                        assert_eq!(prefix.len(), mask.len());
291
                    }
292
                    // Relevance must be 0 to 100
293
2
                    assert!((*ptr).relevance >= 0);
294
2
                    assert!((*ptr).relevance <= 100);
295
                }
296
            }
297
        }
298
2
    }
299

            
300
    const SVG_DATA: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?>
301
                                    <svg
302
                                    width="100px"
303
                                    height="150px"
304
                                    viewBox="0 0 26.458333 26.458333"
305
                                    version="1.1"
306
                                    id="svg5"
307
                                    xmlns="http://www.w3.org/2000/svg"
308
                                    xmlns:svg="http://www.w3.org/2000/svg">
309
                                    <rect
310
                                        style="fill:#aa1144;stroke-width:0.0344347"
311
                                        width="26.458332"
312
                                        height="39.6875"
313
                                        x="4.691162e-07"
314
                                        y="-6.6145835"
315
                                        id="rect2" />
316
                                    </svg>
317
    "#;
318

            
319
    #[test]
320
2
    fn minimal_svg() {
321
1
        unsafe extern "C" fn prep_cb(
322
            pb: *mut gdk_pixbuf::ffi::GdkPixbuf,
323
            pba: *mut gdk_pixbuf::ffi::GdkPixbufAnimation,
324
            user_data: *mut std::ffi::c_void,
325
        ) {
326
1
            assert_eq!(user_data, null_mut());
327
1
            assert_eq!(pba, null_mut());
328

            
329
1
            let w = gdk_pixbuf::ffi::gdk_pixbuf_get_width(pb);
330
1
            let h = gdk_pixbuf::ffi::gdk_pixbuf_get_height(pb);
331
1
            let stride = gdk_pixbuf::ffi::gdk_pixbuf_get_rowstride(pb);
332
1
            assert_eq!(w, 100);
333
1
            assert_eq!(h, 150);
334

            
335
1
            let pixels = gdk_pixbuf::ffi::gdk_pixbuf_get_pixels(pb);
336

            
337
            // Upper left pixel #aa1144ff
338
1
            assert_eq!(*pixels, 0xaa);
339
1
            assert_eq!(*pixels.offset(1), 0x11);
340
1
            assert_eq!(*pixels.offset(2), 0x44);
341
1
            assert_eq!(*pixels.offset(3), 0xff);
342

            
343
            // Bottom left pixel
344
1
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize), 0xaa);
345
1
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 1), 0x11);
346
1
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 2), 0x44);
347
1
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 3), 0xff);
348

            
349
            // Bottom right pixel
350
1
            assert_eq!(
351
1
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4),
352
                0xaa
353
            );
354
1
            assert_eq!(
355
1
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 1),
356
                0x11
357
            );
358
1
            assert_eq!(
359
1
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 2),
360
                0x44
361
            );
362
1
            assert_eq!(
363
1
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 3),
364
                0xff
365
            );
366
1
        }
367
        unsafe {
368
1
            let ctx = crate::begin_load(None, Some(prep_cb), None, null_mut(), null_mut());
369
1
            assert_ne!(ctx, null_mut());
370

            
371
            let inc =
372
1
                crate::load_increment(ctx, SVG_DATA.as_ptr(), SVG_DATA.len() as u32, null_mut());
373
1
            assert_ne!(inc, 0);
374

            
375
1
            crate::stop_load(ctx, null_mut());
376
        }
377
2
    }
378
}