cff 0.5.0

A zero-allocation CFF parser.
// Copyright 2018 Kyle Mayes
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! CFF top-level `DICT`s.

use tarrasque::{Extract, ExtractError, ExtractResult, Stream};

use crate::error::{MISMATCH, OPERATOR};
use super::{Entry, Numbers};

/// A section of a top-level `DICT` for a CID-keyed font.
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub struct Cid {
    pub ros: (u16, u16, i32),
    pub font_version: f32,
    pub font_revision: f32,
    pub font_type: i32,
    pub count: i32,
    pub uid_base: Option<i32>,
    pub fd_array: i32,
    pub fd_select: i32,
    pub font_name: Option<u16>,
}

/// A top-level `DICT` for a font.
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub struct Top<'s> {
    /// The string ID for the version of the font.
    pub version: Option<u16>,
    /// The string ID for the notice of the font.
    pub notice: Option<u16>,
    /// The string ID for the copyright of the font.
    pub copyright: Option<u16>,
    /// The string ID for the full name of the font.
    pub full_name: Option<u16>,
    /// The string ID for the family name of the font.
    pub family_name: Option<u16>,
    /// The string ID for the weight of the font.
    pub weight: Option<u16>,
    /// Whether the font is a monospace font.
    pub is_fixed_pitch: bool,
    pub italic_angle: i32,
    pub underline_position: i32,
    pub underline_thickness: i32,
    pub paint_type: i32,
    pub charstring_type: i32,
    pub font_matrix: [f32; 6],
    pub unique_id: Option<i32>,
    pub font_bounding_box: [i32; 4],
    pub stroke_width: i32,
    pub xuid: Option<Numbers<'s>>,
    pub charset: i32,
    pub encoding: i32,
    pub charstrings: Option<i32>,
    pub private: Option<(i32, i32)>,
    pub synthetic_base: Option<i32>,
    pub post_script: Option<u16>,
    pub base_font_name: Option<u16>,
    pub base_font_blend: Option<Numbers<'s>>,
    pub cid: Option<Cid>,
}

impl<'s> Extract<'s, ()> for Top<'s> {
    #[inline]
    fn extract(stream: &mut Stream<'s>, _: ()) -> ExtractResult<'s, Self> {
        let mut top = Top::default();
        top.is_fixed_pitch = false;
        top.italic_angle = 0;
        top.underline_position = -100;
        top.underline_thickness = 50;
        top.paint_type = 0;
        top.charstring_type = 2;
        top.font_matrix = [0.001, 0.0, 0.0, 0.001, 0.0, 0.0];
        top.font_bounding_box = [0, 0, 0, 0];
        top.stroke_width = 0;
        top.charset = 0;

        let mut cid = Cid::default();
        cid.font_version = 0.0;
        cid.font_revision = 0.0;
        cid.font_type = 0;
        cid.count = 8720;

        while !stream.is_empty() {
            let Entry { operator, numbers } = stream.extract(())?;
            match operator.0 {
                0 => top.version = Some(numbers.first()?.as_string_id()?),
                1 => top.notice = Some(numbers.first()?.as_string_id()?),
                2 => top.full_name = Some(numbers.first()?.as_string_id()?),
                3 => top.family_name = Some(numbers.first()?.as_string_id()?),
                4 => top.weight = Some(numbers.first()?.as_string_id()?),
                5 => if numbers.len() == 4 {
                    for (index, number) in numbers.numbers().enumerate() {
                        top.font_bounding_box[index] = number.into();
                    }
                } else {
                    return Err(ExtractError::Code(MISMATCH));
                },
                14 => top.xuid = Some(numbers),
                15 => top.charset = numbers.first()?.into(),
                16 => top.encoding = numbers.first()?.into(),
                17 => top.charstrings = Some(numbers.first()?.into()),
                18 => if numbers.len() == 2 {
                    let mut numbers = numbers.numbers();
                    let a = numbers.next().unwrap().into();
                    let b = numbers.next().unwrap().into();
                    top.private = Some((a, b));
                } else {
                    return Err(ExtractError::Code(MISMATCH));
                },
                3072 => top.copyright = Some(numbers.first()?.as_string_id()?),
                3073 => top.is_fixed_pitch = numbers.first()?.as_boolean()?,
                3074 => top.italic_angle = numbers.first()?.into(),
                3075 => top.underline_position = numbers.first()?.into(),
                3076 => top.underline_thickness = numbers.first()?.into(),
                3077 => top.paint_type = numbers.first()?.into(),
                3078 => top.charstring_type = numbers.first()?.into(),
                3079 => if numbers.len() == 6 {
                    for (index, number) in numbers.numbers().enumerate() {
                        top.font_matrix[index] = number.into();
                    }
                } else {
                    return Err(ExtractError::Code(MISMATCH));
                },
                3080 => top.stroke_width = numbers.first()?.into(),
                3092 => top.synthetic_base = Some(numbers.first()?.into()),
                3093 => top.post_script = Some(numbers.first()?.as_string_id()?),
                3094 => top.base_font_name = Some(numbers.first()?.as_string_id()?),
                3095 => top.base_font_blend = Some(numbers),
                3102 => if numbers.len() == 3 {
                    let mut numbers = numbers.numbers();
                    let registry = numbers.next().unwrap().as_string_id()?;
                    let ordering = numbers.next().unwrap().as_string_id()?;
                    let supplement = numbers.next().unwrap().into();
                    cid.ros = (registry, ordering, supplement);
                } else {
                    return Err(ExtractError::Code(MISMATCH));
                },
                3103 => cid.font_version = numbers.first()?.into(),
                3104 => cid.font_revision = numbers.first()?.into(),
                3105 => cid.font_type = numbers.first()?.into(),
                3106 => cid.count = numbers.first()?.into(),
                3107 => cid.uid_base = Some(numbers.first()?.into()),
                3108 => cid.fd_array = numbers.first()?.into(),
                3109 => cid.fd_select = numbers.first()?.into(),
                3110 => cid.font_name = Some(numbers.first()?.as_string_id()?),
                _ => return Err(ExtractError::Code(OPERATOR)),
            }
        }

        if cid.ros != (0, 0, 0) {
            top.cid = Some(cid);
        }

        Ok(top)
    }
}