diff --git a/fontique/src/attributes.rs b/fontique/src/attributes.rs index 8d7dc7bc..40988087 100644 --- a/fontique/src/attributes.rs +++ b/fontique/src/attributes.rs @@ -9,6 +9,8 @@ use core_maths::CoreFloat; use core::fmt; +pub(crate) const DEFAULT_OBLIQUE_ANGLE: f32 = 14.0; + /// Primary attributes for font matching: [`FontWidth`], [`FontStyle`] and [`FontWeight`]. /// /// These are used to [configure] a [`Query`]. @@ -492,6 +494,13 @@ impl FontStyle { _ => Self::Normal, } } + + pub(crate) fn oblique_angle(&self) -> Option { + match self { + Self::Oblique(angle) => Some(angle.unwrap_or(DEFAULT_OBLIQUE_ANGLE)), + _ => None, + } + } } impl fmt::Display for FontStyle { @@ -500,7 +509,7 @@ impl fmt::Display for FontStyle { Self::Normal => "normal", Self::Italic => "italic", Self::Oblique(None) => "oblique", - Self::Oblique(Some(degrees)) if *degrees == 14.0 => "oblique", + Self::Oblique(Some(degrees)) if *degrees == DEFAULT_OBLIQUE_ANGLE => "oblique", Self::Oblique(Some(degrees)) => { return write!(f, "oblique({degrees}deg)"); } diff --git a/fontique/src/font.rs b/fontique/src/font.rs index 599739af..af7ff246 100644 --- a/fontique/src/font.rs +++ b/fontique/src/font.rs @@ -4,6 +4,7 @@ //! Model for a font. use crate::CharmapIndex; +use crate::matching::FontMatchingInfo; use super::attributes::{FontStyle, FontWeight, FontWidth}; use super::source::{SourceInfo, SourceKind}; @@ -27,6 +28,18 @@ pub struct FontInfo { charmap_index: CharmapIndex, } +#[allow(clippy::from_over_into)] // In future From won't be possible +impl Into for &FontInfo { + fn into(self) -> FontMatchingInfo { + FontMatchingInfo { + width: (self.width().ratio() * 100.0) as i32, + style: self.style(), + weight: self.weight().value(), + has_slnt: self.has_slant_axis(), + } + } +} + impl FontInfo { /// Creates a new font object from the given source and index. pub fn from_source(source: SourceInfo, index: u32) -> Option { diff --git a/fontique/src/matching.rs b/fontique/src/matching.rs index bd276599..ea443d25 100644 --- a/fontique/src/matching.rs +++ b/fontique/src/matching.rs @@ -3,462 +3,362 @@ //! Implementation of the CSS font matching algorithm. -use super::attributes::{FontStyle, FontWeight, FontWidth}; -use super::font::FontInfo; +use core::ops::Deref; + +use super::attributes::{DEFAULT_OBLIQUE_ANGLE, FontStyle, FontWeight, FontWidth}; +use core::cmp::Ordering; use smallvec::SmallVec; -const DEFAULT_OBLIQUE_ANGLE: f32 = 14.0; +// Public API + +#[derive(Copy, Clone)] +pub struct FontMatchingInfo { + pub width: i32, + pub style: FontStyle, + pub weight: f32, + pub has_slnt: bool, +} pub fn match_font( - set: &[FontInfo], + fonts: impl IntoIterator>, width: FontWidth, style: FontStyle, weight: FontWeight, synthesize_style: bool, ) -> Option { - const OBLIQUE_THRESHOLD: f32 = DEFAULT_OBLIQUE_ANGLE; - match set.len() { - 0 => return None, - 1 => return Some(0), - _ => {} + let set = CandidateFontSet::new(fonts); + set.match_font_impl(width, style, weight, synthesize_style) +} + +// Private implementation details + +#[derive(Copy, Clone)] +struct CandidateFont { + index: usize, + info: FontMatchingInfo, +} +impl Deref for CandidateFont { + type Target = FontMatchingInfo; + fn deref(&self) -> &Self::Target { + &self.info } - #[derive(Copy, Clone)] - struct Candidate { - index: usize, - width: i32, - style: FontStyle, - weight: f32, - has_slnt: bool, +} + +struct CandidateFontSet(SmallVec<[CandidateFont; 16]>); + +impl CandidateFontSet { + fn new(fonts: impl IntoIterator>) -> Self { + let inner = fonts + .into_iter() + .enumerate() + .map(|(index, info)| CandidateFont { + index, + info: info.into(), + }) + .collect(); + Self(inner) } - let mut set: SmallVec<[Candidate; 16]> = set - .iter() - .enumerate() - .map(|(i, font)| Candidate { - index: i, - width: (font.width().ratio() * 100.0) as i32, - style: font.style(), - weight: font.weight().value(), - has_slnt: font.has_slant_axis(), - }) - .collect(); - let width = (width.ratio() * 100.0) as i32; - let weight = weight.value(); - // font-width is tried first: - let mut use_width = set[0].width; - if !set.iter().any(|f| f.width == width) { - // If the desired width value is less than or equal to 100%... - if width <= 100 { - // width values below the desired width value are checked in - // descending order... - if let Some(found) = set - .iter() - .filter(|f| f.width < width) - .max_by_key(|f| f.width) - { - use_width = found.width; - } - // followed by width values above the desired width value in - // ascending order until a match is found. - else if let Some(found) = set - .iter() - .filter(|f| f.width > width) - .min_by_key(|f| f.width) - { - use_width = found.width; - } - } - // Otherwise, ... - else { - // width values above the desired width value are checked in - // ascending order... - if let Some(found) = set - .iter() - .filter(|f| f.width > width) - .min_by_key(|f| f.width) - { - use_width = found.width; + + // Width methods + + fn has_width(&self, width: i32) -> bool { + self.0.iter().any(|f| f.width == width) + } + + fn max_width_below(&self, width: i32) -> Option { + self.0 + .iter() + .filter(|f| f.width < width) + .max_by_key(|f| f.width) + .map(|f| f.width) + } + + fn min_width_above(&self, width: i32) -> Option { + self.0 + .iter() + .filter(|f| f.width > width) + .min_by_key(|f| f.width) + .map(|f| f.width) + } + + // Weight methods + + fn fonts_matching_weight( + &self, + predicate: impl Fn(f32) -> bool, + ) -> impl Iterator { + self.0.iter().filter(move |f| predicate(f.weight)) + } + + fn max_weight_matching(&self, predicate: impl Fn(f32) -> bool) -> Option<&CandidateFont> { + self.fonts_matching_weight(predicate) + .max_by(|x, y| x.weight.partial_cmp(&y.weight).unwrap_or(Ordering::Less)) + } + + fn min_weight_matching(&self, predicate: impl Fn(f32) -> bool) -> Option<&CandidateFont> { + self.fonts_matching_weight(predicate) + .min_by(|x, y| x.weight.partial_cmp(&y.weight).unwrap_or(Ordering::Less)) + } + + // Style methods + + fn has_style(&self, style: FontStyle) -> bool { + self.0.iter().any(|f| f.style == style) + } + + fn has_variable_font_with_slnt_axis(&self) -> bool { + self.0.iter().any(|f| f.has_slnt) + } + + fn fonts_matching_oblique_angle( + &self, + predicate: impl Fn(f32) -> bool, + ) -> impl Iterator { + self.0 + .iter() + .filter_map(move |f| match f.style.oblique_angle() { + Some(a) if predicate(a) => Some((f, a)), + _ => None, + }) + } + + fn min_oblique_angle_matching(&self, predicate: impl Fn(f32) -> bool) -> Option { + self.fonts_matching_oblique_angle(predicate) + .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Ordering::Less)) + .map(|f| f.0.style) + } + + fn max_oblique_angle_matching(&self, predicate: impl Fn(f32) -> bool) -> Option { + self.fonts_matching_oblique_angle(predicate) + .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Ordering::Less)) + .map(|f| f.0.style) + } + + fn fallback_style(&self, synthesize_style: bool, predicate: impl Fn(f32) -> bool) -> FontStyle { + if synthesize_style { + if self.has_style(FontStyle::Normal) { + FontStyle::Normal + } else { + self.0[0].style } - // followed by width values below the desired width value in - // descending order until a match is found. - else if let Some(found) = set - .iter() - .filter(|f| f.width < width) - .max_by_key(|f| f.width) - { - use_width = found.width; + } else { + // Choose an italic style + if self.has_style(FontStyle::Italic) { + FontStyle::Italic + } else { + // oblique values less than or equal to 0deg are checked in descending order + self.max_oblique_angle_matching(predicate) + .unwrap_or(self.0[0].style) } } - } else { - use_width = width; } - set.retain(|f| f.width == use_width); - use core::cmp::Ordering::*; - let oblique_fonts = set.iter().filter_map(|f| oblique_style(f.style)); - // font-style is tried next: - // NOTE: this code uses an oblique threshold of 14deg rather than - // the current value of 20deg in the spec. - // See: https://github.com/w3c/csswg-drafts/issues/2295 - let mut use_style = style; - let mut _use_slnt = false; - if !set.iter().any(|f| f.style == use_style) { - // If the value of font-style is italic: - if style == FontStyle::Italic { - // oblique values greater than or equal to 14deg are checked in - // ascending order - if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a >= OBLIQUE_THRESHOLD) - .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } - // followed by positive oblique values below 14deg in descending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a > 0. && *a < OBLIQUE_THRESHOLD) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; + + // Matching algorithm + + // Method consumes self because it mutates it's containing collection + // which means that calling this twice could not work + fn match_font_impl( + mut self, + width: FontWidth, + style: FontStyle, + weight: FontWeight, + synthesize_style: bool, + ) -> Option { + // Early return for case of 0 or 1 fonts where matching is trivial + match self.0.len() { + 0 => return None, + 1 => return Some(0), + _ => {} + } + + let width = (width.ratio() * 100.0) as i32; + let weight = weight.value(); + + // font-width is tried first: + let use_width = if !self.has_width(width) { + // If the desired width value is less than or equal to 100% then... + if width <= 100 { + // Width values below the desired width value are checked in descending order followed by + // width values above the desired width value in ascending order until a match is found. + self.max_width_below(width) + .or_else(|| self.min_width_above(width)) + .unwrap_or(width) } - // If no match is found, oblique values less than or equal to 0deg - // are checked in descending order until a match is found. - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a < 0.) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - use_style = set[0].style; + // Otherwise... + else { + // Width values above the desired width value are checked in ascending order followed by + // width values below the desired width value in descending order until a match is found. + self.min_width_above(width) + .or_else(|| self.max_width_below(width)) + .unwrap_or(width) } - } - // If the value of font-style is oblique... - else if let Some(angle) = oblique_angle(style) { - // and the requested angle is greater than or equal to 14deg - if angle >= OBLIQUE_THRESHOLD { - // oblique values greater than or equal to angle are checked in + } else { + width + }; + self.0.retain(|f| f.width == use_width); + + // NOTE: this code uses an oblique threshold of 14deg rather than + // the current value of 11deg in the spec. + // See: https://github.com/w3c/csswg-drafts/issues/2295 + const OBLIQUE_THRESHOLD: f32 = DEFAULT_OBLIQUE_ANGLE; + + // font-style is tried next: + let mut _use_slnt = false; + let use_style = if self.has_style(style) { + style + } else { + // If the value of font-style is italic: + if style == FontStyle::Italic { + // oblique values greater than or equal to 14deg are checked in // ascending order - if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a >= angle) - .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } - // followed by positive oblique values below angle in descending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a > 0. && *a < angle) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - // If font-synthesis-style has the value auto, then for variable - // fonts with a slnt axis a match is created by setting the slnt - // value with the specified oblique value; otherwise, a fallback - // match is produced by geometric shearing to the specified - // oblique value. - if synthesize_style { - if set.iter().any(|f| f.has_slnt) { - _use_slnt = true; - } else { - use_style = if set.iter().any(|f| f.style == FontStyle::Normal) { - FontStyle::Normal - } else { - set[0].style - }; - } - } else { - // Choose an italic style - if set.iter().any(|f| f.style == FontStyle::Italic) { - use_style = FontStyle::Italic; - } - // oblique values less than or equal to 0deg are checked in descending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a <= 0.) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - use_style = set[0].style; - } - } - } + self.min_oblique_angle_matching(|a| a >= OBLIQUE_THRESHOLD) + // followed by positive oblique values below 14deg in descending order + .or_else(|| { + self.max_oblique_angle_matching(|a| a > 0.0 && a < OBLIQUE_THRESHOLD) + }) + // If no match is found, oblique values less than or equal to 0deg + // are checked in descending order until a match is found. + .or_else(|| self.max_oblique_angle_matching(|a| a < 0.0)) + .unwrap_or(self.0[0].style) } - // if the requested angle is greater than or equal to 0deg - // and less than 14deg - else if angle >= 0. { - // positive oblique values below angle in descending order - if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a > 0. && *a < angle) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } - // followed by oblique values greater than or equal to angle in - // ascending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a >= angle) - .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - // If font-synthesis-style has the value auto, then for variable - // fonts with a slnt axis a match is created by setting the slnt - // value with the specified oblique value; otherwise, a fallback - // match is produced by geometric shearing to the specified - // oblique value. - if synthesize_style { - if set.iter().any(|f| f.has_slnt) { - _use_slnt = true; - } else { - use_style = if set.iter().any(|f| f.style == FontStyle::Normal) { - FontStyle::Normal + // If the value of font-style is oblique... + else if let Some(angle) = style.oblique_angle() { + // and the requested angle is greater than or equal to 14deg + if angle >= OBLIQUE_THRESHOLD { + // oblique values greater than or equal to angle are checked in + // ascending order + self.min_oblique_angle_matching(|a| a >= angle) + // followed by positive oblique values below angle in descending order + .or_else(|| self.max_oblique_angle_matching(|a| a > 0.0 && a < angle)) + .unwrap_or_else(|| { + // If font-synthesis-style has the value auto, then for variable + // fonts with a slnt axis a match is created by selfting the slnt + // value with the specified oblique value; otherwise, a fallback + // match is produced by geometric shearing to the specified + // oblique value. + if synthesize_style && self.has_variable_font_with_slnt_axis() { + _use_slnt = true; + style } else { - set[0].style - }; - } - } else { - // Choose an italic style - if set.iter().any(|f| f.style == FontStyle::Italic) { - use_style = FontStyle::Italic; - } - // oblique values less than or equal to 0deg are checked in descending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a <= 0.) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - use_style = set[0].style; - } - } - } - } - // -14deg < angle < 0deg - else if angle > -OBLIQUE_THRESHOLD { - // negative oblique values above angle in ascending order - if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a < 0. && *a > angle) - .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; + self.fallback_style(synthesize_style, |a| a <= 0.0) + } + }) } - // followed by oblique values less than or equal to angle in - // descending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a <= angle) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - // If font-synthesis-style has the value auto, then for variable - // fonts with a slnt axis a match is created by setting the slnt - // value with the specified oblique value; otherwise, a fallback - // match is produced by geometric shearing to the specified - // oblique value. - if synthesize_style { - if set.iter().any(|f| f.has_slnt) { - _use_slnt = true; - } else { - use_style = if set.iter().any(|f| f.style == FontStyle::Normal) { - FontStyle::Normal + // if the requested angle is greater than or equal to 0deg + // and less than 14deg + else if angle >= 0. { + // positive oblique values below angle in descending order + self.max_oblique_angle_matching(|a| a > 0.0 && a < angle) + // followed by oblique values greater than or equal to angle in + // ascending order + .or_else(|| self.min_oblique_angle_matching(|a| a >= angle)) + .unwrap_or_else(|| { + // If font-synthesis-style has the value auto, then for variable + // fonts with a slnt axis a match is created by selfting the slnt + // value with the specified oblique value; otherwise, a fallback + // match is produced by geometric shearing to the specified + // oblique value. + if synthesize_style && self.has_variable_font_with_slnt_axis() { + _use_slnt = true; + style } else { - set[0].style - }; - } - } else { - // Choose an italic style - if set.iter().any(|f| f.style == FontStyle::Italic) { - use_style = FontStyle::Italic; - } - // oblique values greater than or equal to 0deg are checked in ascending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a >= 0.) - .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - use_style = set[0].style; - } - } + self.fallback_style(synthesize_style, |a| a <= 0.0) + } + }) } - } - // angle < -14 deg - else { - // oblique values less than or equal to angle are checked in - // descending order - if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a <= angle) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; + // -14deg < angle < 0deg + else if angle > -OBLIQUE_THRESHOLD { + // negative oblique values above angle in ascending order + self.min_oblique_angle_matching(|a| a < 0. && a > angle) + // followed by oblique values less than or equal to angle in + // descending order + .or_else(|| self.max_oblique_angle_matching(|a| a <= angle)) + .unwrap_or_else(|| { + // If font-synthesis-style has the value auto, then for variable + // fonts with a slnt axis a match is created by selfting the slnt + // value with the specified oblique value; otherwise, a fallback + // match is produced by geometric shearing to the specified + // oblique value. + if synthesize_style && self.has_variable_font_with_slnt_axis() { + _use_slnt = true; + style + } else { + self.fallback_style(synthesize_style, |a| a >= 0.0) + } + }) } - // followed by negative oblique values above angle in ascending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a < 0. && *a > angle) - .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - // If font-synthesis-style has the value auto, then for variable - // fonts with a slnt axis a match is created by setting the slnt - // value with the specified oblique value; otherwise, a fallback - // match is produced by geometric shearing to the specified - // oblique value. - if synthesize_style { - if set.iter().any(|f| f.has_slnt) { - _use_slnt = true; - } else { - use_style = if set.iter().any(|f| f.style == FontStyle::Normal) { - FontStyle::Normal + // angle < -14 deg + else { + // oblique values less than or equal to angle are checked in + // descending order + self.max_oblique_angle_matching(|a| a >= angle) + // followed by negative oblique values above angle in ascending order + .or_else(|| self.min_oblique_angle_matching(|a| a < 0.0 && a > angle)) + .unwrap_or_else(|| { + // If font-synthesis-style has the value auto, then for variable + // fonts with a slnt axis a match is created by selfting the slnt + // value with the specified oblique value; otherwise, a fallback + // match is produced by geometric shearing to the specified + // oblique value. + if synthesize_style && self.has_variable_font_with_slnt_axis() { + _use_slnt = true; + style } else { - set[0].style - }; - } - } else { - // Choose an italic style - if set.iter().any(|f| f.style == FontStyle::Italic) { - use_style = FontStyle::Italic; - } - // oblique values greater than or equal to 0deg are checked in ascending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a >= 0.) - .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - use_style = set[0].style; - } - } + self.fallback_style(synthesize_style, |a| a >= 0.0) + } + }) } } - } - // If the value of font-style is normal... - else { - // oblique values greater than or equal to 0deg are checked in - // ascending order - if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a >= 0.) - .min_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } - // followed by italic fonts - else if let Some(found) = set.iter().find(|f| f.style == FontStyle::Italic) { - use_style = found.style; - } - // followed by oblique values less than 0deg in descending order - else if let Some(found) = oblique_fonts - .clone() - .filter(|(_, a)| *a < 0.) - .max_by(|x, y| x.1.partial_cmp(&y.1).unwrap_or(Less)) - { - use_style = found.0; - } else { - use_style = set[0].style; - } - } - } - set.retain(|f| f.style == use_style); - // font-weight is matched next: - if let Some(f) = set.iter().find(|f| f.weight == weight) { - return Some(f.index); - } else { - // If the desired weight is inclusively between 400 and 500... - if (400.0..=500.0).contains(&weight) { - // weights greater than or equal to the target weight are checked in ascending - // order until 500 is hit and checked - if let Some(found) = set - .iter() - .enumerate() - .filter(|f| f.1.weight >= weight && f.1.weight <= 500.0) - .min_by(|x, y| x.1.weight.partial_cmp(&y.1.weight).unwrap_or(Less)) - { - return Some(found.1.index); - } - // followed by weights less than the target weight in descending order - if let Some(found) = set - .iter() - .enumerate() - .filter(|f| f.1.weight < weight) - .max_by(|x, y| x.1.weight.partial_cmp(&y.1.weight).unwrap_or(Less)) - { - return Some(found.1.index); - } - // followed by weights greater than 500, until a match is found. - if let Some(found) = set - .iter() - .enumerate() - .filter(|f| f.1.weight > 500.0) - .min_by(|x, y| x.1.weight.partial_cmp(&y.1.weight).unwrap_or(Less)) - { - return Some(found.1.index); - } - } - // If the desired weight is less than 400... - else if weight < 400.0 { - // weights less than or equal to the target weight are checked in descending - if let Some(found) = set - .iter() - .filter(|f| f.weight <= weight) - .max_by(|x, y| x.weight.partial_cmp(&y.weight).unwrap_or(Less)) - { - return Some(found.index); + // If the value of font-style is normal... + else { + // oblique values greater than or equal to 0deg are checked in + // ascending order + self.min_oblique_angle_matching(|a| a >= 0.0) + // followed by italic fonts + .or_else(|| { + self.0 + .iter() + .find(|f| f.style == FontStyle::Italic) + .map(|f| f.style) + }) + // followed by oblique values less than 0deg in descending order + .or_else(|| self.max_oblique_angle_matching(|a| a < 0.0)) + .unwrap_or(self.0[0].style) } - // followed by weights greater than the target weight in ascending order - if let Some(found) = set - .iter() - .filter(|f| f.weight > weight) - .min_by(|x, y| x.weight.partial_cmp(&y.weight).unwrap_or(Less)) - { - return Some(found.index); + }; + + self.0.retain(|f| f.style == use_style); + + // font-weight is matched next: + if let Some(index) = self.0.iter().position(|f| f.weight == weight) { + Some(index) + } else { + // If the desired weight is inclusively between 400 and 500... + if (400.0..=500.0).contains(&weight) { + self + // weights greater than or equal to the target weight are checked in ascending + // order until 500 is hit and checked + .min_weight_matching(|w| w >= weight && w <= 500.0) + // followed by weights less than the target weight in descending order + .or_else(|| self.max_weight_matching(|w| w < weight)) + // followed by weights greater than 500, until a match is found. + .or_else(|| self.min_weight_matching(|w| w > 500.0)) } - } - // If the desired weight is greater than 500... - else { - // weights greater than or equal to the target weight are checked in ascending - if let Some(found) = set - .iter() - .filter(|f| f.weight >= weight) - .min_by(|x, y| x.weight.partial_cmp(&y.weight).unwrap_or(Less)) - { - return Some(found.index); + // If the desired weight is less than 400... + else if weight < 400.0 { + // weights less than or equal to the target weight are checked in descending order + self.max_weight_matching(|w| w < weight) + // followed by weights greater than the target weight in ascending order + .or_else(|| self.min_weight_matching(|w| w > weight)) } - // followed by weights less than the target weight in descending order - if let Some(found) = set - .iter() - .filter(|f| f.weight < weight) - .max_by(|x, y| x.weight.partial_cmp(&y.weight).unwrap_or(Less)) - { - return Some(found.index); + // If the desired weight is greater than 500... + else { + // weights greater than or equal to the target weight are checked in ascending + self.min_weight_matching(|w| w >= weight) + // followed by weights less than the target weight in descending order + .or_else(|| self.max_weight_matching(|w| w < weight)) } + .map(|found| found.index) } } - None -} - -fn oblique_angle(style: FontStyle) -> Option { - match style { - FontStyle::Oblique(angle) => Some(angle.unwrap_or(DEFAULT_OBLIQUE_ANGLE)), - _ => None, - } -} - -fn oblique_style(style: FontStyle) -> Option<(FontStyle, f32)> { - match style { - FontStyle::Oblique(angle) => Some((style, angle.unwrap_or(DEFAULT_OBLIQUE_ANGLE))), - _ => None, - } }