use std::borrow::Cow; use std::convert::TryInto; use std::ops::Range; use rustybuzz::{Feature, Tag, UnicodeBuffer}; use super::prelude::*; use crate::font::{ Face, FaceId, FontFamily, FontStretch, FontStyle, FontVariant, FontWeight, VerticalFontMetric, }; use crate::geom::{Dir, Em, Length, Point, Size}; use crate::style::{ FontFeatures, NumberPosition, NumberType, NumberWidth, Style, TextStyle, }; use crate::util::SliceExt; /// `font`: Configure the font. pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult { struct FontDef(Rc>); struct FamilyDef(Rc>); struct FeatureList(Vec<(String, u32)>); struct StylisticSet(Option); castable! { FontDef: "font family or array of font families", Value::Str(string) => Self(Rc::new(vec![FontFamily::Named(string.to_lowercase())])), Value::Array(values) => Self(Rc::new( values .into_iter() .filter_map(|v| v.cast().ok()) .collect() )), @family: FontFamily => Self(Rc::new(vec![family.clone()])), } castable! { FamilyDef: "string or array of strings", Value::Str(string) => Self(Rc::new(vec![string.to_lowercase()])), Value::Array(values) => Self(Rc::new( values .into_iter() .filter_map(|v| v.cast().ok()) .map(|string: Str| string.to_lowercase()) .collect() )), } castable! { FontStyle: "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: "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: "relative", Value::Relative(v) => Self::from_ratio(v.get() as f32), } castable! { VerticalFontMetric: "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: "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: "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: "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: "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! { FeatureList: "array of strings or dictionary mapping tags to integers", Value::Array(values) => Self(values .into_iter() .filter_map(|v| v.cast().ok()) .map(|string: Str| (string.to_lowercase(), 1)) .collect()), Value::Dict(values) => Self(values .into_iter() .filter_map(|(k, v)| { if let Ok(value) = v.cast::() { Some((k.to_lowercase(), value as u32)) } else { None } }) .collect()), } let list = args.named("family")?.or_else(|| { let families: Vec<_> = args.all().collect(); (!families.is_empty()).then(|| FontDef(Rc::new(families))) }); let serif = args.named("serif")?; let sans_serif = args.named("sans-serif")?; let monospace = args.named("monospace")?; let fallback = args.named("fallback")?; let size = args.named::("size")?.or_else(|| args.find()); let style = args.named("style")?; let weight = args.named("weight")?; let stretch = args.named("stretch")?; let fill = args.named("fill")?.or_else(|| args.find()); let top_edge = args.named("top-edge")?; let bottom_edge = args.named("bottom-edge")?; 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::