1
//! Legacy C API for functions that render directly to a `GdkPixbuf`.
2
//!
3
//! This is the implementation of the `rsvg_pixbuf_*` family of functions.
4

            
5
#![cfg(feature = "pixbuf")]
6

            
7
use std::path::PathBuf;
8
use std::ptr;
9

            
10
use gdk_pixbuf::{Colorspace, Pixbuf};
11
use glib::translate::*;
12
use rgb::FromSlice;
13

            
14
use super::dpi::Dpi;
15
use super::handle::{checked_i32, set_gerror};
16
use super::sizing::LegacySize;
17

            
18
use rsvg::c_api_only::{Pixel, PixelOps, Session, SharedImageSurface, SurfaceType, ToPixel};
19
use rsvg::{CairoRenderer, Loader, RenderingError};
20

            
21
/// GdkPixbuf's endian-independent RGBA8 pixel layout.
22
type GdkPixbufRGBA = rgb::RGBA8;
23

            
24
/// Trait to convert pixels in various formats to RGBA, for GdkPixbuf.
25
///
26
/// GdkPixbuf unconditionally uses RGBA ordering regardless of endianness,
27
/// but we need to convert to it from Cairo's endian-dependent 0xaarrggbb.
28
trait ToGdkPixbufRGBA {
29
    fn to_pixbuf_rgba(&self) -> GdkPixbufRGBA;
30
}
31

            
32
impl ToGdkPixbufRGBA for Pixel {
33
    #[inline]
34
798400
    fn to_pixbuf_rgba(&self) -> GdkPixbufRGBA {
35
798400
        GdkPixbufRGBA {
36
798400
            r: self.r,
37
798400
            g: self.g,
38
798400
            b: self.b,
39
798400
            a: self.a,
40
        }
41
798400
    }
42
}
43

            
44
1
pub fn empty_pixbuf() -> Result<Pixbuf, RenderingError> {
45
    // GdkPixbuf does not allow zero-sized pixbufs, but Cairo allows zero-sized
46
    // surfaces.  In this case, return a 1-pixel transparent pixbuf.
47

            
48
1
    let pixbuf = Pixbuf::new(Colorspace::Rgb, true, 8, 1, 1)
49
        .ok_or_else(|| RenderingError::OutOfMemory(String::from("creating a Pixbuf")))?;
50
1
    pixbuf.put_pixel(0, 0, 0, 0, 0, 0);
51

            
52
1
    Ok(pixbuf)
53
1
}
54

            
55
9
pub fn pixbuf_from_surface(surface: &SharedImageSurface) -> Result<Pixbuf, RenderingError> {
56
9
    let width = surface.width();
57
9
    let height = surface.height();
58

            
59
9
    let pixbuf = Pixbuf::new(Colorspace::Rgb, true, 8, width, height)
60
        .ok_or_else(|| RenderingError::OutOfMemory(String::from("creating a Pixbuf")))?;
61

            
62
9
    assert!(pixbuf.colorspace() == Colorspace::Rgb);
63
9
    assert!(pixbuf.bits_per_sample() == 8);
64
9
    assert!(pixbuf.n_channels() == 4);
65

            
66
9
    let pixbuf_data = unsafe { pixbuf.pixels() };
67
9
    let stride = pixbuf.rowstride() as usize;
68

            
69
    // We use chunks_mut(), not chunks_exact_mut(), because gdk-pixbuf tends
70
    // to make the last row *not* have the full stride (i.e. it is
71
    // only as wide as the pixels in that row).
72
18
    pixbuf_data
73
        .chunks_mut(stride)
74
9
        .take(height as usize)
75
4340
        .map(|row| row.as_rgba_mut())
76
9
        .zip(surface.rows())
77
4340
        .flat_map(|(dest_row, src_row)| src_row.iter().zip(dest_row.iter_mut()))
78
798400
        .for_each(|(src, dest)| *dest = src.to_pixel().unpremultiply().to_pixbuf_rgba());
79

            
80
9
    Ok(pixbuf)
81
9
}
82

            
83
enum SizeKind {
84
    Zoom,
85
    WidthHeight,
86
    WidthHeightMax,
87
    ZoomMax,
88
}
89

            
90
struct SizeMode {
91
    kind: SizeKind,
92
    x_zoom: f64,
93
    y_zoom: f64,
94
    width: i32,
95
    height: i32,
96
}
97

            
98
6
fn get_final_size(in_width: f64, in_height: f64, size_mode: &SizeMode) -> (f64, f64) {
99
6
    if in_width == 0.0 || in_height == 0.0 {
100
        return (0.0, 0.0);
101
    }
102

            
103
    let mut out_width;
104
    let mut out_height;
105

            
106
6
    match size_mode.kind {
107
2
        SizeKind::Zoom => {
108
2
            out_width = size_mode.x_zoom * in_width;
109
2
            out_height = size_mode.y_zoom * in_height;
110
        }
111

            
112
        SizeKind::ZoomMax => {
113
1
            out_width = size_mode.x_zoom * in_width;
114
1
            out_height = size_mode.y_zoom * in_height;
115

            
116
1
            if out_width > f64::from(size_mode.width) || out_height > f64::from(size_mode.height) {
117
1
                let zoom_x = f64::from(size_mode.width) / out_width;
118
1
                let zoom_y = f64::from(size_mode.height) / out_height;
119
1
                let zoom = zoom_x.min(zoom_y);
120

            
121
1
                out_width *= zoom;
122
1
                out_height *= zoom;
123
            }
124
        }
125

            
126
        SizeKind::WidthHeightMax => {
127
1
            let zoom_x = f64::from(size_mode.width) / in_width;
128
1
            let zoom_y = f64::from(size_mode.height) / in_height;
129

            
130
1
            let zoom = zoom_x.min(zoom_y);
131

            
132
1
            out_width = zoom * in_width;
133
1
            out_height = zoom * in_height;
134
1
        }
135

            
136
        SizeKind::WidthHeight => {
137
2
            if size_mode.width != -1 {
138
1
                out_width = f64::from(size_mode.width);
139
            } else {
140
1
                out_width = in_width;
141
            }
142

            
143
2
            if size_mode.height != -1 {
144
1
                out_height = f64::from(size_mode.height);
145
            } else {
146
1
                out_height = in_height;
147
            }
148
        }
149
    }
150

            
151
6
    (out_width, out_height)
152
6
}
153

            
154
6
pub fn render_to_pixbuf_at_size(
155
    renderer: &CairoRenderer<'_>,
156
    document_width: f64,
157
    document_height: f64,
158
    desired_width: f64,
159
    desired_height: f64,
160
) -> Result<Pixbuf, RenderingError> {
161
6
    if desired_width == 0.0
162
6
        || desired_height == 0.0
163
6
        || document_width == 0.0
164
6
        || document_height == 0.0
165
    {
166
        return empty_pixbuf();
167
    }
168

            
169
6
    let surface = cairo::ImageSurface::create(
170
6
        cairo::Format::ARgb32,
171
6
        checked_i32(desired_width.ceil())?,
172
6
        checked_i32(desired_height.ceil())?,
173
1
    )?;
174

            
175
    {
176
5
        let cr = cairo::Context::new(&surface)?;
177
5
        cr.scale(
178
5
            desired_width / document_width,
179
5
            desired_height / document_height,
180
        );
181

            
182
5
        let viewport = cairo::Rectangle::new(0.0, 0.0, document_width, document_height);
183

            
184
        // We do it with a cr transform so we can scale non-proportionally.
185
11
        renderer.render_document(&cr, &viewport)?;
186
5
    }
187

            
188
5
    let shared_surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
189

            
190
5
    pixbuf_from_surface(&shared_surface)
191
6
}
192

            
193
6
unsafe fn pixbuf_from_file_with_size_mode(
194
    filename: *const libc::c_char,
195
    size_mode: &SizeMode,
196
    error: *mut *mut glib::ffi::GError,
197
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
198
6
    let path = PathBuf::from_glib_none(filename);
199

            
200
6
    let session = Session::default();
201

            
202
6
    let handle = match Loader::new_with_session(session.clone()).read_path(path) {
203
6
        Ok(handle) => handle,
204
        Err(e) => {
205
            set_gerror(&session, error, 0, &format!("{e}"));
206
            return ptr::null_mut();
207
        }
208
    };
209

            
210
6
    let dpi = Dpi::default();
211
6
    let renderer = CairoRenderer::new(&handle).with_dpi(dpi.x(), dpi.y());
212

            
213
6
    let (document_width, document_height) = match renderer.legacy_document_size() {
214
6
        Ok(dim) => dim,
215
        Err(e) => {
216
            set_gerror(&session, error, 0, &format!("{e}"));
217
            return ptr::null_mut();
218
        }
219
    };
220

            
221
6
    let (desired_width, desired_height) =
222
6
        get_final_size(document_width, document_height, size_mode);
223

            
224
6
    render_to_pixbuf_at_size(
225
        &renderer,
226
        document_width,
227
        document_height,
228
        desired_width,
229
        desired_height,
230
    )
231
5
    .map(|pixbuf| pixbuf.to_glib_full())
232
7
    .unwrap_or_else(|e| {
233
2
        set_gerror(&session, error, 0, &format!("{e}"));
234
1
        ptr::null_mut()
235
1
    })
236
6
}
237

            
238
#[no_mangle]
239
1
pub unsafe extern "C" fn rsvg_pixbuf_from_file(
240
    filename: *const libc::c_char,
241
    error: *mut *mut glib::ffi::GError,
242
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
243
    rsvg_return_val_if_fail! {
244
        rsvg_pixbuf_from_file => ptr::null_mut();
245

            
246
1
        !filename.is_null(),
247
1
        error.is_null() || (*error).is_null(),
248
    }
249

            
250
1
    pixbuf_from_file_with_size_mode(
251
        filename,
252
        &SizeMode {
253
            kind: SizeKind::WidthHeight,
254
            x_zoom: 0.0,
255
            y_zoom: 0.0,
256
            width: -1,
257
            height: -1,
258
        },
259
        error,
260
    )
261
1
}
262

            
263
#[no_mangle]
264
1
pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_size(
265
    filename: *const libc::c_char,
266
    width: libc::c_int,
267
    height: libc::c_int,
268
    error: *mut *mut glib::ffi::GError,
269
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
270
    rsvg_return_val_if_fail! {
271
        rsvg_pixbuf_from_file_at_size => ptr::null_mut();
272

            
273
1
        !filename.is_null(),
274
1
        (width >= 1 && height >= 1) || (width == -1 && height == -1),
275
1
        error.is_null() || (*error).is_null(),
276
    }
277

            
278
1
    pixbuf_from_file_with_size_mode(
279
        filename,
280
1
        &SizeMode {
281
1
            kind: SizeKind::WidthHeight,
282
            x_zoom: 0.0,
283
            y_zoom: 0.0,
284
            width,
285
            height,
286
        },
287
        error,
288
    )
289
1
}
290

            
291
#[no_mangle]
292
2
pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_zoom(
293
    filename: *const libc::c_char,
294
    x_zoom: libc::c_double,
295
    y_zoom: libc::c_double,
296
    error: *mut *mut glib::ffi::GError,
297
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
298
    rsvg_return_val_if_fail! {
299
        rsvg_pixbuf_from_file_at_zoom => ptr::null_mut();
300

            
301
2
        !filename.is_null(),
302
2
        x_zoom > 0.0 && y_zoom > 0.0,
303
2
        error.is_null() || (*error).is_null(),
304
    }
305

            
306
2
    pixbuf_from_file_with_size_mode(
307
        filename,
308
2
        &SizeMode {
309
2
            kind: SizeKind::Zoom,
310
            x_zoom,
311
            y_zoom,
312
            width: 0,
313
            height: 0,
314
        },
315
        error,
316
    )
317
2
}
318

            
319
#[no_mangle]
320
1
pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_zoom_with_max(
321
    filename: *const libc::c_char,
322
    x_zoom: libc::c_double,
323
    y_zoom: libc::c_double,
324
    max_width: libc::c_int,
325
    max_height: libc::c_int,
326
    error: *mut *mut glib::ffi::GError,
327
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
328
    rsvg_return_val_if_fail! {
329
        rsvg_pixbuf_from_file_at_zoom_with_max => ptr::null_mut();
330

            
331
1
        !filename.is_null(),
332
1
        x_zoom > 0.0 && y_zoom > 0.0,
333
1
        max_width >= 1 && max_height >= 1,
334
1
        error.is_null() || (*error).is_null(),
335
    }
336

            
337
1
    pixbuf_from_file_with_size_mode(
338
        filename,
339
1
        &SizeMode {
340
1
            kind: SizeKind::ZoomMax,
341
            x_zoom,
342
            y_zoom,
343
            width: max_width,
344
            height: max_height,
345
        },
346
        error,
347
    )
348
1
}
349

            
350
#[no_mangle]
351
1
pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_max_size(
352
    filename: *const libc::c_char,
353
    max_width: libc::c_int,
354
    max_height: libc::c_int,
355
    error: *mut *mut glib::ffi::GError,
356
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
357
    rsvg_return_val_if_fail! {
358
        rsvg_pixbuf_from_file_at_max_size => ptr::null_mut();
359

            
360
1
        !filename.is_null(),
361
1
        max_width >= 1 && max_height >= 1,
362
1
        error.is_null() || (*error).is_null(),
363
    }
364

            
365
1
    pixbuf_from_file_with_size_mode(
366
        filename,
367
1
        &SizeMode {
368
1
            kind: SizeKind::WidthHeightMax,
369
            x_zoom: 0.0,
370
            y_zoom: 0.0,
371
            width: max_width,
372
            height: max_height,
373
        },
374
        error,
375
    )
376
1
}