hvif 0.1.1

Parser and renderer for HVIF (Haiku Vector Icon Format)
Documentation
// Copyright (c) 2021 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#![deny(missing_docs)]

//! This crate implements a parser for the Haiku Vector Icon Format, or HVIF.
//!
//! ```
//! use hvif::Hvif;
//!
//! fn main() {
//!     let data = b"ncif\0\0\0";
//!     if let Ok((_, hvif)) = Hvif::parse(data) {
//!         println!("{:?}", hvif);
//!     }
//! }
//! ```

use core::ops::Index;
use nom::{
    bytes::complete::tag,
    multi::{length_count, many_m_n},
    number::complete::u8,
    sequence::tuple,
    IResult,
};
use std::convert::TryInto;

pub mod path;
pub mod shape;
pub mod style;

use path::Path;
use shape::Shape;
use style::Style;

#[cfg(feature = "cairo-rs")]
mod render;
#[cfg(feature = "cairo-rs")]
pub use render::render;

/// The base type for a coordinate in HVIF, it can go from -128. to 192., while
/// the inner canvas goes from 0. to 64.
#[derive(Debug, Clone, PartialEq)]
pub struct Coord(f32);

impl Coord {
    /// Fetch the inner f32 of this Coord.
    pub fn as_f32(&self) -> f32 {
        self.0
    }

    /// Convert this Coord into a f64.
    pub fn to_f64(&self) -> f64 {
        self.0.into()
    }

    fn parse(i: &[u8]) -> IResult<&[u8], Coord> {
        let (i, a) = u8(i)?;
        if a < 128 {
            Ok((i, Coord((a as f32) - 32.)))
        } else {
            let (i, b) = u8(i)?;
            let c = ((a & 0x7f) as u16) << 8 | (b as u16);
            Ok((i, Coord((c as f32) / 102. - 128.)))
        }
    }
}

/// Represents a point in the HVIF, which can only be between -128. to 192. in
/// each direction (with the canvas being from 0. to 64.).
#[derive(Debug, Clone, PartialEq)]
pub struct Point {
    /// The x coordinate of this point.
    pub x: Coord,

    /// The y coordinate of this point.
    pub y: Coord,
}

impl Point {
    fn parse(i: &[u8]) -> IResult<&[u8], Point> {
        let (i, (x, y)) = tuple((Coord::parse, Coord::parse))(i)?;
        Ok((i, Point { x, y }))
    }
}

/// The base type of a transform matrix.
#[derive(Debug, Clone, PartialEq)]
pub struct Float24(f32);

impl Float24 {
    const ZERO: Float24 = Float24(0.);
    const ONE: Float24 = Float24(1.);

    /// Fetch the inner f32 of this Float24.
    pub fn as_f32(&self) -> f32 {
        self.0
    }

    /// Convert this Float24 into a f64.
    pub fn to_f64(&self) -> f64 {
        self.0 as f64
    }

    fn parse(i: &[u8]) -> IResult<&[u8], Float24> {
        let (i, (a, b, c)) = tuple((u8, u8, u8))(i)?;
        let value = (a as u32) << 16 | (b as u32) << 8 | (c as u32);
        if value == 0 {
            Ok((i, Float24(0.)))
        } else {
            let sign = !!(a & 0x80);
            let exp: i16 = ((a & 0x7e) >> 1) as i16 - 32;
            let mantissa = (value & 0x01ffff) << 6;
            let int = ((sign as u32) << 31) | (((exp + 127) as u32) << 23) | mantissa;
            let float = unsafe { std::mem::transmute(int) };
            Ok((i, Float24(float)))
        }
    }
}

impl From<Coord> for Float24 {
    fn from(coord: Coord) -> Float24 {
        Float24(coord.0)
    }
}

/// A 3×2 transform matrix.
#[derive(Debug, Clone, PartialEq)]
pub struct Transform([Float24; 6]);

impl Index<usize> for Transform {
    type Output = Float24;

    fn index(&self, idx: usize) -> &Float24 {
        &self.0[idx]
    }
}

impl Transform {
    #[cfg(test)]
    const IDENTITY: Transform = Transform([
        Float24::ONE,
        Float24::ZERO,
        Float24::ZERO,
        Float24::ONE,
        Float24::ZERO,
        Float24::ZERO,
    ]);

    fn parse(i: &[u8]) -> IResult<&[u8], Transform> {
        let (i, vec) = many_m_n(6, 6, Float24::parse)(i)?;
        // TODO: use a nom error instead of a panic.
        let array = vec
            .try_into()
            .unwrap_or_else(|vec| panic!("Transform didn’t contain six float24: {:?}", vec));
        Ok((i, Transform(array)))
    }
}

fn check_header(i: &[u8]) -> IResult<&[u8], ()> {
    // b"ficn" in little endian.
    let (i, _) = tag(b"ncif")(i)?;
    Ok((i, ()))
}

/// Entry point of this crate, this struct represents a HVIF file in memory.
#[derive(Debug, Clone, PartialEq)]
pub struct Hvif {
    /// The list of styles in this HVIF file.
    pub styles: Vec<Style>,

    /// The list of paths in this HVIF file.
    pub paths: Vec<Path>,

    /// The list of shapes in this HVIF file.
    pub shapes: Vec<Shape>,
}

impl Hvif {
    /// Parse a HVIF file from its serialisation.
    pub fn parse(i: &[u8]) -> IResult<&[u8], Hvif> {
        let (i, _) = check_header(i)?;
        let (i, styles) = length_count(u8, Style::parse)(i)?;
        let (i, paths) = length_count(u8, Path::parse)(i)?;
        let (i, shapes) = length_count(u8, Shape::parse)(i)?;
        Ok((
            i,
            Hvif {
                styles,
                paths,
                shapes,
            },
        ))
    }
}

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

    macro_rules! assert_size (
        ($t:ty, $sz:expr) => (
            assert_eq!(::std::mem::size_of::<$t>(), $sz);
        );
    );

    #[test]
    fn sizes() {
        assert_size!(Point, 8);
        assert_size!(Hvif, 72);
    }

    #[test]
    fn empty() {
        let empty = b"ncif\0\0\0";
        let (i, hvif) = Hvif::parse(empty).unwrap();
        assert!(i.is_empty());
        assert_eq!(hvif.styles.len(), 0);
        assert_eq!(hvif.paths.len(), 0);
        assert_eq!(hvif.shapes.len(), 0);
    }
}