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 how a shape is made, from one or more paths with a style applied.

use crate::{Float24, Point, Transform};
use bitflags::bitflags;
use nom::{
    bytes::complete::tag, multi::length_count, number::complete::u8, sequence::tuple, IResult,
};

bitflags! {
    /// Flags which apply to a given Shape.
    pub struct ShapeFlags: u8 {
        /// This Shape has a transform applied.
        const TRANSFORM = 0b00000010;

        /// This shape has hinting.
        const HINTING = 0b00000100;

        /// This Shape is to be only displayed between some level of details.
        const LOD_SCALE = 0b00001000;

        /// This Shape has at least one transformer.
        const HAS_TRANSFORMERS = 0b00010000;

        /// This Shape is translated.
        const TRANSLATION = 0b00100000;
    }
}

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

fn parse_translation(i: &[u8]) -> IResult<&[u8], Transform> {
    let (i, Point { x, y }) = Point::parse(i)?;
    Ok((
        i,
        Transform([
            Float24::ONE,
            Float24::ZERO,
            Float24::ZERO,
            Float24::ONE,
            x.into(),
            y.into(),
        ]),
    ))
}

enum TransformerType {
    Affine = 20,
    Contour = 21,
    Perspective = 22,
    Stroke = 23,
}

impl TransformerType {
    fn parse(i: &[u8]) -> IResult<&[u8], TransformerType> {
        let (i, type_) = u8(i)?;
        use TransformerType::*;
        Ok((
            i,
            match type_ {
                20 => Affine,
                21 => Contour,
                22 => Perspective,
                23 => Stroke,
                _ => {
                    unreachable!("Unknown transformer type {}", type_);
                }
            },
        ))
    }
}

/// Specifies how to render the junction of two lines when stroking.
#[derive(Debug, Clone, PartialEq)]
pub enum LineJoin {
    /// Use a sharp (angled) corner, see the miter limit.
    MiterJoin,

    /// Like MiterJoin but revert?
    MiterJoinRevert,

    /// Use a rounded join, the center of the circle is the joint point.
    RoundJoin,

    /// Use a cut-off join, the join is cut off at half the line width from the joint point.
    BevelJoin,

    /// Like a mix of MiterJoin and RoundJoin?
    MiterJoinRound,
}

impl From<u8> for LineJoin {
    fn from(bits: u8) -> LineJoin {
        use LineJoin::*;
        match bits {
            0b0000 => MiterJoin,
            0b0001 => MiterJoinRevert,
            0b0010 => RoundJoin,
            0b0011 => BevelJoin,
            0b0100 => MiterJoinRound,
            _ => unreachable!("Unknown LineJoin."),
        }
    }
}

/// Specifies how to render the endpoints of the path when stroking.
#[derive(Debug, Clone, PartialEq)]
pub enum LineCap {
    /// Start (and stop) the line exactly at the start (end) point.
    Butt,

    /// Use squared ending, the center of the square is the end point.
    Square,

    /// Use a round ending, the center of the circle is the end point.
    Round,
}

impl From<u8> for LineCap {
    fn from(bits: u8) -> LineCap {
        use LineCap::*;
        match bits {
            0b0000 => Butt,
            0b0001 => Square,
            0b0010 => Round,
            _ => unreachable!("Unknown LineCap."),
        }
    }
}

/// This shape has rendering transformers.
#[derive(Debug, Clone, PartialEq)]
pub enum Transformer {
    /// There is an additional affine transform being applied.
    Affine(Transform),

    /// This shape is a contour, the inside won’t be filled.
    Contour {
        /// The contour width.
        width: i8,

        /// Specifies how to render the junction of two lines.
        line_join: LineJoin,

        /// The miter limit.
        miter_limit: u8,
    },

    // Unused.
    //Perspective,

    /// This shape is a stroke, the inside won’t be filled.
    Stroke {
        /// The stroke width.
        width: i8,

        /// Specifies how to render the junction of two lines.
        line_join: LineJoin,

        /// Specifies how to render the endpoints of the path.
        line_cap: LineCap,

        /// The miter limit.
        miter_limit: u8,
    },
}

