cranpose-render-common 0.1.162

Common rendering contracts for Cranpose
Documentation
use std::sync::Arc;

use cranpose_ui::text::TextStyle;

#[derive(Clone, Default)]
pub(crate) struct FontTracking(Arc<[(f32, f32)]>);

impl FontTracking {
    pub(crate) fn from_face(face: &ttf_parser::Face<'_>) -> Self {
        face.tables()
            .trak
            .and_then(|table| Self::from_table(table, f32::from(face.units_per_em())))
            .unwrap_or_default()
    }

    fn from_table(table: ttf_parser::trak::Table<'_>, units_per_em: f32) -> Option<Self> {
        let data = table.horizontal;
        let normal = data.tracks.into_iter().find(|track| track.value == 0.0)?;
        let mut points = Vec::with_capacity(usize::from(data.sizes.len()));
        for index in 0..data.sizes.len() {
            let size = data.sizes.get(index)?.0;
            if size <= 0.0 || points.last().is_some_and(|&(previous, _)| size <= previous) {
                return None;
            }
            points.push((size, f32::from(normal.values.get(index)?) / units_per_em));
        }
        Some(Self(points.into()))
    }

    fn at_size(&self, size: f32) -> f32 {
        if !size.is_finite() || size <= 0.0 || self.0.is_empty() {
            return 0.0;
        }
        let index = self.0.partition_point(|&(point_size, _)| point_size < size);
        let em = if index == 0 {
            self.0[0].1
        } else if index == self.0.len() {
            self.0[index - 1].1
        } else {
            let (low_size, low) = self.0[index - 1];
            let (high_size, high) = self.0[index];
            low + (high - low) * (size - low_size) / (high_size - low_size)
        };
        em * size
    }

    pub(crate) fn resolve(&self, style: &TextStyle, size: f32) -> f32 {
        if style.span_style.letter_spacing.is_unspecified() {
            self.at_size(size)
        } else {
            style.resolve_letter_spacing(size)
        }
    }
}

#[cfg(test)]
#[path = "tests/font_tracking_tests.rs"]
mod tests;