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

Code before:
    }
}

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"),
                }
                Some(regex)
            }
            None => None,
        };
        Ok(Self { name: string.to_lowercase().into(), coverage })
    }

    /// The lowercased family name.

Review comment (suggestion):
If we keep the regex, it can use the `typst_utils::singleton` macro

Code after:
    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)]
pub enum Covers {
    /// Covers all codepoints except those used both in Latin and CJK fonts.
    LatinInCjk,
    /// Covers the set of codepoints for which the regex matches.
    Regex(Regex),
}

impl Covers {