webfont-generator 0.2.2

Generate webfonts (SVG, TTF, EOT, WOFF, WOFF2) from SVG icons
Documentation
use std::io::{Error, ErrorKind, Write};

use flate2::Compression;
use flate2::write::ZlibEncoder;

const WOFF_HEADER_SIZE: usize = 44;
const META_OFFSET_POS: usize = 24;
const META_LENGTH_POS: usize = 28;
const META_ORIG_LENGTH_POS: usize = 32;
const LENGTH_POS: usize = 8;

pub(crate) fn ttf_to_woff1(ttf: &[u8], metadata: Option<&str>) -> Result<Vec<u8>, Error> {
    let mut woff_buf = ::woff::version1::compress(ttf, 1, 0)
        .ok_or_else(|| Error::new(ErrorKind::InvalidData, "WOFF compression failed"))?;
    if let Some(metadata) = metadata {
        inject_woff_metadata(&mut woff_buf, metadata)?;
    }
    Ok(woff_buf)
}

pub(crate) fn ttf_to_woff2(ttf: &[u8]) -> Result<Vec<u8>, Error> {
    ::woff::version2::compress(ttf, "", 11, true)
        .ok_or_else(|| Error::new(ErrorKind::InvalidData, "WOFF2 compression failed"))
}

fn inject_woff_metadata(woff: &mut Vec<u8>, metadata: &str) -> Result<(), Error> {
    if woff.len() < WOFF_HEADER_SIZE {
        return Err(Error::new(
            ErrorKind::InvalidData,
            "WOFF buffer is too short to contain a valid header.",
        ));
    }

    let meta_raw = metadata.as_bytes();
    let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
    encoder.write_all(meta_raw)?;
    let meta_compressed = encoder.finish()?;

    let meta_offset = woff.len() as u32;
    let meta_length = meta_compressed.len() as u32;
    let meta_orig_length = meta_raw.len() as u32;

    woff.extend_from_slice(&meta_compressed);

    let total_length = woff.len() as u32;

    woff[LENGTH_POS..LENGTH_POS + 4].copy_from_slice(&total_length.to_be_bytes());
    woff[META_OFFSET_POS..META_OFFSET_POS + 4].copy_from_slice(&meta_offset.to_be_bytes());
    woff[META_LENGTH_POS..META_LENGTH_POS + 4].copy_from_slice(&meta_length.to_be_bytes());
    woff[META_ORIG_LENGTH_POS..META_ORIG_LENGTH_POS + 4]
        .copy_from_slice(&meta_orig_length.to_be_bytes());

    Ok(())
}

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

    #[test]
    fn injects_metadata_into_woff_header() {
        let mut woff = vec![0u8; WOFF_HEADER_SIZE];

        inject_woff_metadata(&mut woff, "<metadata />").unwrap();

        assert!(woff.len() > WOFF_HEADER_SIZE);

        let total_length = u32::from_be_bytes(woff[LENGTH_POS..LENGTH_POS + 4].try_into().unwrap());
        let meta_offset = u32::from_be_bytes(
            woff[META_OFFSET_POS..META_OFFSET_POS + 4]
                .try_into()
                .unwrap(),
        );
        let meta_length = u32::from_be_bytes(
            woff[META_LENGTH_POS..META_LENGTH_POS + 4]
                .try_into()
                .unwrap(),
        );
        let meta_orig = u32::from_be_bytes(
            woff[META_ORIG_LENGTH_POS..META_ORIG_LENGTH_POS + 4]
                .try_into()
                .unwrap(),
        );

        assert_eq!(total_length, woff.len() as u32);
        assert_eq!(meta_offset, WOFF_HEADER_SIZE as u32);
        assert_eq!(meta_orig, 12);
        assert!(meta_length > 0);
    }

    #[test]
    fn rejects_buffer_too_short() {
        let mut woff = vec![0u8; 10];
        let err = inject_woff_metadata(&mut woff, "test").unwrap_err();
        assert_eq!(err.kind(), ErrorKind::InvalidData);
    }
}