libtlg-rs 0.2.4

Rust version of libtlg
Documentation
//! C API for libtlg-rs
//!
//! Use follow command to build the C API library
//! ```shell
//! cargo install cargo-c
//! cargo cbuild --release --features "encode"
//! ```
use std::ffi::{c_char, c_void};
#[cfg(feature = "encode")]
use std::io::Write;
use std::io::{Read, Seek, SeekFrom};

/// A readable stream for TLG processing.
pub struct ReadableStream {
    /// User data pointer.
    userdata: *mut c_void,
    /// Read function pointer.
    read_fn: extern "C" fn(*mut c_void, *mut u8, usize) -> usize,
    /// Seek function pointer.
    seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
}

#[cfg(feature = "encode")]
/// A writeable stream for TLG processing.
pub struct WriteableStream {
    /// User data pointer.
    userdata: *mut c_void,
    /// Write function pointer.
    write_fn: extern "C" fn(*mut c_void, *const u8, usize) -> usize,
    /// Flush function pointer.
    flush_fn: extern "C" fn(*mut c_void) -> i32,
    /// Seek function pointer.
    seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
}

#[repr(C)]
/// A tag in TLG file.
pub struct Tag {
    /// Tag name.
    pub name: *mut c_char,
    /// Length of tag name.
    pub name_length: usize,
    /// Tag value.
    pub value: *mut c_char,
    /// Length of tag value.
    pub value_length: usize,
}

#[repr(C)]
/// Color type of TLG image.
pub enum TlgColorType {
    /// Grayscale 8-bit.
    Grayscale8,
    /// BGR 24-bit.
    Bgr24,
    /// BGRA 32-bit.
    Bgra32,
}

#[repr(C)]
pub struct Tlg {
    /// TLG version. 5 or 6.
    pub version: u32,
    /// Image width.
    pub width: u32,
    /// Image height.
    pub height: u32,
    /// Color type.
    pub color_type: TlgColorType,
    /// Pointer to image data.
    pub data: *mut u8,
    /// Number of bytes in data.
    pub data_length: usize,
    /// Pointer to tags, null if no tags.
    pub tags: *mut Tag,
    /// Number of tags.
    pub tag_count: usize,
}

impl Read for ReadableStream {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let ret = (self.read_fn)(self.userdata, buf.as_mut_ptr(), buf.len());
        Ok(ret)
    }
}

impl Seek for ReadableStream {
    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
        let (offset, whence) = match pos {
            SeekFrom::Start(off) => (off as i64, 0),
            SeekFrom::End(off) => (off, 2),
            SeekFrom::Current(off) => (off, 1),
        };
        let ret = (self.seek_fn)(self.userdata, offset, whence);
        Ok(ret)
    }
}

#[cfg(feature = "encode")]
impl Write for WriteableStream {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let ret = (self.write_fn)(self.userdata, buf.as_ptr(), buf.len());
        Ok(ret)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        let ret = (self.flush_fn)(self.userdata);
        if ret == 0 {
            Ok(())
        } else {
            Err(std::io::Error::new(
                std::io::ErrorKind::Other,
                "Flush failed",
            ))
        }
    }
}

#[cfg(feature = "encode")]
impl Seek for WriteableStream {
    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
        let (offset, whence) = match pos {
            SeekFrom::Start(off) => (off as i64, 0),
            SeekFrom::End(off) => (off, 2),
            SeekFrom::Current(off) => (off, 1),
        };
        let ret = (self.seek_fn)(self.userdata, offset, whence);
        Ok(ret)
    }
}

/// Create a readable stream.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn create_readable_stream(
    userdata: *mut c_void,
    read_fn: extern "C" fn(*mut c_void, *mut u8, usize) -> usize,
    seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
) -> *mut ReadableStream {
    let stream = ReadableStream {
        userdata,
        read_fn,
        seek_fn,
    };
    Box::into_raw(Box::new(stream))
}

/// Destroy a readable stream.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn destroy_readable_stream(stream: *mut ReadableStream) {
    if !stream.is_null() {
        drop(unsafe { Box::from_raw(stream) });
    }
}

/// Create a writeable stream.
#[cfg(feature = "encode")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn create_writeable_stream(
    userdata: *mut c_void,
    write_fn: extern "C" fn(*mut c_void, *const u8, usize) -> usize,
    flush_fn: extern "C" fn(*mut c_void) -> i32,
    seek_fn: extern "C" fn(*mut c_void, i64, i32) -> u64,
) -> *mut WriteableStream {
    let stream = WriteableStream {
        userdata,
        write_fn,
        flush_fn,
        seek_fn,
    };
    Box::into_raw(Box::new(stream))
}

/// Destroy a writeable stream.
#[cfg(feature = "encode")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn destroy_writeable_stream(stream: *mut WriteableStream) {
    if !stream.is_null() {
        drop(unsafe { Box::from_raw(stream) });
    }
}

