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/.

//! Describes the style of each shape.

use crate::Transform;
use bitflags::bitflags;
use nom::{multi::many_m_n, number::complete::u8, sequence::tuple, IResult};

/// Represents one colour, with optimisations in the cases there is no alpha,
/// or all three components are equal (that is, grey).
#[derive(Debug, Clone, PartialEq)]
pub enum Colour {
    /// Contains all four components.
    Rgba(u8, u8, u8, u8),

    /// Completely opaque RGB.
    Rgb(u8, u8, u8),

    /// All three colours are equal.
    Grey(u8, u8),

    /// Completely opaque grey.
    GreyNoAlpha(u8),
}

impl Colour {
    fn parse_rgba(i: &[u8]) -> IResult<&[u8], Colour> {
        let (i, (r, g, b, a)) = tuple((u8, u8, u8, u8))(i)?;
        Ok((i, Colour::Rgba(r, g, b, a)))
    }

    fn parse_rgb(i: &[u8]) -> IResult<&[u8], Colour> {
        let (i, (r, g, b)) = tuple((u8, u8, u8))(i)?;
        Ok((i, Colour::Rgb(r, g, b)))
    }

    fn parse_grey(i: &[u8]) -> IResult<&[u8], Colour> {
        let (i, (grey, alpha)) = tuple((u8, u8))(i)?;
        Ok((i, Colour::Grey(grey, alpha)))
    }

    fn parse_grey_no_alpha(i: &[u8]) -> IResult<&[u8], Colour> {
        let (i, grey) = u8(i)?;
        Ok((i, Colour::GreyNoAlpha(grey)))
    }

    /// Returns whether a colour is opaque, that is no alpha value is present.
    pub fn is_opaque(&self) -> bool {
        match *self {
            Colour::Rgba(_, _, _, a) | Colour::Grey(_, a) => a == 255,
            Colour::Rgb(_, _, _) | Colour::GreyNoAlpha(_) => true,
        }
    }

    /// Converts this colour to its rgb components.
    ///
    /// Safety: it must be called only on opaque colours.
    pub fn to_rgb(&self) -> (u8, u8, u8) {
        match *self {
            Colour::Rgba(r, g, b, a) if a == 255 => (r, g, b),
            Colour::Rgb(r, g, b) => (r, g, b),
            Colour::Grey(grey, alpha) if alpha == 255 => (grey, grey, grey),
            Colour::GreyNoAlpha(grey) => (grey, grey, grey),
            _ => panic!("This function must only be called on opaque colours."),
        }
    }

    /// Converts this colour to its rgba components.
    pub fn to_rgba(&self) -> (u8, u8, u8, u8) {
        match *self {
            Colour::Rgba(r, g, b, a) => (r, g, b, a),
            Colour::Rgb(r, g, b) => (r, g, b, 255),
            Colour::Grey(grey, alpha) => (grey, grey, grey, alpha),
            Colour::GreyNoAlpha(grey) => (grey, grey, grey, 255),
        }
    }
}

enum StyleType {
    SolidColour = 1,
    Gradient = 2,
    SolidColourNoAlpha = 3,
    SolidGrey = 4,
    SolidGreyNoAlpha = 5,
}

impl StyleType {
    fn parse(i: &[u8]) -> IResult<&[u8], StyleType> {
        let (i, type_) = u8(i)?;
        use StyleType::*;
        Ok((
            i,
            match type_ {
                1 => SolidColour,
                2 => Gradient,
                3 => SolidColourNoAlpha,
                4 => SolidGrey,
                5 => SolidGreyNoAlpha,
                _ => {
                    unreachable!("Unknown style type {}", type_);
                }
            },
        ))
    }
}

/// The type of the gradient, for use for rendering.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GradientType {
    /// A linear gradient, interpolating linearly alongside the vector.
    Linear = 0,

    /// A circular gradient.
    Circular = 1,

    /// A gradient in a diamond shape.
    Diamond = 2,

    /// A conic gradient.
    Conic = 3,

    /// What is this even?
    Xy = 4,

    /// Aaaaah!
    SqrtXy = 5,
}