impl Transformer {
    fn parse(i: &[u8]) -> IResult<&[u8], Transformer> {
        let (i, type_) = TransformerType::parse(i)?;
        use Transformer::*;
        match type_ {
            TransformerType::Affine => {
                let (i, matrix) = Transform::parse(i)?;
                Ok((i, Affine(matrix)))
            }
            TransformerType::Contour => {
                let (i, (width, line_options, miter_limit)) = tuple((u8, u8, u8))(i)?;
                let width = ((width as i16) - 128) as i8;
                let line_join = LineJoin::from(line_options);
                Ok((
                    i,
                    Contour {
                        width,
                        line_join,
                        miter_limit,
                    },
                ))
            }
            TransformerType::Perspective => {
                todo!("Unsupported in HVIF.")
            }
            TransformerType::Stroke => {
                let (i, (width, line_options, miter_limit)) = tuple((u8, u8, u8))(i)?;
                let width = ((width as i16) - 128) as i8;
                let line_join = LineJoin::from(line_options & 0xf);
                let line_cap = LineCap::from(line_options >> 4);
                Ok((
                    i,
                    Stroke {
                        width,
                        line_join,
                        line_cap,
                        miter_limit,
                    },
                ))
            }
        }
    }
}

/// A shape.
#[derive(Debug, Clone, PartialEq)]
pub struct Shape {
    /// The Style to apply to this Shape.
    pub style: u8,

    /// The list of paths which compose this Shape.
    pub paths: Vec<u8>,

    /// The flags affecting this Shape.
    pub flags: ShapeFlags,

    /// A 3×2 transform matrix to place this Shape.
    pub transform: Option<Transform>,

    /// The minimum and maximum levels of detail at which to render this Shape.
    pub lod_scale: Option<(u8, u8)>,

    /// Transformers applied to this Shape.
    pub transformers: Vec<Transformer>,
}

impl Shape {
    /// Parse a Shape from its HVIF serialisation.
    pub fn parse(i: &[u8]) -> IResult<&[u8], Shape> {
        let (i, _) = tag(b"\x0a")(i)?;
        let (i, style) = u8(i)?;
        let (i, paths) = length_count(u8, u8)(i)?;
        let (i, flags) = ShapeFlags::parse(i)?;
        let (i, transform) = if flags.contains(ShapeFlags::TRANSFORM) {
            let (i, matrix) = Transform::parse(i)?;
            (i, Some(matrix))
        } else if flags.contains(ShapeFlags::TRANSLATION) {
            let (i, matrix) = parse_translation(i)?;
            (i, Some(matrix))
        } else {
            (i, None)
        };
        let (i, lod_scale) = if flags.contains(ShapeFlags::LOD_SCALE) {
            let (i, lod_scale) = tuple((u8, u8))(i)?;
            (i, Some(lod_scale))
        } else {
            (i, None)
        };
        let (i, transformers) = if flags.contains(ShapeFlags::HAS_TRANSFORMERS) {
            length_count(u8, Transformer::parse)(i)?
        } else {
            (i, Vec::new())
        };
        Ok((
            i,
            Shape {
                style,
                paths,
                flags,
                transform,
                lod_scale,
                transformers,
            },
        ))
    }
}

#[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!(ShapeFlags, 1);
        assert_size!(LineJoin, 1);
        assert_size!(LineCap, 1);
        assert_size!(Transformer, 28);
        assert_size!(Shape, 88);
    }

    #[test]
    fn empty() {
        let data = b"\x0a\x00\x00\x00";
        let (i, shape) = Shape::parse(data).unwrap();
        assert!(i.is_empty());
        assert_eq!(shape.style, 0);
        assert_eq!(shape.paths, []);
        assert_eq!(shape.flags, ShapeFlags::empty());
        assert_eq!(shape.transform, None);
        assert_eq!(shape.lod_scale, None);
        assert_eq!(shape.transformers, []);
    }

    #[test]
    fn transform() {
        let data = b"\x0a\x00\x00\x02\x40\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00";
        let (i, shape) = Shape::parse(data).unwrap();
        assert!(i.is_empty());
        assert_eq!(shape.style, 0);
        assert_eq!(shape.paths, []);
        assert_eq!(shape.flags, ShapeFlags::TRANSFORM);
        assert_eq!(shape.transform.unwrap(), Transform::IDENTITY);
        assert_eq!(shape.lod_scale, None);
        assert_eq!(shape.transformers, []);
    }

    #[test]
    fn transformer() {
        let data = b"\x0a\x00\x00\x10\x01\x17\x84\x00\x04";
        let (i, shape) = Shape::parse(data).unwrap();
        assert!(i.is_empty());
        assert_eq!(shape.style, 0);
        assert_eq!(shape.paths, []);
        assert_eq!(shape.flags, ShapeFlags::HAS_TRANSFORMERS);
        assert_eq!(shape.transform, None);
        assert_eq!(shape.lod_scale, None);
        assert_eq!(
            shape.transformers,
            [Transformer::Stroke {
                width: 4,
                line_join: LineJoin::MiterJoin,
                line_cap: LineCap::Butt,
                miter_limit: 4
            }]
        );
    }
}