/// Check if it's a valid TLG.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn check_tlg(stream: *mut ReadableStream) -> bool {
    if stream.is_null() {
        return false;
    }
    let stream = unsafe { &mut *stream };
    match crate::check_tlg(stream) {
        Ok(valid) => valid,
        Err(_) => false,
    }
}

/// Check if it's a valid TLG.
/// 11 bytes are needed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn is_valid_tlg(data: *const u8, length: usize) -> bool {
    let data = unsafe { std::slice::from_raw_parts(data, length) };
    crate::is_valid_tlg(data)
}

/// Load a TLG from a readable stream.
/// Returns a pointer to Tlg struct, or null on error.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn load_tlg(stream: *mut ReadableStream) -> *mut Tlg {
    if stream.is_null() {
        return std::ptr::null_mut();
    }
    let stream = unsafe { &mut *stream };
    match crate::load_tlg(stream) {
        Ok(mut tlg) => {
            let color_type = match tlg.color {
                crate::TlgColorType::Grayscale8 => TlgColorType::Grayscale8,
                crate::TlgColorType::Bgr24 => TlgColorType::Bgr24,
                crate::TlgColorType::Bgra32 => TlgColorType::Bgra32,
            };
            tlg.data.shrink_to_fit();
            let data_ptr = tlg.data.as_mut_ptr();
            let data_len = tlg.data.len();
            std::mem::forget(tlg.data);
            let (tag, tag_count) = if tlg.tags.is_empty() {
                (std::ptr::null_mut(), 0)
            } else {
                let mut tags = Vec::with_capacity(tlg.tags.len());
                for (mut name, mut value) in tlg.tags.drain() {
                    let name_ptr = name.as_mut_ptr();
                    let name_len = name.len();
                    name.shrink_to_fit();
                    std::mem::forget(name);
                    let value_ptr = value.as_mut_ptr();
                    let value_len = value.len();
                    value.shrink_to_fit();
                    std::mem::forget(value);
                    tags.push(Tag {
                        name: name_ptr as *mut c_char,
                        name_length: name_len,
                        value: value_ptr as *mut c_char,
                        value_length: value_len,
                    });
                }
                let tag_ptr = tags.as_mut_ptr();
                let tag_count = tags.len();
                tags.shrink_to_fit();
                std::mem::forget(tags);
                (tag_ptr, tag_count)
            };
            let tlg_c = Tlg {
                version: tlg.version,
                width: tlg.width,
                height: tlg.height,
                color_type,
                data: data_ptr,
                data_length: data_len,
                tags: tag,
                tag_count,
            };
            Box::into_raw(Box::new(tlg_c))
        }
        Err(_) => std::ptr::null_mut(),
    }
}

/// Destroy a Tlg struct.
/// Also frees the image data.
/// WARN: Make sure the Tlg struct is created by `load_tlg`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn destroy_tlg(tlg: *mut Tlg) {
    if tlg.is_null() {
        return;
    }
    let tlg = unsafe { Box::from_raw(tlg) };
    let _data = unsafe { Vec::from_raw_parts(tlg.data, tlg.data_length, tlg.data_length) };
    if !tlg.tags.is_null() {
        let tags = unsafe { Vec::from_raw_parts(tlg.tags, tlg.tag_count, tlg.tag_count) };
        for tag in tags {
            let _name = unsafe {
                Vec::from_raw_parts(tag.name as *mut u8, tag.name_length, tag.name_length)
            };
            let _value = unsafe {
                Vec::from_raw_parts(tag.value as *mut u8, tag.value_length, tag.value_length)
            };
        }
    }
}

/// Encode a TLG image to a writeable stream.
/// Returns true on success, false on error.
#[cfg(feature = "encode")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn save_tlg(tlg: Tlg, stream: *mut WriteableStream) -> bool {
    if stream.is_null() || tlg.data.is_null() {
        return false;
    }
    let stream = unsafe { &mut *stream };
    let color = match tlg.color_type {
        TlgColorType::Grayscale8 => crate::TlgColorType::Grayscale8,
        TlgColorType::Bgr24 => crate::TlgColorType::Bgr24,
        TlgColorType::Bgra32 => crate::TlgColorType::Bgra32,
    };
    let data = unsafe { std::slice::from_raw_parts(tlg.data, tlg.data_length) }.to_vec();
    let mut tags = std::collections::HashMap::new();
    if !tlg.tags.is_null() && tlg.tag_count > 0 {
        let tag_slice = unsafe { std::slice::from_raw_parts(tlg.tags, tlg.tag_count) };
        for tag in tag_slice {
            let name =
                unsafe { std::slice::from_raw_parts(tag.name as *const u8, tag.name_length) }
                    .to_vec();
            let value =
                unsafe { std::slice::from_raw_parts(tag.value as *const u8, tag.value_length) }
                    .to_vec();
            tags.insert(name, value);
        }
    }
    let tlg_rust = crate::Tlg {
        version: tlg.version,
        width: tlg.width,
        height: tlg.height,
        color,
        data,
        tags,
    };
    match crate::save_tlg(&tlg_rust, stream) {
        Ok(_) => true,
        Err(_) => false,
    }
}