impl GradientType {
    fn parse(i: &[u8]) -> IResult<&[u8], GradientType> {
        let (i, type_) = u8(i)?;
        use GradientType::*;
        Ok((
            i,
            match type_ {
                0 => Linear,
                1 => Circular,
                2 => Diamond,
                3 => Conic,
                4 => Xy,
                5 => SqrtXy,
                _ => unreachable!("Unknown gradient type {}", type_),
            },
        ))
    }
}

bitflags! {
    /// Flags affecting a gradient.
    pub struct GradientFlags: u8 {
        /// This gradient contains a 3×2 transformation matrix.
        const TRANSFORM = 0b00000010;

        /// This gradient is fully opaque.
        const NO_ALPHA = 0b00000100;

        // Unused.
        //const SIXTEEN_BIT_COLOURS = 0b00001000;

        /// This gradient only uses greys, no other colours.
        const GREYS = 0b00010000;
    }
}

impl GradientFlags {
    fn parse(i: &[u8]) -> IResult<&[u8], GradientFlags> {
        let (i, flags) = u8(i)?;
        Ok((i, GradientFlags::from_bits_truncate(flags)))
    }
}

/// A point in a gradient.
#[derive(Debug, Clone, PartialEq)]
pub struct Stop {
    /// The offset of this stop, interpolated.
    pub offset: u8,

    /// The colour of this stop.
    pub colour: Colour,
}

impl Stop {
    fn parse_rgba(i: &[u8]) -> IResult<&[u8], Stop> {
        let (i, (offset, colour)) = tuple((u8, Colour::parse_rgba))(i)?;
        Ok((i, Stop { offset, colour }))
    }

    fn parse_rgb(i: &[u8]) -> IResult<&[u8], Stop> {
        let (i, (offset, colour)) = tuple((u8, Colour::parse_rgb))(i)?;
        Ok((i, Stop { offset, colour }))
    }

    fn parse_grey(i: &[u8]) -> IResult<&[u8], Stop> {
        let (i, (offset, colour)) = tuple((u8, Colour::parse_grey))(i)?;
        Ok((i, Stop { offset, colour }))
    }

    fn parse_grey_no_alpha(i: &[u8]) -> IResult<&[u8], Stop> {
        let (i, (offset, colour)) = tuple((u8, Colour::parse_grey_no_alpha))(i)?;
        Ok((i, Stop { offset, colour }))
    }
}

/// A gradient.
#[derive(Debug, Clone, PartialEq)]
pub struct Gradient {
    /// The type of this gradient.
    pub type_: GradientType,

    /// The flags of this gradient.
    pub flags: GradientFlags,

    /// Contains up to 255 different stops in this gradient.
    pub stops: Vec<Stop>,

    /// An optional 3×2 transformation matrix applied to this gradient.
    pub transform: Option<Transform>,
}

impl Gradient {
    fn parse(i: &[u8]) -> IResult<&[u8], Gradient> {
        let (i, (type_, flags)) = tuple((GradientType::parse, GradientFlags::parse))(i)?;
        // length_count() can’t be used here, since we could have a transform matrix in-between.
        let (i, num_stops) = u8(i)?;
        let num_stops = num_stops as usize;
        let (i, transform) = if flags.contains(GradientFlags::TRANSFORM) {
            let (i, transform) = Transform::parse(i)?;
            (i, Some(transform))
        } else {
            (i, None)
        };
        let (i, stops) = if flags.contains(GradientFlags::NO_ALPHA) {
            if flags.contains(GradientFlags::GREYS) {
                many_m_n(num_stops, num_stops, Stop::parse_grey_no_alpha)(i)?
            } else {
                many_m_n(num_stops, num_stops, Stop::parse_rgb)(i)?
            }
        } else if flags.contains(GradientFlags::GREYS) {
            many_m_n(num_stops, num_stops, Stop::parse_grey)(i)?
        } else {
            many_m_n(num_stops, num_stops, Stop::parse_rgba)(i)?
        };
        Ok((
            i,
            Gradient {
                type_,
                flags,
                stops,
                transform,
            },
        ))
    }
}

