yofi 0.2.0

minimalistic menu for wayland
use std::convert::TryInto;
use std::path::Path;

use font_kit::family_name::FamilyName;
use font_kit::loaders::freetype::Font as FontkitFont;
use font_kit::properties::Properties;
use font_kit::source::SystemSource;
use pathfinder_geometry::vector::vec2f;
use raqote::{DrawOptions, Point, Source};

use super::{DrawTarget, FontBackend, FontColor, Result};

pub struct Font {
    inner: FontkitFont,
    face: Option<ttf_parser::Face<'static>>,
}

impl Font {
    fn from_inner(inner: FontkitFont) -> Self {
        let face = match inner.handle().as_ref() {
            Some(font_kit::handle::Handle::Memory { bytes, font_index }) => {
                ttf_parser::Face::from_slice(bytes.as_slice(), *font_index)
                    .map(|face| unsafe { std::mem::transmute(face) })
                    .ok()
            }
            _ => None,
        };

        Self { inner, face }
    }

    fn find_kerning(&self, prev: u32, next: u32) -> Option<f32> {
        let prev = ttf_parser::GlyphId(prev.try_into().ok()?);
        let next = ttf_parser::GlyphId(next.try_into().ok()?);

        self.face
            .as_ref()?
            .tables()
            .kern?
            .subtables
            .into_iter()
            .filter(|st| st.horizontal && !st.variable)
            .filter_map(|st| st.glyphs_kerning(prev, next))
            .next()
            .map(|x| x as f32)
    }
}

impl FontBackend for Font {
    fn default() -> Self {
        SystemSource::new()
            .select_best_match(&[FamilyName::Monospace], &Properties::new())
            .unwrap()
            .load()
            .map(Self::from_inner)
            .unwrap()
    }

    fn font_by_name(name: &str) -> Result<Self> {
        SystemSource::new()
            .select_best_match(&[FamilyName::Title(name.to_string())], &Properties::new())?
            .load()
            .map(Self::from_inner)
            .map_err(Into::into)
    }

    fn font_by_path(path: &Path) -> Result<Self> {
        FontkitFont::from_path(path, 0)
            .map(Self::from_inner)
            .map_err(Into::into)
    }

    fn draw(
        &self,
        dt: &mut DrawTarget,
        text: &str,
        font_size: f32,
        start_pos: Point,
        color: FontColor,
        opts: &DrawOptions,
    ) {
        let em = font_size / 1.24;
        let start = vec2f(start_pos.x, start_pos.y + font_size);
        let units_per_em = self.inner.metrics().units_per_em as f32;

        let (_, _, ids, positions) = text
            .chars()
            .filter_map(|c| {
                let id = self.inner.glyph_for_char(c);

                let id = if let Some(id) = id {
                    id
                } else {
                    log::warn!("cannot find glyph for {:?}", c);
                    return None;
                };

                let advance = match self.inner.advance(id) {
                    Ok(x) => x,
                    Err(err) => {
                        log::warn!("cannot advance font for {:?}: {}", c, err);
                        return None;
                    }
                };

                Some((id, advance))
            })
            .fold(
                (start, None, vec![], vec![]),
                |(start, prev_id, mut ids, mut positions), (id, advance)| {
                    ids.push(id);
                    let kern_x = prev_id
                        .and_then(|prev_id| self.find_kerning(prev_id, id))
                        .unwrap_or(0.0)
                        * em
                        / units_per_em;
                    positions.push(Point::new(start.x() + kern_x, start.y()));

                    let delta = advance * em / units_per_em as f32 + vec2f(kern_x, 0.0);

                    (start + delta, Some(id), ids, positions)
                },
            );

        let mut draw_glyphs = |ids: &[_], positions: &[_], color| {
            dt.draw_glyphs(&self.inner, em, ids, positions, &Source::Solid(color), opts);
        };

        match color {
            FontColor::Single(color) => draw_glyphs(&ids, &positions, color),
            FontColor::Multiple(colors) => {
                for ((id, position), color) in ids.into_iter().zip(positions).zip(colors) {
                    draw_glyphs(&[id], &[position], color);
                }
            }
        };
    }
}