mplusfonts-macros 0.3.4

Procedural macros re-exported in the mplusfonts crate
Documentation
mod glyph;
mod string;

use std::collections::BTreeMap;
use std::sync::RwLock;
use std::{iter, thread};

use glyph::{GlyphOffsets, GlyphSpacing};
use string::StringRefList;
use swash::scale::ScaleContext;
use swash::shape::ShapeContext;

use crate::mplus::Arguments;
use crate::mplus::charmap::CharmapEntry;
use crate::mplus::font::{Font, FontWidth};

use super::CharDictionary;

pub fn render(args: &Arguments, is_fallback: bool) -> BTreeMap<String, CharmapEntry> {
    let entries = BTreeMap::new();
    let font = args.font.value();
    let font_ref = font.as_ref(is_fallback);
    let is_code = matches!(font, Font::MPLUSCode { .. });
    if !is_code && is_fallback {
        return entries;
    }

    let mut coords = Vec::new();
    let units = args.weight.into_value();
    let weight_axis = font_ref
        .variations()
        .find_by_tag(swash::tag_from_str_lossy("wght"))
        .expect("expected font weight axis");

    coords.push(weight_axis.normalize(units.into()));

    if let Font::MPLUSCode { variable, .. } = font {
        let (.., FontWidth(units)) = *variable;
        if !is_fallback {
            let width_axis = font_ref
                .variations()
                .find_by_tag(swash::tag_from_str_lossy("wdth"))
                .expect("expected font width axis");

            coords.push(width_axis.normalize(units.into()));
        }
    }

    let pixels_per_em = args.size.into_value();
    let glyph_metrics = font_ref.glyph_metrics(&coords).scale(pixels_per_em);
    let glyph_spacing = GlyphSpacing::from_font(font, pixels_per_em);

    let mut contexts: Vec<_> = iter::repeat_with(ShapeContext::new)
        .take(thread::available_parallelism().map(Into::into).unwrap_or(1))
        .collect();

    let shapers = contexts.iter_mut().map(|context| {
        context
            .builder(font_ref)
            .normalized_coords(&coords)
            .size(pixels_per_em)
            .features(&[("jp04", cfg!(feature = "alt-jis2004") as u16)])
            .features(&[("liga", !is_fallback as u16)])
            .build()
    });

    let positions = args.positions.into_value();
    let bit_depth = args.bit_depth.into_value();

    let mut contexts: Vec<_> = iter::repeat_with(ScaleContext::new)
        .take(shapers.len() * positions as usize)
        .collect();

    let scalers = contexts.iter_mut().map(|context| {
        context
            .builder(font_ref)
            .normalized_coords(&coords)
            .size(pixels_per_em)
            .hint(args.hint.into_value())
            .build()
    });

    let mut scalers: Vec<_> = scalers.collect();
    let scalers = scalers.chunks_mut(positions as usize);
    let renders = scalers.map(|scalers| {
        let glyph_metrics = &glyph_metrics;
        let glyph_spacing = &glyph_spacing;
        move |glyph_offsets: GlyphOffsets| {
            glyph_offsets.scale(scalers, positions, bit_depth, glyph_metrics, glyph_spacing)
        }
    });

    let strings: Vec<_> = args
        .sources
        .iter()
        .flat_map(|source| source.strings(is_code))
        .collect();

    let indices = 0..shapers.len();
    let empties = iter::repeat_with(Vec::new).take(shapers.len());
    let strings = strings
        .iter()
        .flat_map(|strings| strings.iter())
        .filter(|string| !string.is_empty())
        .zip(indices.cycle())
        .fold(empties.collect(), |mut strings: Vec<_>, (string, index)| {
            strings
                .get_mut(index)
                .expect("expected index to be less than number of shapers")
                .push(string);

            strings
        });

    let strings = strings.into_iter().map(StringRefList);
    let entries = RwLock::new(entries);
    thread::scope(|scope| {
        let entries = CharDictionary::new(&entries);
        let glyph_spacing = &glyph_spacing;
        shapers
            .into_iter()
            .zip(renders)
            .zip(strings)
            .for_each(|((shaper, render), strings)| {
                scope.spawn(move || {
                    strings.shape_and_render(entries, shaper, render, glyph_spacing, is_fallback)
                });
            });
    });

    entries
        .into_inner()
        .expect("expected no-poison lock on entries")
}