gdeflate 0.4.1

A library for compressing and decompressing the GDeflate format
Documentation
#[cfg(test)]
mod tests;

pub mod sys {
    pub use gdeflate_sys::*;
}

use std::{ptr, slice};

use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
    #[error("Failed to create compressor")]
    CompressorCreationFailed,
    #[error("Failed to create decompressor")]
    DecompressorCreationFailed,
}

#[repr(i32)]
#[derive(Copy, Clone, Debug)]
pub enum CompressionLevel {
    None = 0,
    Level1 = 1,
    Level2 = 2,
    Level3 = 3,
    Level4 = 4,
    Level5 = 5,
    Level6 = 6,
    Level7 = 7,
    Level8 = 8,
    Level9 = 9,
    Level10 = 10,
    Level11 = 11,
    Level12 = 12,
}

#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tile {
    pub compressed_size: u32,
    pub uncompressed_size: u32,
}

#[derive(Clone, Debug)]
pub struct OwnedCompressionResult {
    pub bytes: Vec<u8>,
    pub tiles: Vec<Tile>,
    pub tile_size: u32,
}

#[derive(Clone, Debug)]
pub struct CompressionResult<'a> {
    pub bytes: &'a [u8],
    pub tiles: &'a [Tile],
    pub tile_size: u32,
}

impl<'a> CompressionResult<'a> {
    #[inline]
    pub fn new(bytes: &'a [u8], tiles: &'a [Tile], tile_size: u32) -> Self {
        Self {
            bytes,
            tiles,
            tile_size,
        }
    }
}

impl<'a> From<&'a OwnedCompressionResult> for CompressionResult<'a> {
    fn from(result: &'a OwnedCompressionResult) -> Self {
        Self {
            bytes: &result.bytes,
            tiles: &result.tiles,
            tile_size: result.tile_size,
        }
    }
}

pub struct Compressor(*mut sys::libdeflate_gdeflate_compressor);

impl Compressor {
    pub fn new(compression_level: CompressionLevel) -> Result<Self, Error> {
        let compressor =
            unsafe { sys::libdeflate_alloc_gdeflate_compressor(compression_level as _) };
        if compressor.is_null() {
            Err(Error::CompressorCreationFailed)
        } else {
            Ok(Self(compressor))
        }
    }

    pub fn compress(
        &mut self,
        uncompressed_bytes: &[u8],
        tile_size: usize,
    ) -> Result<OwnedCompressionResult, Error> {
        let num_tiles = (uncompressed_bytes.len() + tile_size - 1) / tile_size;

        let mut num_pages = 0;
        let scratch_size =
            unsafe { sys::libdeflate_gdeflate_compress_bound(self.0, tile_size, &mut num_pages) };
        assert_eq!(num_pages, 1);

        let mut scratch_buffer = vec![0u8; scratch_size];

        let mut bytes = vec![];
        let mut tiles = Vec::with_capacity(num_tiles);

        for i in 0..num_tiles {
            let tile_offset = i * tile_size;

            let mut compressed_page = sys::libdeflate_gdeflate_out_page {
                data: scratch_buffer.as_mut_ptr().cast(),
                nbytes: scratch_size,
            };

            let remaining = uncompressed_bytes.len() - tile_offset;
            let uncompressed_size = remaining.min(tile_size);

            unsafe {
                sys::libdeflate_gdeflate_compress(
                    self.0,
                    uncompressed_bytes.as_ptr().add(tile_offset).cast(),
                    uncompressed_size,
                    &mut compressed_page,
                    1,
                );

                bytes.extend_from_slice(slice::from_raw_parts(
                    compressed_page.data.cast(),
                    compressed_page.nbytes,
                ));

                tiles.push(Tile {
                    compressed_size: compressed_page.nbytes as _,
                    uncompressed_size: uncompressed_size as _,
                })
            }
        }

        Ok(OwnedCompressionResult {
            bytes,
            tiles,
            tile_size: tile_size as _,
        })
    }
}

impl Drop for Compressor {
    fn drop(&mut self) {
        unsafe { sys::libdeflate_free_gdeflate_compressor(self.0) }
    }
}

pub struct Decompressor(*mut sys::libdeflate_gdeflate_decompressor);

impl Decompressor {
    pub fn new() -> Result<Self, Error> {
        let decompressor = unsafe { sys::libdeflate_alloc_gdeflate_decompressor() };
        if decompressor.is_null() {
            Err(Error::DecompressorCreationFailed)
        } else {
            Ok(Self(decompressor))
        }
    }

    pub fn decompress<'a>(
        &mut self,
        result: impl Into<CompressionResult<'a>>,
    ) -> Result<Vec<u8>, Error> {
        let result = result.into();

        let uncompressed_size = result
            .tiles
            .iter()
            .map(|tile| tile.uncompressed_size)
            .sum::<u32>() as _;

        let mut bytes = vec![0u8; uncompressed_size];

        let mut compressed_offset = 0;
        let mut uncompressed_offset = 0;

        for tile in result.tiles {
            let mut compressed_page = sys::libdeflate_gdeflate_in_page {
                data: unsafe { result.bytes.as_ptr().add(compressed_offset) }.cast(),
                nbytes: tile.compressed_size as _,
            };

            unsafe {
                sys::libdeflate_gdeflate_decompress(
                    self.0,
                    &mut compressed_page,
                    1,
                    bytes.as_mut_ptr().add(uncompressed_offset).cast(),
                    tile.uncompressed_size as _,
                    ptr::null_mut(),
                );
            }

            compressed_offset += tile.compressed_size as usize;
            uncompressed_offset += tile.uncompressed_size as usize;
        }

        Ok(bytes)
    }
}

impl Drop for Decompressor {
    fn drop(&mut self) {
        unsafe { sys::libdeflate_free_gdeflate_decompressor(self.0) }
    }
}