Add math.scr

This commit is contained in:
mkorje 2025-06-24 23:16:22 +10:00
parent b37f57bdec
commit 4b13abc728
No known key found for this signature in database
8 changed files with 54 additions and 53 deletions

2
Cargo.lock generated
View File

@ -413,7 +413,7 @@ dependencies = [
[[package]] [[package]]
name = "codex" name = "codex"
version = "0.1.1" version = "0.1.1"
source = "git+https://github.com/mkorje/codex?rev=f02fa4a#f02fa4a04f2a9937f043892ee865dd06628264ba" source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00"
[[package]] [[package]]
name = "color-print" name = "color-print"

View File

@ -47,7 +47,7 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] }
clap_complete = "4.2.1" clap_complete = "4.2.1"
clap_mangen = "0.2.10" clap_mangen = "0.2.10"
codespan-reporting = "0.11" codespan-reporting = "0.11"
codex = { git = "https://github.com/mkorje/codex", rev = "f02fa4a" } codex = { git = "https://github.com/typst/codex", rev = "9ac86f9" }
color-print = "0.3.6" color-print = "0.3.6"
comemo = "0.4" comemo = "0.4"
csv = "1" csv = "1"

View File

@ -65,12 +65,21 @@ fn layout_inline_text(
ctx: &mut MathContext, ctx: &mut MathContext,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<FrameFragment> { ) -> SourceResult<FrameFragment> {
let variant = EquationElem::variant_in(styles);
let bold = EquationElem::bold_in(styles);
// Disable auto-italic.
let italic = EquationElem::italic_in(styles).or(Some(false));
if text.chars().all(|c| c.is_ascii_digit() || c == '.') { if text.chars().all(|c| c.is_ascii_digit() || c == '.') {
// Small optimization for numbers. Note that this lays out slightly // Small optimization for numbers. Note that this lays out slightly
// differently to normal text and is worth re-evaluating in the future. // differently to normal text and is worth re-evaluating in the future.
let mut fragments = vec![]; let mut fragments = vec![];
for unstyled_c in text.chars() { for unstyled_c in text.chars() {
let c = styled_char(styles, unstyled_c, false); // This is fine as ascii digits and '.' can never end up as more
// than a single char after styling.
let style = MathStyle::select(unstyled_c, variant, bold, italic);
let c = to_style(unstyled_c, style).next().unwrap();
let glyph = GlyphFragment::new_char(ctx.font, styles, c, span)?; let glyph = GlyphFragment::new_char(ctx.font, styles, c, span)?;
fragments.push(glyph.into()); fragments.push(glyph.into());
} }
@ -84,8 +93,10 @@ fn layout_inline_text(
.map(|p| p.wrap()); .map(|p| p.wrap());
let styles = styles.chain(&local); let styles = styles.chain(&local);
let styled_text: EcoString = let styled_text: EcoString = text
text.chars().map(|c| styled_char(styles, c, false)).collect(); .chars()
.flat_map(|c| to_style(c, MathStyle::select(c, variant, bold, italic)))
.collect();
let spaced = styled_text.graphemes(true).nth(1).is_some(); let spaced = styled_text.graphemes(true).nth(1).is_some();
let elem = TextElem::packed(styled_text).spanned(span); let elem = TextElem::packed(styled_text).spanned(span);
@ -125,9 +136,16 @@ pub fn layout_symbol(
Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)), Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)),
_ => (elem.text, styles), _ => (elem.text, styles),
}; };
let c = styled_char(styles, unstyled_c, true);
let variant = EquationElem::variant_in(styles);
let bold = EquationElem::bold_in(styles);
let italic = EquationElem::italic_in(styles);
let style = MathStyle::select(unstyled_c, variant, bold, italic);
let text: EcoString = to_style(unstyled_c, style).collect();
let fragment: MathFragment = let fragment: MathFragment =
match GlyphFragment::new_char(ctx.font, symbol_styles, c, elem.span()) { match GlyphFragment::new(ctx.font, symbol_styles, &text, elem.span()) {
Ok(mut glyph) => { Ok(mut glyph) => {
adjust_glyph_layout(&mut glyph, ctx, styles); adjust_glyph_layout(&mut glyph, ctx, styles);
glyph.into() glyph.into()
@ -135,8 +153,7 @@ pub fn layout_symbol(
Err(_) => { Err(_) => {
// Not in the math font, fallback to normal inline text layout. // Not in the math font, fallback to normal inline text layout.
// TODO: Should replace this with proper fallback in [`GlyphFragment::new`]. // TODO: Should replace this with proper fallback in [`GlyphFragment::new`].
layout_inline_text(c.encode_utf8(&mut [0; 4]), elem.span(), ctx, styles)? layout_inline_text(&text, elem.span(), ctx, styles)?.into()
.into()
} }
}; };
ctx.push(fragment); ctx.push(fragment);
@ -162,38 +179,6 @@ fn adjust_glyph_layout(
} }
} }
/// Style the character by selecting the Unicode codepoint for italic, bold,
/// caligraphic, etc.
fn styled_char(styles: StyleChain, c: char, auto_italic: bool) -> char {
if let Some(c) = basic_exception(c) {
return c;
}
let variant = EquationElem::variant_in(styles);
let bold = EquationElem::bold_in(styles);
let italic =
EquationElem::italic_in(styles).or_else(|| (!auto_italic).then_some(false));
let style = MathStyle::select(c, variant, bold, italic);
// At the moment we are only using styles that output a single character,
// so we just grab the first character in the ToStyle iterator.
to_style(c, style).next().unwrap()
}
fn basic_exception(c: char) -> Option<char> {
Some(match c {
'〈' => '⟨',
'〉' => '⟩',
'《' => '⟪',
'》' => '⟫',
'א' => 'ℵ',
'ב' => 'ℶ',
'ג' => 'ℷ',
'ד' => 'ℸ',
_ => return None,
})
}
/// The non-dotless version of a dotless character that can be used with the /// The non-dotless version of a dotless character that can be used with the
/// `dtls` OpenType feature. /// `dtls` OpenType feature.
pub fn try_dotless(c: char) -> Option<char> { pub fn try_dotless(c: char) -> Option<char> {

View File

@ -80,6 +80,7 @@ pub fn module() -> Module {
math.define_func::<italic>(); math.define_func::<italic>();
math.define_func::<serif>(); math.define_func::<serif>();
math.define_func::<sans>(); math.define_func::<sans>();
math.define_func::<scr>();
math.define_func::<cal>(); math.define_func::<cal>();
math.define_func::<frak>(); math.define_func::<frak>();
math.define_func::<mono>(); math.define_func::<mono>();

View File

@ -64,20 +64,34 @@ pub fn sans(
body.styled(EquationElem::set_variant(Some(MathVariant::SansSerif))) body.styled(EquationElem::set_variant(Some(MathVariant::SansSerif)))
} }
/// Calligraphic font style in math. /// Calligraphic (chancery) font style in math.
/// ///
/// ```example /// ```example
/// Let $cal(P)$ be the set of ... /// Let $cal(P)$ be the set of ...
/// ``` /// ```
/// ///
/// This corresponds both to LaTeX's `\mathcal` and `\mathscr` as both of these /// This is the default calligraphic/script style for most math fonts. See
/// styles share the same Unicode codepoints. Switching between the styles is /// [`scr`]($math.scr) for more on how to get the other style (roundhand).
/// thus only possible if supported by the font via #[func(title = "Calligraphic", keywords = ["mathcal", "chancery"])]
pub fn cal(
/// The content to style.
body: Content,
) -> Content {
body.styled(EquationElem::set_variant(Some(MathVariant::Chancery)))
}
/// Script (roundhand) font style in math.
///
/// ```example
/// $ scr(S) $
/// ```
///
/// Very few math fonts currently support differentiating `cal` and `scr`. Some
/// fonts support switching between the styles via
/// [font features]($text.features). /// [font features]($text.features).
/// ///
/// For the default math font, the roundhand style is available through the /// Say, for example, the roundhand style is available through the `ss01`
/// `ss01` feature. Therefore, you could define your own version of `\mathscr` /// feature. Then, you could define your own version of `\mathscr` like this:
/// like this:
/// ///
/// ```example /// ```example
/// #let scr(it) = text( /// #let scr(it) = text(
@ -90,12 +104,12 @@ pub fn sans(
/// ///
/// (The box is not conceptually necessary, but unfortunately currently needed /// (The box is not conceptually necessary, but unfortunately currently needed
/// due to limitations in Typst's text style handling in math.) /// due to limitations in Typst's text style handling in math.)
#[func(title = "Calligraphic", keywords = ["mathcal", "mathscr"])] #[func(title = "Script", keywords = ["mathscr", "roundhand"])]
pub fn cal( pub fn scr(
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
body.styled(EquationElem::set_variant(Some(MathVariant::Script))) body.styled(EquationElem::set_variant(Some(MathVariant::Roundhand)))
} }
/// Fraktur font style in math. /// Fraktur font style in math.

View File

@ -5,7 +5,7 @@
title: Variants title: Variants
category: math category: math
path: ["math"] path: ["math"]
filter: ["serif", "sans", "frak", "mono", "bb", "cal"] filter: ["serif", "sans", "frak", "mono", "bb", "cal", "scr"]
details: | details: |
Alternate typefaces within formulas. Alternate typefaces within formulas.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 489 B

View File

@ -47,7 +47,8 @@ $bb(Gamma) , bb(gamma), bb(Pi), bb(pi), bb(sum)$
--- math-style-hebrew-exceptions --- --- math-style-hebrew-exceptions ---
// Test hebrew exceptions. // Test hebrew exceptions.
$aleph, beth, gimel, daleth$ $aleph, beth, gimel, daleth$ \
$upright(aleph), upright(beth), upright(gimel), upright(daleth)$
--- issue-3650-italic-equation --- --- issue-3650-italic-equation ---
_abc $sin(x) "abc"$_ \ _abc $sin(x) "abc"$_ \