mplusfonts-macros 0.3.4

Procedural macros re-exported in the mplusfonts crate
Documentation
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::sync::RwLock;

use crate::mplus::bitmap::{Glyph, GlyphList};
use crate::mplus::charmap::CharmapEntry;

#[derive(Clone, Copy)]
pub struct CharDictionary<'a>(&'a RwLock<BTreeMap<String, CharmapEntry>>);

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct CharDictionaryKey(String);

impl<'a> CharDictionary<'a> {
    pub fn new(entries: &'a RwLock<BTreeMap<String, CharmapEntry>>) -> Self {
        Self(entries)
    }

    pub fn contains_key(&self, entry_key: &CharDictionaryKey) -> bool {
        let Self(entries) = self;
        let entries = entries.read().expect("expected no-poison lock on entries");
        let key = entry_key.as_ref();

        entries.contains_key(key)
    }

    pub fn insert_glyphs(&self, entry_key: CharDictionaryKey, glyphs: Vec<Glyph>) {
        let Self(entries) = self;
        let mut entries = entries.write().expect("expected no-poison lock on entries");
        let key = entry_key.into_inner();
        let entry = entries.entry(key);
        entry.or_insert_with_key(|key| CharmapEntry {
            key: key.clone(),
            advance_chars: key.chars().count(),
            advance_width_to: BTreeMap::new(),
            advance_width: glyphs.iter().map(|glyph| glyph.advance_width).sum(),
            glyphs: GlyphList(glyphs),
        });
    }

    pub fn insert_advance_width(
        &self,
        entry_key: CharDictionaryKey,
        to_entry_key: CharDictionaryKey,
        advance_width: f32,
    ) {
        let Self(entries) = self;
        let mut entries = entries.write().expect("expected no-poison lock on entries");
        let key = entry_key.into_inner();
        let entry = entries.entry(key);
        let entry = entry.and_modify(|entry| {
            if entry.advance_width != advance_width {
                let to_key = to_entry_key.into_inner();
                if let Some(previous) = entry.advance_width_to.insert(to_key, advance_width) {
                    debug_assert_eq!(
                        previous,
                        advance_width,
                        "expected no change in value in case of update to entry with key `{key:?}`",
                        key = entry.key,
                    );
                }
            }
        });
        debug_assert!(
            matches!(entry, Entry::Occupied(_)),
            "expected to modify an existing entry"
        );
    }
}

impl CharDictionaryKey {
    pub fn try_from(string: &str, start: usize, end: usize) -> Result<Self, ()> {
        let bytes: Vec<_> = string.bytes().skip(start).take(end - start).collect();
        debug_assert!(
            !bytes.is_empty(),
            "indexing into `{string:?}`, \
            out of bounds at `{end:?}`"
        );
        let key = match String::from_utf8(bytes) {
            Ok(substring) if substring.is_empty() => return Err(()),
            Ok(substring) => substring,
            Err(e) => {
                debug_assert_eq!(
                    None,
                    Some(e),
                    "indexing into `{string:?}`, \
                    expected character boundary at bytes `{start:?}` and `{end:?}`"
                );
                return Err(());
            }
        };

        Ok(Self(key))
    }

    pub fn into_inner(self) -> String {
        let Self(key) = self;

        key
    }
}

impl AsRef<str> for CharDictionaryKey {
    fn as_ref(&self) -> &str {
        let Self(key) = self;

        key
    }
}

impl<T: Into<String>> From<T> for CharDictionaryKey {
    fn from(key: T) -> Self {
        Self(key.into())
    }
}