Skip to main content

rs_vips/
target.rs

1// (c) Copyright 2019-2025 OLX
2// (c) Copyright 2025 mrdkprj
3use crate::{
4    bindings::{self, gint64},
5    connection::VipsConnection,
6    error::Error,
7    utils::{new_c_string, path_to_cstring, result_cond},
8    Result,
9};
10use std::{
11    ffi::{c_int, c_void},
12    io::{Read, Seek, SeekFrom, Write},
13    path::Path,
14};
15
16/// A unified interface for writing data to various output destinations
17#[derive(Debug, Clone)]
18pub struct VipsTarget {
19    pub(crate) ctx: *mut bindings::VipsTarget,
20}
21
22pub trait Writer: Write + Read + Seek + Send + 'static {}
23impl<T: Write + Read + Seek + Send + 'static> Writer for T {}
24struct TargetContext {
25    writer: Box<dyn Writer>,
26}
27
28impl VipsTarget {
29    /// Create a target from a writer.
30    pub fn new_to_writer<W: Writer>(output: W) -> Result<VipsTarget> {
31        unsafe {
32            let target = bindings::vips_target_custom_new();
33
34            if target.is_null() {
35                return Err(
36                    Error::InitializationError("Cannot create VipsTargetCustom".to_string()),
37                );
38            }
39
40            let context = Box::new(
41                TargetContext {
42                    writer: Box::new(output),
43                },
44            );
45
46            let user_data = Box::into_raw(context) as *mut c_void;
47
48            bindings::g_signal_connect_data(
49                target as *mut c_void,
50                c"write".as_ptr(),
51                Some(
52                    std::mem::transmute::<
53                        unsafe extern "C" fn(
54                            target: *mut bindings::VipsTargetCustom,
55                            data: *const c_void,
56                            length: bindings::gint64,
57                            user_data: *mut c_void,
58                        ) -> bindings::gint64,
59                        unsafe extern "C" fn(),
60                    >(on_write),
61                ),
62                user_data,
63                None,
64                0,
65            );
66
67            bindings::g_signal_connect_data(
68                target as *mut c_void,
69                c"read".as_ptr(),
70                Some(
71                    std::mem::transmute::<
72                        unsafe extern "C" fn(
73                            target: *mut bindings::VipsTargetCustom,
74                            buffer: *mut c_void,
75                            length: gint64,
76                            user_data: *mut c_void,
77                        ) -> gint64,
78                        unsafe extern "C" fn(),
79                    >(on_read),
80                ),
81                user_data,
82                None,
83                0,
84            );
85
86            bindings::g_signal_connect_data(
87                target as *mut c_void,
88                c"seek".as_ptr(),
89                Some(
90                    std::mem::transmute::<
91                        unsafe extern "C" fn(
92                            target: *mut bindings::VipsTargetCustom,
93                            offset: gint64,
94                            whence: c_int,
95                            user_data: *mut c_void,
96                        ) -> gint64,
97                        unsafe extern "C" fn(),
98                    >(on_seek),
99                ),
100                user_data,
101                None,
102                0,
103            );
104
105            bindings::g_signal_connect_data(
106                target as *mut c_void,
107                c"end".as_ptr(),
108                Some(
109                    std::mem::transmute::<
110                        unsafe extern "C" fn(
111                            target: *mut bindings::VipsTargetCustom,
112                            user_data: *mut c_void,
113                        ) -> c_int,
114                        unsafe extern "C" fn(),
115                    >(on_end),
116                ),
117                user_data,
118                None,
119                0,
120            );
121
122            Ok(
123                VipsTarget {
124                    ctx: target as *mut bindings::VipsTarget,
125                },
126            )
127        }
128    }
129
130    /// Create a target attached to a file descriptor. descriptor is kept open until the target is finalized.
131    pub fn new_to_descriptor(descriptor: i32) -> Result<VipsTarget> {
132        unsafe {
133            let res = bindings::vips_target_new_to_descriptor(descriptor);
134            result_cond(
135                !res.is_null(),
136                VipsTarget {
137                    ctx: res,
138                },
139                Error::InitializationError(
140                    "Could not initialise VipsTarget from descriptor".to_string(),
141                ),
142            )
143        }
144    }
145
146    /// Create a target attached to a file.
147    pub fn new_to_file<P: AsRef<Path>>(filename: P) -> Result<VipsTarget> {
148        unsafe {
149            let filename_c_str = path_to_cstring(filename)?;
150            let res = bindings::vips_target_new_to_file(filename_c_str.as_ptr());
151            result_cond(
152                !res.is_null(),
153                VipsTarget {
154                    ctx: res,
155                },
156                Error::InitializationError("Could not initialise VipsTarget from file".to_string()),
157            )
158        }
159    }
160
161    /// Create a target which will write to a memory area. Read from blob to get memory.
162    pub fn new_to_memory() -> Result<VipsTarget> {
163        unsafe {
164            let res = bindings::vips_target_new_to_memory();
165            result_cond(
166                !res.is_null(),
167                VipsTarget {
168                    ctx: res,
169                },
170                Error::InitializationError(
171                    "Could not initialise VipsTarget from memory".to_string(),
172                ),
173            )
174        }
175    }
176
177    /// Write a single character ch to target.
178    pub fn putc(&mut self, ch: char) -> Result<()> {
179        unsafe {
180            let res = bindings::vips_target_putc(
181                self.ctx,
182                ch as c_int,
183            );
184            result_cond(
185                res != -1,
186                (),
187                Error::OperationError("Could not write to target".to_string()),
188            )
189        }
190    }
191
192    /// Write buffer to the output.
193    pub fn write(&mut self, buffer: &[u8]) -> Result<()> {
194        unsafe {
195            let res = bindings::vips_target_write(
196                self.ctx,
197                buffer.as_ptr() as *const c_void,
198                buffer.len(),
199            );
200            result_cond(
201                res != -1,
202                (),
203                Error::OperationError("Could not write to target".to_string()),
204            )
205        }
206    }
207
208    /// Write a string to target.
209    pub fn writes(&mut self, text: &str) -> Result<()> {
210        unsafe {
211            let cstr = new_c_string(text)?;
212            let res = bindings::vips_target_writes(
213                self.ctx,
214                cstr.as_ptr(),
215            );
216            result_cond(
217                res != -1,
218                (),
219                Error::OperationError("Could not write to target".to_string()),
220            )
221        }
222    }
223
224    /// Write str to target, but escape stuff that xml hates in text.
225    pub fn write_amp(&mut self, text: &str) -> Result<()> {
226        unsafe {
227            let cstr = new_c_string(text)?;
228            let res = bindings::vips_target_write_amp(
229                self.ctx,
230                cstr.as_ptr(),
231            );
232            result_cond(
233                res != -1,
234                (),
235                Error::OperationError("Could not write to target".to_string()),
236            )
237        }
238    }
239
240    /// Call this at the end of write to make the target do any cleaning up.
241    pub fn end(self) {
242        unsafe {
243            bindings::vips_target_end(self.ctx);
244        }
245    }
246
247    pub fn get_blob(&self) -> Vec<u8> {
248        unsafe {
249            if self
250                .ctx
251                .is_null()
252                || (*self.ctx)
253                    .blob
254                    .is_null()
255            {
256                return Vec::new();
257            }
258            let mut size = 0;
259            let bytes = bindings::vips_blob_get(
260                (*self.ctx).blob,
261                &mut size,
262            );
263            let slice = std::slice::from_raw_parts(
264                bytes as *const u8,
265                size,
266            );
267            slice.to_vec()
268        }
269    }
270
271    // VipsConnection for VipsTarget
272    pub fn filename(&self) -> Option<String> {
273        unsafe { VipsConnection::filename(&mut (*self.ctx).parent_object) }
274    }
275
276    // VipsConnection for VipsTarget
277    pub fn nick(&self) -> Option<String> {
278        unsafe { VipsConnection::nick(&mut (*self.ctx).parent_object) }
279    }
280}
281
282unsafe extern "C" fn on_read(
283    _: *mut bindings::VipsTargetCustom,
284    buffer: *mut c_void,
285    length: gint64,
286    user_data: *mut c_void,
287) -> gint64 {
288    let ctx = &mut *(user_data as *mut TargetContext);
289
290    let slice = std::slice::from_raw_parts_mut(
291        buffer as *mut u8,
292        length as usize,
293    );
294
295    match ctx
296        .writer
297        .read(slice)
298    {
299        Ok(n) => n as i64,
300        Err(_) => -1,
301    }
302}
303
304unsafe extern "C" fn on_seek(
305    _: *mut bindings::VipsTargetCustom,
306    offset: gint64,
307    whence: c_int,
308    user_data: *mut c_void,
309) -> gint64 {
310    let ctx = &mut *(user_data as *mut TargetContext);
311
312    let pos = match whence {
313        0 => SeekFrom::Start(offset as u64),
314        1 => SeekFrom::Current(offset),
315        2 => SeekFrom::End(offset),
316        _ => return -1,
317    };
318
319    match ctx
320        .writer
321        .seek(pos)
322    {
323        Ok(n) => n as gint64,
324        Err(_) => -1,
325    }
326}
327
328unsafe extern "C" fn on_write(
329    _: *mut bindings::VipsTargetCustom,
330    data: *const c_void,
331    length: gint64,
332    user_data: *mut c_void,
333) -> gint64 {
334    let ctx = &mut *(user_data as *mut TargetContext);
335
336    let slice = std::slice::from_raw_parts(
337        data as *const u8,
338        length as usize,
339    );
340
341    match ctx
342        .writer
343        .write(slice)
344    {
345        Ok(n) => n as gint64,
346        Err(_) => -1,
347    }
348}
349
350unsafe extern "C" fn on_end(_: *mut bindings::VipsTargetCustom, data: *mut c_void) -> i32 {
351    let _ = Box::from_raw(data as *mut TargetContext);
352    0
353}
354
355impl Drop for VipsTarget {
356    fn drop(&mut self) {
357        unsafe {
358            if !self
359                .ctx
360                .is_null()
361            {
362                bindings::g_object_unref(self.ctx as *mut c_void);
363            }
364        }
365    }
366}