Skip to main content

libtlg_rs/
capi.rs

1//! C API for libtlg-rs
2//!
3//! Use follow command to build the C API library
4//! ```shell
5//! cargo install cargo-c
6//! cargo cbuild --release --features "encode"
7//! ```
8use std::ffi::{c_char, c_void};
9#[cfg(feature = "encode")]
10use std::io::Write;
11use std::io::{Read, Seek, SeekFrom};
12
13/// A readable stream for TLG processing.
14pub struct ReadableStream {
15    /// User data pointer.
16    userdata: *mut c_void,
17    /// Read function pointer.
18    read_fn: extern "C" fn(*mut c_void, *mut u8, usize) -> usize,
19    /// Seek function pointer.
20    seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
21}
22
23#[cfg(feature = "encode")]
24/// A writeable stream for TLG processing.
25pub struct WriteableStream {
26    /// User data pointer.
27    userdata: *mut c_void,
28    /// Write function pointer.
29    write_fn: extern "C" fn(*mut c_void, *const u8, usize) -> usize,
30    /// Flush function pointer.
31    flush_fn: extern "C" fn(*mut c_void) -> i32,
32    /// Seek function pointer.
33    seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
34}
35
36#[repr(C)]
37/// A tag in TLG file.
38pub struct Tag {
39    /// Tag name.
40    pub name: *mut c_char,
41    /// Length of tag name.
42    pub name_length: usize,
43    /// Tag value.
44    pub value: *mut c_char,
45    /// Length of tag value.
46    pub value_length: usize,
47}
48
49#[repr(C)]
50/// Color type of TLG image.
51pub enum TlgColorType {
52    /// Grayscale 8-bit.
53    Grayscale8,
54    /// BGR 24-bit.
55    Bgr24,
56    /// BGRA 32-bit.
57    Bgra32,
58}
59
60#[repr(C)]
61pub struct Tlg {
62    /// TLG version. 5 or 6.
63    pub version: u32,
64    /// Image width.
65    pub width: u32,
66    /// Image height.
67    pub height: u32,
68    /// Color type.
69    pub color_type: TlgColorType,
70    /// Pointer to image data.
71    pub data: *mut u8,
72    /// Number of bytes in data.
73    pub data_length: usize,
74    /// Pointer to tags, null if no tags.
75    pub tags: *mut Tag,
76    /// Number of tags.
77    pub tag_count: usize,
78}
79
80impl Read for ReadableStream {
81    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
82        let ret = (self.read_fn)(self.userdata, buf.as_mut_ptr(), buf.len());
83        Ok(ret)
84    }
85}
86
87impl Seek for ReadableStream {
88    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
89        let (offset, whence) = match pos {
90            SeekFrom::Start(off) => (off as i64, 0),
91            SeekFrom::End(off) => (off, 2),
92            SeekFrom::Current(off) => (off, 1),
93        };
94        let ret = (self.seek_fn)(self.userdata, offset, whence);
95        Ok(ret)
96    }
97}
98
99#[cfg(feature = "encode")]
100impl Write for WriteableStream {
101    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
102        let ret = (self.write_fn)(self.userdata, buf.as_ptr(), buf.len());
103        Ok(ret)
104    }
105
106    fn flush(&mut self) -> std::io::Result<()> {
107        let ret = (self.flush_fn)(self.userdata);
108        if ret == 0 {
109            Ok(())
110        } else {
111            Err(std::io::Error::new(
112                std::io::ErrorKind::Other,
113                "Flush failed",
114            ))
115        }
116    }
117}
118
119#[cfg(feature = "encode")]
120impl Seek for WriteableStream {
121    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
122        let (offset, whence) = match pos {
123            SeekFrom::Start(off) => (off as i64, 0),
124            SeekFrom::End(off) => (off, 2),
125            SeekFrom::Current(off) => (off, 1),
126        };
127        let ret = (self.seek_fn)(self.userdata, offset, whence);
128        Ok(ret)
129    }
130}
131
132/// Create a readable stream.
133#[unsafe(no_mangle)]
134pub unsafe extern "C" fn create_readable_stream(
135    userdata: *mut c_void,
136    read_fn: extern "C" fn(*mut c_void, *mut u8, usize) -> usize,
137    seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
138) -> *mut ReadableStream {
139    let stream = ReadableStream {
140        userdata,
141        read_fn,
142        seek_fn,
143    };
144    Box::into_raw(Box::new(stream))
145}
146
147/// Destroy a readable stream.
148#[unsafe(no_mangle)]
149pub unsafe extern "C" fn destroy_readable_stream(stream: *mut ReadableStream) {
150    if !stream.is_null() {
151        drop(unsafe { Box::from_raw(stream) });
152    }
153}
154
155/// Create a writeable stream.
156#[cfg(feature = "encode")]
157#[unsafe(no_mangle)]
158pub unsafe extern "C" fn create_writeable_stream(
159    userdata: *mut c_void,
160    write_fn: extern "C" fn(*mut c_void, *const u8, usize) -> usize,
161    flush_fn: extern "C" fn(*mut c_void) -> i32,
162    seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
163) -> *mut WriteableStream {
164    let stream = WriteableStream {
165        userdata,
166        write_fn,
167        flush_fn,
168        seek_fn,
169    };
170    Box::into_raw(Box::new(stream))
171}
172
173/// Destroy a writeable stream.
174#[cfg(feature = "encode")]
175#[unsafe(no_mangle)]
176pub unsafe extern "C" fn destroy_writeable_stream(stream: *mut WriteableStream) {
177    if !stream.is_null() {
178        drop(unsafe { Box::from_raw(stream) });
179    }
180}
181
182/// Check if it's a valid TLG.
183#[unsafe(no_mangle)]
184pub unsafe extern "C" fn check_tlg(stream: *mut ReadableStream) -> bool {
185    if stream.is_null() {
186        return false;
187    }
188    let stream = unsafe { &mut *stream };
189    match crate::check_tlg(stream) {
190        Ok(valid) => valid,
191        Err(_) => false,
192    }
193}
194
195/// Check if it's a valid TLG.
196/// 11 bytes are needed.
197#[unsafe(no_mangle)]
198pub unsafe extern "C" fn is_valid_tlg(data: *const u8, length: usize) -> bool {
199    let data = unsafe { std::slice::from_raw_parts(data, length) };
200    crate::is_valid_tlg(data)
201}
202
203/// Load a TLG from a readable stream.
204/// Returns a pointer to Tlg struct, or null on error.
205#[unsafe(no_mangle)]
206pub unsafe extern "C" fn load_tlg(stream: *mut ReadableStream) -> *mut Tlg {
207    if stream.is_null() {
208        return std::ptr::null_mut();
209    }
210    let stream = unsafe { &mut *stream };
211    match crate::load_tlg(stream) {
212        Ok(mut tlg) => {
213            let color_type = match tlg.color {
214                crate::TlgColorType::Grayscale8 => TlgColorType::Grayscale8,
215                crate::TlgColorType::Bgr24 => TlgColorType::Bgr24,
216                crate::TlgColorType::Bgra32 => TlgColorType::Bgra32,
217            };
218            tlg.data.shrink_to_fit();
219            let data_ptr = tlg.data.as_mut_ptr();
220            let data_len = tlg.data.len();
221            std::mem::forget(tlg.data);
222            let (tag, tag_count) = if tlg.tags.is_empty() {
223                (std::ptr::null_mut(), 0)
224            } else {
225                let mut tags = Vec::with_capacity(tlg.tags.len());
226                for (mut name, mut value) in tlg.tags.drain() {
227                    let name_ptr = name.as_mut_ptr();
228                    let name_len = name.len();
229                    name.shrink_to_fit();
230                    std::mem::forget(name);
231                    let value_ptr = value.as_mut_ptr();
232                    let value_len = value.len();
233                    value.shrink_to_fit();
234                    std::mem::forget(value);
235                    tags.push(Tag {
236                        name: name_ptr as *mut c_char,
237                        name_length: name_len,
238                        value: value_ptr as *mut c_char,
239                        value_length: value_len,
240                    });
241                }
242                let tag_ptr = tags.as_mut_ptr();
243                let tag_count = tags.len();
244                tags.shrink_to_fit();
245                std::mem::forget(tags);
246                (tag_ptr, tag_count)
247            };
248            let tlg_c = Tlg {
249                version: tlg.version,
250                width: tlg.width,
251                height: tlg.height,
252                color_type,
253                data: data_ptr,
254                data_length: data_len,
255                tags: tag,
256                tag_count,
257            };
258            Box::into_raw(Box::new(tlg_c))
259        }
260        Err(_) => std::ptr::null_mut(),
261    }
262}
263
264/// Destroy a Tlg struct.
265/// Also frees the image data.
266/// WARN: Make sure the Tlg struct is created by `load_tlg`.
267#[unsafe(no_mangle)]
268pub unsafe extern "C" fn destroy_tlg(tlg: *mut Tlg) {
269    if tlg.is_null() {
270        return;
271    }
272    let tlg = unsafe { Box::from_raw(tlg) };
273    let _data = unsafe { Vec::from_raw_parts(tlg.data, tlg.data_length, tlg.data_length) };
274    if !tlg.tags.is_null() {
275        let tags = unsafe { Vec::from_raw_parts(tlg.tags, tlg.tag_count, tlg.tag_count) };
276        for tag in tags {
277            let _name = unsafe {
278                Vec::from_raw_parts(tag.name as *mut u8, tag.name_length, tag.name_length)
279            };
280            let _value = unsafe {
281                Vec::from_raw_parts(tag.value as *mut u8, tag.value_length, tag.value_length)
282            };
283        }
284    }
285}
286
287/// Encode a TLG image to a writeable stream.
288/// Returns true on success, false on error.
289#[cfg(feature = "encode")]
290#[unsafe(no_mangle)]
291pub unsafe extern "C" fn save_tlg(tlg: Tlg, stream: *mut WriteableStream) -> bool {
292    if stream.is_null() || tlg.data.is_null() {
293        return false;
294    }
295    let stream = unsafe { &mut *stream };
296    let color = match tlg.color_type {
297        TlgColorType::Grayscale8 => crate::TlgColorType::Grayscale8,
298        TlgColorType::Bgr24 => crate::TlgColorType::Bgr24,
299        TlgColorType::Bgra32 => crate::TlgColorType::Bgra32,
300    };
301    let data = unsafe { std::slice::from_raw_parts(tlg.data, tlg.data_length) }.to_vec();
302    let mut tags = std::collections::HashMap::new();
303    if !tlg.tags.is_null() && tlg.tag_count > 0 {
304        let tag_slice = unsafe { std::slice::from_raw_parts(tlg.tags, tlg.tag_count) };
305        for tag in tag_slice {
306            let name =
307                unsafe { std::slice::from_raw_parts(tag.name as *const u8, tag.name_length) }
308                    .to_vec();
309            let value =
310                unsafe { std::slice::from_raw_parts(tag.value as *const u8, tag.value_length) }
311                    .to_vec();
312            tags.insert(name, value);
313        }
314    }
315    let tlg_rust = crate::Tlg {
316        version: tlg.version,
317        width: tlg.width,
318        height: tlg.height,
319        color,
320        data,
321        tags,
322    };
323    match crate::save_tlg(&tlg_rust, stream) {
324        Ok(_) => true,
325        Err(_) => false,
326    }
327}