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

Code before:
        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.
    pub fn as_str(&self) -> &str {
        &self.name
    }

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

    pub fn coverage_str(&self) -> Option<&str> {
        self.coverage.as_ref().map(|r| r.as_str())
    }
}

cast! {
    FontFamily,

Review comment (bug):
I don't think we need to restrict the regex in this way, to be honest. If the regex is multi-char, it just won't be a match, that's on the user. Perhaps attention should be drawn to this on the docs, but matching on the regex syntax seems a bit bug-prone.

Code after:
    /// 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 {
    /// Retrieve the regex for the coverage.
    pub fn as_regex(&self) -> &Regex {
        match self {
            Self::LatinInCjk => singleton!(
                Regex,
                Regex::new(
                    "[^\u{00B7}\u{2013}\u{2014}\u{2018}\u{2019}\
                       \u{201C}\u{201D}\u{2025}-\u{2027}\u{2E3A}]"
                )
                .unwrap()
            ),
            Self::Regex(regex) => regex,
        }
    }
}