use std::borrow::Cow; use std::convert::TryInto; use std::ops::Range; use rustybuzz::{Feature, UnicodeBuffer}; use ttf_parser::Tag; use super::prelude::*; use crate::font::{ Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight, VerticalFontMetric, }; use crate::geom::{Dir, Em, Length, Point, Size}; use crate::style::{ FontFamily, FontFeatures, NumberPosition, NumberType, NumberWidth, Style, StylisticSet, TextStyle, }; use crate::util::{EcoString, SliceExt}; /// `font`: Configure the font. pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult { castable! { Vec, Expected: "string, generic family or array thereof", Value::Str(string) => vec![FontFamily::Named(string.to_lowercase())], Value::Array(values) => { values.into_iter().filter_map(|v| v.cast().ok()).collect() }, @family: FontFamily => vec![family.clone()], } castable! { Vec, Expected: "string or array of strings", Value::Str(string) => vec![string.to_lowercase()], Value::Array(values) => values .into_iter() .filter_map(|v| v.cast().ok()) .map(|string: EcoString| string.to_lowercase()) .collect(), } castable! { FontStyle, Expected: "string", Value::Str(string) => match string.as_str() { "normal" => Self::Normal, "italic" => Self::Italic, "oblique" => Self::Oblique, _ => Err(r#"expected "normal", "italic" or "oblique""#)?, }, } castable! { FontWeight, Expected: "integer or string", Value::Int(v) => v.try_into().map_or(Self::BLACK, Self::from_number), Value::Str(string) => match string.as_str() { "thin" => Self::THIN, "extralight" => Self::EXTRALIGHT, "light" => Self::LIGHT, "regular" => Self::REGULAR, "medium" => Self::MEDIUM, "semibold" => Self::SEMIBOLD, "bold" => Self::BOLD, "extrabold" => Self::EXTRABOLD, "black" => Self::BLACK, _ => Err("unknown font weight")?, }, } castable! { FontStretch, Expected: "relative", Value::Relative(v) => Self::from_ratio(v.get() as f32), } castable! { VerticalFontMetric, Expected: "linear or string", Value::Length(v) => Self::Linear(v.into()), Value::Relative(v) => Self::Linear(v.into()), Value::Linear(v) => Self::Linear(v), Value::Str(string) => match string.as_str() { "ascender" => Self::Ascender, "cap-height" => Self::CapHeight, "x-height" => Self::XHeight, "baseline" => Self::Baseline, "descender" => Self::Descender, _ => Err("unknown font metric")?, }, } castable! { StylisticSet, Expected: "none or integer", Value::None => Self(None), Value::Int(v) => match v { 1 ..= 20 => Self(Some(v as u8)), _ => Err("must be between 1 and 20")?, }, } castable! { NumberType, Expected: "auto or string", Value::Auto => Self::Auto, Value::Str(string) => match string.as_str() { "lining" => Self::Lining, "old-style" => Self::OldStyle, _ => Err(r#"expected "lining" or "old-style""#)?, }, } castable! { NumberWidth, Expected: "auto or string", Value::Auto => Self::Auto, Value::Str(string) => match string.as_str() { "proportional" => Self::Proportional, "tabular" => Self::Tabular, _ => Err(r#"expected "proportional" or "tabular""#)?, }, } castable! { NumberPosition, Expected: "string", Value::Str(string) => match string.as_str() { "normal" => Self::Normal, "subscript" => Self::Subscript, "superscript" => Self::Superscript, _ => Err(r#"expected "normal", "subscript" or "superscript""#)?, }, } castable! { Vec<(Tag, u32)>, Expected: "array of strings or dictionary mapping tags to integers", Value::Array(values) => values .into_iter() .filter_map(|v| v.cast().ok()) .map(|string: EcoString| (Tag::from_bytes_lossy(string.as_bytes()), 1)) .collect(), Value::Dict(values) => values .into_iter() .filter_map(|(k, v)| { let tag = Tag::from_bytes_lossy(k.as_bytes()); let num = v.cast::().ok()?.try_into().ok()?; Some((tag, num)) }) .collect(), } let list = args.named("family")?.or_else(|| { let families: Vec<_> = args.all().collect(); (!families.is_empty()).then(|| families) }); let serif = args.named("serif")?; let sans_serif = args.named("sans-serif")?; let monospace = args.named("monospace")?; let fallback = args.named("fallback")?; let style = args.named("style")?; let weight = args.named("weight")?; let tracking = args.named("tracking")?; let stretch = args.named("stretch")?; let size = args.named::("size")?.or_else(|| args.find()); let top_edge = args.named("top-edge")?; let bottom_edge = args.named("bottom-edge")?; let fill = args.named("fill")?.or_else(|| args.find()); let kerning = args.named("kerning")?; let smallcaps = args.named("smallcaps")?; let alternates = args.named("alternates")?; let stylistic_set = args.named("stylistic-set")?; let ligatures = args.named("ligatures")?; let discretionary_ligatures = args.named("discretionary-ligatures")?; let historical_ligatures = args.named("historical-ligatures")?; let number_type = args.named("number-type")?; let number_width = args.named("number-width")?; let number_position = args.named("number-position")?; let slashed_zero = args.named("slashed-zero")?; let fractions = args.named("fractions")?; let features = args.named("features")?; let body = args.find::