Repository: typst/typst
File: crates/typst-library/src/text/mod.rs (Rust)

Code before:
    name: EcoString,
    coverage: Option<Regex>,
}

impl Hash for FontFamily {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
        self.coverage_str().hash(state);
    }
}

impl PartialEq for FontFamily {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name && self.coverage_str() == other.coverage_str()
    }
}

impl FontFamily {
    /// Create a named font family variant.
    pub fn new(string: &str) -> Self {
        Self::new_with_coverage(string, None).unwrap()
    }

    pub fn new_with_coverage(
        string: &str,
        coverage: Option<Value>,
    ) -> HintedStrResult<Self> {
        let coverage = match coverage {
            Some(Value::Str(name)) => {
                if &*name == "latin-in-cjk" {
                    static LATIN_IN_CJK_REGEX: LazyLock<Regex> = LazyLock::new(|| {
                        Regex::new( "[^\u{00B7}\u{2013}\u{2014}\u{2018}\u{2019}\u{201C}\u{201D}\u{2025}-\u{2027}\u{2E3A}]").unwrap()
                    });
                    Some(LATIN_IN_CJK_REGEX.clone())
                } else {
                    bail!("unknown character set");
                }
            }
            Some(coverage) => {
                let regex = coverage.cast::<Regex>()?;
                let ast = regex_syntax::ast::parse::Parser::new()
                    .parse(regex.as_str())
                    .unwrap();
                match ast {
                    regex_syntax::ast::Ast::ClassBracketed(..)
                    | regex_syntax::ast::Ast::ClassUnicode(..)
                    | regex_syntax::ast::Ast::ClassPerl(..)
                    | regex_syntax::ast::Ast::Dot(..)
                    | regex_syntax::ast::Ast::Literal(..) => (),
                    _ => bail!("invalid regex in font coverage"),
                }

Review comment (suggestion):
I think we could have a new `enum Covers { LatinInCjk, Regex(Regex) }` type that uses `cast!` to cast from a string or regex without allowing an arbitrary `Value` here.

We could even use that across the board, never converting LatinInCjk to a regex in the first place.

Code after:
        text.push_str(self.text());
    }
}

/// A lowercased font family like "arial".
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct FontFamily {
    // The name of the font family
    name: EcoString,
    // A regex that defines the Unicode codepoints supported by the font.
    covers: Option<Covers>,
}

impl FontFamily {
    /// Create a named font family variant.
    pub fn new(string: &str) -> Self {
        Self::with_coverage(string, None)
    }

    /// Create a font family by name and optional Unicode coverage.
    pub fn with_coverage(string: &str, covers: Option<Covers>) -> Self {
        Self { name: string.to_lowercase().into(), covers }
    }

    /// The lowercased family name.
    pub fn as_str(&self) -> &str {
        &self.name
    }

    /// The user-set coverage of the font family.
    pub fn covers(&self) -> Option<&Regex> {
        self.covers.as_ref().map(|covers| covers.as_regex())
    }
}

cast! {
    FontFamily,
    self => self.name.into_value(),
    string: EcoString => Self::new(&string),
    mut v: Dict => {
        let ret = Self::with_coverage(
            &v.take("name")?.cast::<EcoString>()?,
            v.take("covers").ok().map(|v| v.cast()).transpose()?
        );
        v.finish(&["name", "covers"])?;
        ret
    },
}

/// Defines which codepoints a font family will be used for.
#[derive(Debug, Clone, PartialEq, Hash)]