ttf-parser 0.10.1

A high-level, safe, zero-allocation TrueType font parser.
Documentation
// https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx

use core::num::NonZeroU16;

use crate::GlyphId;
use crate::parser::{Stream, FromData, LazyArray16};


#[derive(Clone, Copy)]
struct HorizontalMetrics {
    advance_width: u16,
    lsb: i16,
}

impl FromData for HorizontalMetrics {
    const SIZE: usize = 4;

    #[inline]
    fn parse(data: &[u8]) -> Option<Self> {
        let mut s = Stream::new(data);
        Some(HorizontalMetrics {
            advance_width: s.read::<u16>()?,
            lsb: s.read::<i16>()?,
        })
    }
}


#[derive(Clone, Copy)]
pub struct Table<'a> {
    metrics: LazyArray16<'a, HorizontalMetrics>,
    bearings: Option<LazyArray16<'a, i16>>,
    number_of_metrics: u16, // Sum of long metrics + bearings.
}

impl<'a> Table<'a> {
    pub fn parse(
        data: &'a [u8],
        number_of_hmetrics: NonZeroU16,
        number_of_glyphs: NonZeroU16,
    ) -> Option<Self> {
        let mut s = Stream::new(data);
        let metrics = s.read_array16::<HorizontalMetrics>(number_of_hmetrics.get())?;

        let mut number_of_metrics = number_of_hmetrics.get();

        // 'If the number_of_hmetrics is less than the total number of glyphs,
        // then that array is followed by an array for the left side bearing values
        // of the remaining glyphs.'
        let bearings_count = number_of_glyphs.get().checked_sub(number_of_hmetrics.get());
        let bearings = if let Some(count) = bearings_count {
            number_of_metrics += count;
            s.read_array16::<i16>(count)
        } else {
            None
        };

        Some(Table {
            metrics,
            bearings,
            number_of_metrics,
        })
    }

    #[inline]
    pub fn advance(&self, glyph_id: GlyphId) -> Option<u16> {
        if glyph_id.0 >= self.number_of_metrics {
            return None;
        }

        if let Some(metrics) = self.metrics.get(glyph_id.0) {
            Some(metrics.advance_width)
        } else {
            // 'As an optimization, the number of records can be less than the number of glyphs,
            // in which case the advance width value of the last record applies
            // to all remaining glyph IDs.'
            self.metrics.last().map(|m| m.advance_width)
        }
    }

    #[inline]
    pub fn side_bearing(&self, glyph_id: GlyphId) -> Option<i16> {
        if let Some(metrics) = self.metrics.get(glyph_id.0) {
            Some(metrics.lsb)
        } else if let Some(bearings) = self.bearings {
            // 'If the number_of_hmetrics is less than the total number of glyphs,
            // then that array is followed by an array for the left side bearing values
            // of the remaining glyphs.'

            let number_of_hmetrics = self.metrics.len();

            // Check for overflow.
            if glyph_id.0 < number_of_hmetrics {
                return None;
            }

            bearings.get(glyph_id.0 - number_of_hmetrics)
        } else {
            None
        }
    }
}

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

    macro_rules! nzu16 {
        ($n:expr) => { NonZeroU16::new($n).unwrap() };
    }

    #[test]
    fn simple_case() {
        let data = &[
            0x00, 0x01, // advance width [0]: 1
            0x00, 0x02, // side bearing [0]: 2
        ];

        let table = Table::parse(data, nzu16!(1), nzu16!(1)).unwrap();
        assert_eq!(table.advance(GlyphId(0)), Some(1));
        assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
    }

    #[test]
    fn empty() {
        assert!(Table::parse(&[], nzu16!(1), nzu16!(1)).is_none());
    }

    #[test]
    fn smaller_than_glyphs_count() {
        let data = &[
            0x00, 0x01, // advance width [0]: 1
            0x00, 0x02, // side bearing [0]: 2

            0x00, 0x03, // side bearing [1]: 3
        ];

        let table = Table::parse(data, nzu16!(1), nzu16!(2)).unwrap();
        assert_eq!(table.advance(GlyphId(0)), Some(1));
        assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
        assert_eq!(table.advance(GlyphId(1)), Some(1));
        assert_eq!(table.side_bearing(GlyphId(1)), Some(3));
    }

    #[test]
    fn less_metrics_than_glyphs() {
        let data = &[
            0x00, 0x01, // advance width [0]: 1
            0x00, 0x02, // side bearing [0]: 2

            0x00, 0x03, // advance width [1]: 3
            0x00, 0x04, // side bearing [1]: 4

            0x00, 0x05, // side bearing [2]: 5
        ];

        let table = Table::parse(data, nzu16!(2), nzu16!(1)).unwrap();
        assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
        assert_eq!(table.side_bearing(GlyphId(1)), Some(4));
        assert_eq!(table.side_bearing(GlyphId(2)), None);
    }

    #[test]
    fn glyph_out_of_bounds_0() {
        let data = &[
            0x00, 0x01, // advance width [0]: 1
            0x00, 0x02, // side bearing [0]: 2
        ];

        let table = Table::parse(data, nzu16!(1), nzu16!(1)).unwrap();
        assert_eq!(table.advance(GlyphId(0)), Some(1));
        assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
        assert_eq!(table.advance(GlyphId(1)), None);
        assert_eq!(table.side_bearing(GlyphId(1)), None);
    }

    #[test]
    fn glyph_out_of_bounds_1() {
        let data = &[
            0x00, 0x01, // advance width [0]: 1
            0x00, 0x02, // side bearing [0]: 2

            0x00, 0x03, // side bearing [1]: 3
        ];

        let table = Table::parse(data, nzu16!(1), nzu16!(2)).unwrap();
        assert_eq!(table.advance(GlyphId(1)), Some(1));
        assert_eq!(table.side_bearing(GlyphId(1)), Some(3));
        assert_eq!(table.advance(GlyphId(2)), None);
        assert_eq!(table.side_bearing(GlyphId(2)), None);
    }
}