use std::{fmt::Write, ops::Deref};
use topcoat_core::{context::Cx, fnv1a::Fnv1a};
use crate::{CssString, FontDisplay, FontSources, FontStyle, FontWeightRange, UnicodeRanges};
#[derive(Debug, Clone, PartialEq)]
pub struct FontFace {
family: String,
src: FontSources,
weight: Option<FontWeightRange>,
style: Option<FontStyle>,
display: Option<FontDisplay>,
unicode_range: Option<UnicodeRanges>,
}
impl FontFace {
#[must_use]
#[track_caller]
pub fn new(family: impl Into<String>, src: impl TryInto<FontSources>) -> Self {
Self {
family: family.into(),
src: src
.try_into()
.unwrap_or_else(|_| panic!("font sources must not be empty")),
weight: None,
style: None,
display: None,
unicode_range: None,
}
}
#[must_use]
pub fn with_weight(mut self, weight: FontWeightRange) -> Self {
self.weight = Some(weight);
self
}
#[must_use]
pub fn with_style(mut self, style: FontStyle) -> Self {
self.style = Some(style);
self
}
#[must_use]
pub fn with_display(mut self, display: FontDisplay) -> Self {
self.display = Some(display);
self
}
#[must_use]
pub fn with_unicode_range(mut self, unicode_range: UnicodeRanges) -> Self {
self.unicode_range = Some(unicode_range);
self
}
#[track_caller]
pub fn fmt(&self, cx: &Cx, f: &mut dyn Write) -> std::fmt::Result {
f.write_str("@font-face { font-family: \"")?;
CssString(&mut *f).write_str(&self.family)?;
f.write_str("\"; src: ")?;
self.src.fmt(cx, &mut *f)?;
if let Some(weight) = self.weight {
write!(f, "; font-weight: {weight}")?;
}
if let Some(style) = self.style {
write!(f, "; font-style: {style}")?;
}
if let Some(display) = self.display {
write!(f, "; font-display: {display}")?;
}
if let Some(unicode_range) = self.unicode_range {
write!(f, "; unicode-range: {unicode_range}")?;
}
f.write_str(" }")?;
Ok(())
}
pub(crate) fn hash(&self, h: Fnv1a<u64>) -> Fnv1a<u64> {
let h = h.write(self.family.as_bytes());
let h = self.src.hash(h);
let h = match self.weight {
Some(weight) => weight.hash(h.write(&[1])),
None => h.write(&[0]),
};
let h = match self.style {
Some(style) => style.hash(h.write(&[1])),
None => h.write(&[0]),
};
let h = match self.display {
Some(display) => display.hash(h.write(&[1])),
None => h.write(&[0]),
};
match self.unicode_range {
Some(unicode_range) => unicode_range.hash(h.write(&[1])),
None => h.write(&[0]),
}
}
#[must_use]
pub fn family(&self) -> &str {
&self.family
}
#[must_use]
pub fn src(&self) -> &FontSources {
&self.src
}
#[must_use]
pub fn weight(&self) -> Option<FontWeightRange> {
self.weight
}
#[must_use]
pub fn style(&self) -> Option<FontStyle> {
self.style
}
#[must_use]
pub fn display(&self) -> Option<FontDisplay> {
self.display
}
#[must_use]
pub fn unicode_range(&self) -> Option<UnicodeRanges> {
self.unicode_range
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FontFaces(Vec<FontFace>);
impl FontFaces {
#[must_use]
#[track_caller]
pub fn new(faces: impl Into<Vec<FontFace>>) -> Self {
let faces = faces.into();
assert!(!faces.is_empty(), "font faces must not be empty");
Self(faces)
}
pub(crate) fn hash(&self, mut h: Fnv1a<u64>) -> Fnv1a<u64> {
for face in &self.0 {
h = face.hash(h);
}
h
}
#[track_caller]
pub fn fmt(&self, cx: &Cx, f: &mut dyn Write) -> std::fmt::Result {
for (index, face) in self.0.iter().enumerate() {
if index > 0 {
f.write_str(" ")?;
}
face.fmt(cx, &mut *f)?;
}
Ok(())
}
#[must_use]
pub fn as_slice(&self) -> &[FontFace] {
&self.0
}
fn try_from_vec(faces: Vec<FontFace>) -> Result<Self, EmptyFontFacesError> {
if faces.is_empty() {
return Err(EmptyFontFacesError);
}
Ok(Self(faces))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmptyFontFacesError;
impl std::fmt::Display for EmptyFontFacesError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("font faces must not be empty")
}
}
impl std::error::Error for EmptyFontFacesError {}
impl TryFrom<Vec<FontFace>> for FontFaces {
type Error = EmptyFontFacesError;
fn try_from(faces: Vec<FontFace>) -> Result<Self, Self::Error> {
Self::try_from_vec(faces)
}
}
impl TryFrom<&[FontFace]> for FontFaces {
type Error = EmptyFontFacesError;
fn try_from(faces: &[FontFace]) -> Result<Self, Self::Error> {
Self::try_from_vec(faces.to_vec())
}
}
impl Deref for FontFaces {
type Target = [FontFace];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}