versatiles_core 3.7.0

A toolbox for converting, checking and serving map tiles in various formats.
Documentation
use crate::Blob;
use anyhow::Result;
use brotli::{BrotliCompress, BrotliDecompress, enc::BrotliEncoderParams};
use std::io::Cursor;
use versatiles_derive::context;

/// Compresses data using Brotli.
///
/// # Arguments
///
/// * `blob` - The data blob to compress.
///
/// # Returns
///
/// * `Ok(Blob)` containing the Brotli-compressed data.
/// * `Err(anyhow::Error)` if compression fails.
///
/// # Errors
///
/// * If the Brotli compression process fails.
#[context("Compressing blob ({} bytes) using Brotli with highest quality settings", blob.len())]
pub fn compress_brotli(blob: &Blob) -> Result<Blob> {
	let params = BrotliEncoderParams {
		quality: 10, // Highest quality
		lgwin: 19,   // Window size
		size_hint: usize::try_from(blob.len())?,
		..Default::default()
	};
	let mut input = Cursor::new(blob.as_slice());
	let mut output = Vec::new();
	BrotliCompress(&mut input, &mut output, &params).context("Failed to compress data using Brotli")?;
	Ok(Blob::from(output))
}

/// Compresses data using Brotli with faster settings.
///
/// This variant uses lower quality settings for faster compression at the expense of compression ratio.
///
/// **Note:** This function is provided for direct use by callers who need to prioritize
/// compression speed over compression ratio. The [`optimize_compression`](super::super::optimize_compression)
/// function uses [`compress_brotli`] internally for maximum compression ratio.
///
/// # Arguments
///
/// * `blob` - The data blob to compress.
///
/// # Returns
///
/// * `Ok(Blob)` containing the Brotli-compressed data.
/// * `Err(anyhow::Error)` if compression fails.
///
/// # Errors
///
/// * If the Brotli compression process fails.
#[context("Compressing blob ({} bytes) using Brotli with fast compression settings", blob.len())]
pub fn compress_brotli_fast(blob: &Blob) -> Result<Blob> {
	let params = BrotliEncoderParams {
		quality: 3, // Lower quality for faster compression
		lgwin: 16,  // Smaller window size
		size_hint: usize::try_from(blob.len())?,
		..Default::default()
	};
	let mut input = Cursor::new(blob.as_slice());
	let mut output = Vec::new();
	BrotliCompress(&mut input, &mut output, &params).context("Failed to compress data using Brotli (fast)")?;
	Ok(Blob::from(output))
}

/// Decompresses data that was compressed using Brotli.
///
/// # Arguments
///
/// * `blob` - The Brotli-compressed data blob.
///
/// # Returns
///
/// * `Ok(Blob)` containing the decompressed data.
/// * `Err(anyhow::Error)` if decompression fails.
///
/// # Errors
///
/// * If the Brotli decompression process fails.
#[context("Decompressing blob ({} bytes) using Brotli", blob.len())]
pub fn decompress_brotli(blob: &Blob) -> Result<Blob> {
	let mut cursor = Cursor::new(blob.as_slice());
	let mut decompressed_data = Vec::new();
	BrotliDecompress(&mut cursor, &mut decompressed_data).context("Failed to decompress data using Brotli")?;
	Ok(Blob::from(decompressed_data))
}

#[cfg(test)]
mod tests {
	use super::super::super::test_utils::generate_test_data;
	use super::*;

	#[test]
	fn should_compress_and_decompress_brotli_correctly() -> Result<()> {
		let data = generate_test_data(10_000);
		let compressed = compress_brotli(&data)?;
		let decompressed = decompress_brotli(&compressed)?;
		assert_eq!(data, decompressed, "Brotli compression and decompression failed");
		Ok(())
	}

	#[test]
	fn should_compress_and_decompress_brotli_fast_correctly() -> Result<()> {
		let data = generate_test_data(10_000);
		let compressed = compress_brotli_fast(&data)?;
		let decompressed = decompress_brotli(&compressed)?;
		assert_eq!(data, decompressed, "Fast Brotli compression and decompression failed");
		Ok(())
	}
}