/// A style, to be applied to one or more shapes.
#[derive(Debug, Clone, PartialEq)]
pub enum Style {
    /// This style is a solid colour.
    SolidColour(Colour),

    /// This style is a gradient.
    Gradient(Gradient),
}

impl Style {
    /// Parse a Style from its HVIF serialisation.
    pub fn parse(i: &[u8]) -> IResult<&[u8], Style> {
        let (i, type_) = StyleType::parse(i)?;
        match type_ {
            StyleType::SolidColour => {
                let (i, colour) = Colour::parse_rgba(i)?;
                Ok((i, Style::SolidColour(colour)))
            }
            StyleType::Gradient => {
                let (i, gradient) = Gradient::parse(i)?;
                Ok((i, Style::Gradient(gradient)))
            }
            StyleType::SolidColourNoAlpha => {
                let (i, colour) = Colour::parse_rgb(i)?;
                Ok((i, Style::SolidColour(colour)))
            }
            StyleType::SolidGrey => {
                let (i, colour) = Colour::parse_grey(i)?;
                Ok((i, Style::SolidColour(colour)))
            }
            StyleType::SolidGreyNoAlpha => {
                let (i, colour) = Colour::parse_grey_no_alpha(i)?;
                Ok((i, Style::SolidColour(colour)))
            }
        }
    }
}

#[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!(Colour, 5);
        assert_size!(GradientType, 1);
        assert_size!(GradientFlags, 1);
        assert_size!(Stop, 6);
        assert_size!(Gradient, 56);
        assert_size!(Style, 64);
    }

    #[test]
    fn solid_grey() {
        let grey = b"\x05\x80";
        let (i, style) = Style::parse(grey).unwrap();
        assert!(i.is_empty());
        assert_eq!(style, Style::SolidColour(Colour::GreyNoAlpha(128)));
    }

    #[test]
    fn solid_colour() {
        let red = b"\x01\xff\x00\x00\x80";
        let (i, style) = Style::parse(red).unwrap();
        assert!(i.is_empty());
        assert_eq!(style, Style::SolidColour(Colour::Rgba(255, 0, 0, 128)));
    }

    #[test]
    fn gradient() {
        let data = b"\x02\x00\x04\x02\x00\x20\x40\x60\xff\x60\x40\x20";
        let (i, style) = Style::parse(data).unwrap();
        println!("{:?}", i);
        assert!(i.is_empty());
        if let Style::Gradient(gradient) = style {
            assert_eq!(gradient.type_, GradientType::Linear);
            assert_eq!(gradient.flags, GradientFlags::NO_ALPHA);
            assert_eq!(
                gradient.stops,
                [
                    Stop {
                        offset: 0,
                        colour: Colour::Rgb(32, 64, 96)
                    },
                    Stop {
                        offset: 255,
                        colour: Colour::Rgb(96, 64, 32)
                    },
                ]
            );
            assert_eq!(gradient.transform, None);
        } else {
            panic!("Parsed style isn’t a gradient.");
        }
    }

    #[test]
    fn gradient_matrix() {
        let data = b"\x02\x00\x02\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00";
        let (i, style) = Style::parse(data).unwrap();
        println!("{:?}", i);
        assert!(i.is_empty());
        if let Style::Gradient(gradient) = style {
            assert_eq!(gradient.type_, GradientType::Linear);
            assert_eq!(gradient.flags, GradientFlags::TRANSFORM);
            assert_eq!(gradient.stops, []);
            assert_eq!(gradient.transform.unwrap(), Transform::IDENTITY);
        } else {
            panic!("Parsed style isn’t a gradient.");
        }
    }
}