use std::{fmt::Write, ops::Deref};
use topcoat_core::{context::Cx, fnv1a::Fnv1a};
use crate::{CssString, FontFormat, FontTech};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FontSourceUrl {
Str(String),
#[cfg(feature = "asset")]
Asset(topcoat_asset::Asset),
}
impl FontSourceUrl {
#[cfg_attr(not(feature = "asset"), expect(unused_variables))]
#[track_caller]
pub fn fmt(&self, cx: &Cx, f: &mut dyn Write) -> std::fmt::Result {
let mut f = CssString(f);
match self {
Self::Str(inner) => f.write_str(inner),
#[cfg(feature = "asset")]
Self::Asset(inner) => topcoat_asset::asset_config(cx).fmt_url(*inner, &mut f),
}
}
#[must_use]
pub fn is_str(&self) -> bool {
matches!(self, Self::Str(..))
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Str(v) => Some(v),
#[cfg(feature = "asset")]
Self::Asset(_) => None,
}
}
#[must_use]
#[cfg(feature = "asset")]
pub fn is_asset(&self) -> bool {
matches!(self, Self::Asset(..))
}
#[must_use]
#[cfg(feature = "asset")]
pub fn as_asset(&self) -> Option<&topcoat_asset::Asset> {
match self {
Self::Asset(v) => Some(v),
Self::Str(_) => None,
}
}
pub(crate) fn hash(&self, h: Fnv1a<u64>) -> Fnv1a<u64> {
match self {
Self::Str(inner) => h.write(b"s").write(inner.as_bytes()),
#[cfg(feature = "asset")]
Self::Asset(inner) => h.write(b"a").write(&inner.id().as_u64().to_le_bytes()),
}
}
}
impl From<&str> for FontSourceUrl {
fn from(v: &str) -> Self {
Self::Str(v.to_owned())
}
}
impl From<String> for FontSourceUrl {
fn from(v: String) -> Self {
Self::Str(v)
}
}
#[cfg(feature = "asset")]
impl From<topcoat_asset::Asset> for FontSourceUrl {
fn from(v: topcoat_asset::Asset) -> Self {
Self::Asset(v)
}
}
#[cfg(feature = "view")]
impl topcoat_view::AttributeValueViewParts for FontSourceUrl {
fn attribute_present(&self) -> bool {
true
}
fn into_view_parts(
self,
cx: &topcoat_core::context::Cx,
parts: &mut topcoat_view::PartsWriter<'_>,
) {
match self {
Self::Str(inner) => inner.into_view_parts(cx, parts),
#[cfg(feature = "asset")]
Self::Asset(inner) => inner.into_view_parts(cx, parts),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FontSource {
Url {
url: FontSourceUrl,
format: Option<FontFormat>,
tech: Option<FontTech>,
},
Local {
name: String,
},
}
impl FontSource {
#[must_use]
pub fn url(
url: impl Into<FontSourceUrl>,
format: Option<FontFormat>,
tech: Option<FontTech>,
) -> Self {
Self::Url {
url: url.into(),
format,
tech,
}
}
#[must_use]
pub fn local(name: impl Into<String>) -> Self {
Self::Local { name: name.into() }
}
#[track_caller]
pub fn fmt(&self, cx: &Cx, f: &mut dyn Write) -> std::fmt::Result {
match self {
Self::Url { url, format, tech } => {
f.write_str("url(\"")?;
url.fmt(cx, &mut *f)?;
f.write_str("\")")?;
if let Some(format) = format {
write!(f, " format({format})")?;
}
if let Some(tech) = tech {
write!(f, " tech({tech})")?;
}
}
Self::Local { name } => {
f.write_str("local(\"")?;
CssString(f).write_str(name)?;
f.write_str("\")")?;
}
}
Ok(())
}
#[must_use]
pub fn is_url(&self) -> bool {
matches!(self, Self::Url { .. })
}
#[must_use]
pub fn is_local(&self) -> bool {
matches!(self, Self::Local { .. })
}
pub(crate) fn hash(&self, h: Fnv1a<u64>) -> Fnv1a<u64> {
match self {
Self::Url { url, format, tech } => {
let h = url.hash(h.write(b"u"));
let h = match format {
Some(format) => format.hash(h.write(&[1])),
None => h.write(&[0]),
};
match tech {
Some(tech) => tech.hash(h.write(&[1])),
None => h.write(&[0]),
}
}
Self::Local { name } => h.write(b"l").write(name.as_bytes()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FontSources(Vec<FontSource>);
impl FontSources {
#[must_use]
#[track_caller]
pub fn new(sources: impl Into<Vec<FontSource>>) -> Self {
let sources = sources.into();
assert!(!sources.is_empty(), "font sources must not be empty");
Self(sources)
}
pub(crate) fn hash(&self, mut h: Fnv1a<u64>) -> Fnv1a<u64> {
for source in &self.0 {
h = source.hash(h);
}
h
}
#[track_caller]
pub fn fmt(&self, cx: &Cx, f: &mut dyn Write) -> std::fmt::Result {
for (index, source) in self.0.iter().enumerate() {
if index > 0 {
f.write_str(", ")?;
}
source.fmt(cx, f)?;
}
Ok(())
}
#[must_use]
pub fn as_slice(&self) -> &[FontSource] {
&self.0
}
fn try_from_vec(sources: Vec<FontSource>) -> Result<Self, EmptyFontSourcesError> {
if sources.is_empty() {
return Err(EmptyFontSourcesError);
}
Ok(Self(sources))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmptyFontSourcesError;
impl std::fmt::Display for EmptyFontSourcesError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("font sources must not be empty")
}
}
impl std::error::Error for EmptyFontSourcesError {}
impl TryFrom<Vec<FontSource>> for FontSources {
type Error = EmptyFontSourcesError;
fn try_from(sources: Vec<FontSource>) -> Result<Self, Self::Error> {
Self::try_from_vec(sources)
}
}
impl TryFrom<&[FontSource]> for FontSources {
type Error = EmptyFontSourcesError;
fn try_from(sources: &[FontSource]) -> Result<Self, Self::Error> {
Self::try_from_vec(sources.to_vec())
}
}
impl Deref for FontSources {
type Target = [FontSource];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}