From fa0174f3cff017e0b0c9162f34f2509dafadc877 Mon Sep 17 00:00:00 2001 From: mkorje Date: Tue, 24 Jun 2025 21:13:47 +1000 Subject: [PATCH 1/4] Move math styling to codex --- Cargo.lock | 3 +- Cargo.toml | 2 +- crates/typst-layout/Cargo.toml | 1 + crates/typst-layout/src/math/text.rs | 217 ++-------------------- crates/typst-library/src/math/equation.rs | 7 +- crates/typst-library/src/math/style.rs | 32 ++-- tests/ref/math-style-fallback.png | Bin 0 -> 935 bytes tests/suite/math/style.typ | 11 +- 8 files changed, 44 insertions(+), 229 deletions(-) create mode 100644 tests/ref/math-style-fallback.png diff --git a/Cargo.lock b/Cargo.lock index 550c4141a..87ce0d3f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,7 +413,7 @@ dependencies = [ [[package]] name = "codex" version = "0.1.1" -source = "git+https://github.com/typst/codex?rev=a5428cb#a5428cb9c81a41354d44b44dbd5a16a710bbd928" +source = "git+https://github.com/mkorje/codex?rev=f02fa4a#f02fa4a04f2a9937f043892ee865dd06628264ba" [[package]] name = "color-print" @@ -3028,6 +3028,7 @@ version = "0.13.1" dependencies = [ "az", "bumpalo", + "codex", "comemo", "ecow", "hypher", diff --git a/Cargo.toml b/Cargo.toml index 6cc59ee89..c2a58421d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] } clap_complete = "4.2.1" clap_mangen = "0.2.10" codespan-reporting = "0.11" -codex = { git = "https://github.com/typst/codex", rev = "a5428cb" } +codex = { git = "https://github.com/mkorje/codex", rev = "f02fa4a" } color-print = "0.3.6" comemo = "0.4" csv = "1" diff --git a/crates/typst-layout/Cargo.toml b/crates/typst-layout/Cargo.toml index cc355a3db..2c314e5c5 100644 --- a/crates/typst-layout/Cargo.toml +++ b/crates/typst-layout/Cargo.toml @@ -21,6 +21,7 @@ typst-timing = { workspace = true } typst-utils = { workspace = true } az = { workspace = true } bumpalo = { workspace = true } +codex = { workspace = true } comemo = { workspace = true } ecow = { workspace = true } hypher = { workspace = true } diff --git a/crates/typst-layout/src/math/text.rs b/crates/typst-layout/src/math/text.rs index 67dc0a2c8..d13251e61 100644 --- a/crates/typst-layout/src/math/text.rs +++ b/crates/typst-layout/src/math/text.rs @@ -1,10 +1,11 @@ use std::f64::consts::SQRT_2; +use codex::styling::{to_style, MathStyle}; use ecow::EcoString; use typst_library::diag::SourceResult; use typst_library::foundations::{Packed, StyleChain, SymbolElem}; use typst_library::layout::{Abs, Size}; -use typst_library::math::{EquationElem, MathSize, MathVariant}; +use typst_library::math::{EquationElem, MathSize}; use typst_library::text::{ BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric, }; @@ -161,127 +162,22 @@ fn adjust_glyph_layout( } } -/// Style the character by selecting the unicode codepoint for italic, bold, +/// Style the character by selecting the Unicode codepoint for italic, bold, /// caligraphic, etc. -/// -/// -/// fn styled_char(styles: StyleChain, c: char, auto_italic: bool) -> char { - use MathVariant::*; - - let variant = EquationElem::variant_in(styles); - let bold = EquationElem::bold_in(styles); - let italic = EquationElem::italic_in(styles).unwrap_or( - auto_italic - && matches!( - c, - 'a'..='z' | 'ħ' | 'ı' | 'ȷ' | 'A'..='Z' | - 'α'..='ω' | '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ' - ) - && matches!(variant, Sans | Serif), - ); - if let Some(c) = basic_exception(c) { return c; } - if let Some(c) = latin_exception(c, variant, bold, italic) { - 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); - if let Some(c) = greek_exception(c, variant, bold, italic) { - return c; - } - - let base = match c { - 'A'..='Z' => 'A', - 'a'..='z' => 'a', - 'Α'..='Ω' => 'Α', - 'α'..='ω' => 'α', - '0'..='9' => '0', - // Hebrew Alef -> Dalet. - '\u{05D0}'..='\u{05D3}' => '\u{05D0}', - _ => return c, - }; - - let tuple = (variant, bold, italic); - let start = match c { - // Latin upper. - 'A'..='Z' => match tuple { - (Serif, false, false) => 0x0041, - (Serif, true, false) => 0x1D400, - (Serif, false, true) => 0x1D434, - (Serif, true, true) => 0x1D468, - (Sans, false, false) => 0x1D5A0, - (Sans, true, false) => 0x1D5D4, - (Sans, false, true) => 0x1D608, - (Sans, true, true) => 0x1D63C, - (Cal, false, _) => 0x1D49C, - (Cal, true, _) => 0x1D4D0, - (Frak, false, _) => 0x1D504, - (Frak, true, _) => 0x1D56C, - (Mono, _, _) => 0x1D670, - (Bb, _, _) => 0x1D538, - }, - - // Latin lower. - 'a'..='z' => match tuple { - (Serif, false, false) => 0x0061, - (Serif, true, false) => 0x1D41A, - (Serif, false, true) => 0x1D44E, - (Serif, true, true) => 0x1D482, - (Sans, false, false) => 0x1D5BA, - (Sans, true, false) => 0x1D5EE, - (Sans, false, true) => 0x1D622, - (Sans, true, true) => 0x1D656, - (Cal, false, _) => 0x1D4B6, - (Cal, true, _) => 0x1D4EA, - (Frak, false, _) => 0x1D51E, - (Frak, true, _) => 0x1D586, - (Mono, _, _) => 0x1D68A, - (Bb, _, _) => 0x1D552, - }, - - // Greek upper. - 'Α'..='Ω' => match tuple { - (Serif, false, false) => 0x0391, - (Serif, true, false) => 0x1D6A8, - (Serif, false, true) => 0x1D6E2, - (Serif, true, true) => 0x1D71C, - (Sans, _, false) => 0x1D756, - (Sans, _, true) => 0x1D790, - (Cal | Frak | Mono | Bb, _, _) => return c, - }, - - // Greek lower. - 'α'..='ω' => match tuple { - (Serif, false, false) => 0x03B1, - (Serif, true, false) => 0x1D6C2, - (Serif, false, true) => 0x1D6FC, - (Serif, true, true) => 0x1D736, - (Sans, _, false) => 0x1D770, - (Sans, _, true) => 0x1D7AA, - (Cal | Frak | Mono | Bb, _, _) => return c, - }, - - // Hebrew Alef -> Dalet. - '\u{05D0}'..='\u{05D3}' => 0x2135, - - // Numbers. - '0'..='9' => match tuple { - (Serif, false, _) => 0x0030, - (Serif, true, _) => 0x1D7CE, - (Bb, _, _) => 0x1D7D8, - (Sans, false, _) => 0x1D7E2, - (Sans, true, _) => 0x1D7EC, - (Mono, _, _) => 0x1D7F6, - (Cal | Frak, _, _) => return c, - }, - - _ => unreachable!(), - }; - - std::char::from_u32(start + (c as u32 - base as u32)).unwrap() + // 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 { @@ -290,93 +186,10 @@ fn basic_exception(c: char) -> Option { '〉' => '⟩', '《' => '⟪', '》' => '⟫', - _ => return None, - }) -} - -fn latin_exception( - c: char, - variant: MathVariant, - bold: bool, - italic: bool, -) -> Option { - use MathVariant::*; - Some(match (c, variant, bold, italic) { - ('B', Cal, false, _) => 'ℬ', - ('E', Cal, false, _) => 'ℰ', - ('F', Cal, false, _) => 'ℱ', - ('H', Cal, false, _) => 'ℋ', - ('I', Cal, false, _) => 'ℐ', - ('L', Cal, false, _) => 'ℒ', - ('M', Cal, false, _) => 'ℳ', - ('R', Cal, false, _) => 'ℛ', - ('C', Frak, false, _) => 'ℭ', - ('H', Frak, false, _) => 'ℌ', - ('I', Frak, false, _) => 'ℑ', - ('R', Frak, false, _) => 'ℜ', - ('Z', Frak, false, _) => 'ℨ', - ('C', Bb, ..) => 'ℂ', - ('H', Bb, ..) => 'ℍ', - ('N', Bb, ..) => 'ℕ', - ('P', Bb, ..) => 'ℙ', - ('Q', Bb, ..) => 'ℚ', - ('R', Bb, ..) => 'ℝ', - ('Z', Bb, ..) => 'ℤ', - ('D', Bb, _, true) => 'ⅅ', - ('d', Bb, _, true) => 'ⅆ', - ('e', Bb, _, true) => 'ⅇ', - ('i', Bb, _, true) => 'ⅈ', - ('j', Bb, _, true) => 'ⅉ', - ('h', Serif, false, true) => 'ℎ', - ('e', Cal, false, _) => 'ℯ', - ('g', Cal, false, _) => 'ℊ', - ('o', Cal, false, _) => 'ℴ', - ('ħ', Serif, .., true) => 'ℏ', - ('ı', Serif, .., true) => '𝚤', - ('ȷ', Serif, .., true) => '𝚥', - _ => return None, - }) -} - -fn greek_exception( - c: char, - variant: MathVariant, - bold: bool, - italic: bool, -) -> Option { - use MathVariant::*; - if c == 'Ϝ' && variant == Serif && bold { - return Some('𝟊'); - } - if c == 'ϝ' && variant == Serif && bold { - return Some('𝟋'); - } - - let list = match c { - 'ϴ' => ['𝚹', '𝛳', '𝜭', '𝝧', '𝞡', 'ϴ'], - '∇' => ['𝛁', '𝛻', '𝜵', '𝝯', '𝞩', '∇'], - '∂' => ['𝛛', '𝜕', '𝝏', '𝞉', '𝟃', '∂'], - 'ϵ' => ['𝛜', '𝜖', '𝝐', '𝞊', '𝟄', 'ϵ'], - 'ϑ' => ['𝛝', '𝜗', '𝝑', '𝞋', '𝟅', 'ϑ'], - 'ϰ' => ['𝛞', '𝜘', '𝝒', '𝞌', '𝟆', 'ϰ'], - 'ϕ' => ['𝛟', '𝜙', '𝝓', '𝞍', '𝟇', 'ϕ'], - 'ϱ' => ['𝛠', '𝜚', '𝝔', '𝞎', '𝟈', 'ϱ'], - 'ϖ' => ['𝛡', '𝜛', '𝝕', '𝞏', '𝟉', 'ϖ'], - 'Γ' => ['𝚪', '𝛤', '𝜞', '𝝘', '𝞒', 'ℾ'], - 'γ' => ['𝛄', '𝛾', '𝜸', '𝝲', '𝞬', 'ℽ'], - 'Π' => ['𝚷', '𝛱', '𝜫', '𝝥', '𝞟', 'ℿ'], - 'π' => ['𝛑', '𝜋', '𝝅', '𝝿', '𝞹', 'ℼ'], - '∑' => ['∑', '∑', '∑', '∑', '∑', '⅀'], - _ => return None, - }; - - Some(match (variant, bold, italic) { - (Serif, true, false) => list[0], - (Serif, false, true) => list[1], - (Serif, true, true) => list[2], - (Sans, _, false) => list[3], - (Sans, _, true) => list[4], - (Bb, ..) => list[5], + 'א' => 'ℵ', + 'ב' => 'ℶ', + 'ג' => 'ℷ', + 'ד' => 'ℸ', _ => return None, }) } diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs index 32be216a4..13685d468 100644 --- a/crates/typst-library/src/math/equation.rs +++ b/crates/typst-library/src/math/equation.rs @@ -1,5 +1,6 @@ use std::num::NonZeroUsize; +use codex::styling::MathVariant; use typst_utils::NonZeroExt; use unicode_math_class::MathClass; @@ -14,7 +15,7 @@ use crate::layout::{ AlignElem, Alignment, BlockElem, InlineElem, OuterHAlignment, SpecificAlignment, VAlignment, }; -use crate::math::{MathSize, MathVariant}; +use crate::math::MathSize; use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement}; use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem}; @@ -114,7 +115,7 @@ pub struct EquationElem { /// The style variant to select. #[internal] #[ghost] - pub variant: MathVariant, + pub variant: Option, /// Affects the height of exponents. #[internal] @@ -131,7 +132,7 @@ pub struct EquationElem { /// Whether to use italic glyphs. #[internal] #[ghost] - pub italic: Smart, + pub italic: Option, /// A forced class to use for all fragment. #[internal] diff --git a/crates/typst-library/src/math/style.rs b/crates/typst-library/src/math/style.rs index f3d28f2a9..9d54d7de3 100644 --- a/crates/typst-library/src/math/style.rs +++ b/crates/typst-library/src/math/style.rs @@ -1,4 +1,6 @@ -use crate::foundations::{func, Cast, Content, Smart}; +use codex::styling::MathVariant; + +use crate::foundations::{func, Cast, Content}; use crate::math::EquationElem; /// Bold font style in math. @@ -24,7 +26,7 @@ pub fn upright( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_italic(Smart::Custom(false))) + body.styled(EquationElem::set_italic(Some(false))) } /// Italic font style in math. @@ -35,7 +37,7 @@ pub fn italic( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_italic(Smart::Custom(true))) + body.styled(EquationElem::set_italic(Some(true))) } /// Serif (roman) font style in math. @@ -46,7 +48,7 @@ pub fn serif( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Serif)) + body.styled(EquationElem::set_variant(Some(MathVariant::Plain))) } /// Sans-serif font style in math. @@ -59,7 +61,7 @@ pub fn sans( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Sans)) + body.styled(EquationElem::set_variant(Some(MathVariant::SansSerif))) } /// Calligraphic font style in math. @@ -93,7 +95,7 @@ pub fn cal( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Cal)) + body.styled(EquationElem::set_variant(Some(MathVariant::Script))) } /// Fraktur font style in math. @@ -106,7 +108,7 @@ pub fn frak( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Frak)) + body.styled(EquationElem::set_variant(Some(MathVariant::Fraktur))) } /// Monospace font style in math. @@ -119,7 +121,7 @@ pub fn mono( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Mono)) + body.styled(EquationElem::set_variant(Some(MathVariant::Monospace))) } /// Blackboard bold (double-struck) font style in math. @@ -137,7 +139,7 @@ pub fn bb( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Bb)) + body.styled(EquationElem::set_variant(Some(MathVariant::DoubleStruck))) } /// Forced display style in math. @@ -240,15 +242,3 @@ pub enum MathSize { /// Math on its own line. Display, } - -/// A mathematical style variant, as defined by Unicode. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Cast, Hash)] -pub enum MathVariant { - #[default] - Serif, - Sans, - Cal, - Frak, - Mono, - Bb, -} diff --git a/tests/ref/math-style-fallback.png b/tests/ref/math-style-fallback.png new file mode 100644 index 0000000000000000000000000000000000000000..de0283762d8208eb46d0d34b6d1727c5692ef64f GIT binary patch literal 935 zcmV;Y16cftP)Nmq2JB?V>G;e89BdSM5) zTJ8q_!hk6J!-Ei;d)EHN(R(1CcTyC_UDxa>Ti1)N-CnI~%S)Z2Fr*;6l=_bqd;`Og z_67rc=Z`cuq@-c*hjrV#0n>A-^8l~-YqtF_4Hungv#@8ak9-Hx@H$WN5cV&&p4_%o z9$rsYRtd<%E6Hj^9){%oWYzRG@^IWuR_#aBaw%BBR|2Q@y}Z110PIbD^up85F;RH= zihr%DJ36p+Vae;0@pta*7lgmP#Q-)IVC&Oc6Tr&Vf^cKrmk!4hHS>TOEtt*#To3ph zj@6})3BvVxeJ-><*Raf?~Ags^r0-i4bW@UzE4QQ<4Y#>qUrRJa9W8(`q~p@RcA~Z?mF1r zde8)za5LLak}7 zVgCw35n$AKuiG&G2CBm(X=Q4F~7Ok6oxmy;dI{hAJ7**{8l(T0)Q_RT6# Date: Tue, 24 Jun 2025 23:16:22 +1000 Subject: [PATCH 2/4] Add `math.scr` --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/typst-layout/src/math/text.rs | 63 ++++++++------------- crates/typst-library/src/math/mod.rs | 1 + crates/typst-library/src/math/style.rs | 34 +++++++---- docs/reference/groups.yml | 2 +- tests/ref/math-style-hebrew-exceptions.png | Bin 296 -> 489 bytes tests/suite/math/style.typ | 3 +- 8 files changed, 54 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87ce0d3f0..5406776f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,7 +413,7 @@ dependencies = [ [[package]] name = "codex" 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]] name = "color-print" diff --git a/Cargo.toml b/Cargo.toml index c2a58421d..272eed1aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] } clap_complete = "4.2.1" clap_mangen = "0.2.10" 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" comemo = "0.4" csv = "1" diff --git a/crates/typst-layout/src/math/text.rs b/crates/typst-layout/src/math/text.rs index d13251e61..ad42119ee 100644 --- a/crates/typst-layout/src/math/text.rs +++ b/crates/typst-layout/src/math/text.rs @@ -65,12 +65,21 @@ fn layout_inline_text( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult { + 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 == '.') { // Small optimization for numbers. Note that this lays out slightly // differently to normal text and is worth re-evaluating in the future. let mut fragments = vec![]; 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)?; fragments.push(glyph.into()); } @@ -84,8 +93,10 @@ fn layout_inline_text( .map(|p| p.wrap()); let styles = styles.chain(&local); - let styled_text: EcoString = - text.chars().map(|c| styled_char(styles, c, false)).collect(); + let styled_text: EcoString = text + .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 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)), _ => (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 = - match GlyphFragment::new_char(ctx.font, symbol_styles, c, elem.span()) { + match GlyphFragment::new(ctx.font, symbol_styles, &text, elem.span()) { Ok(mut glyph) => { adjust_glyph_layout(&mut glyph, ctx, styles); glyph.into() @@ -135,8 +153,7 @@ pub fn layout_symbol( Err(_) => { // Not in the math font, fallback to normal inline text layout. // TODO: Should replace this with proper fallback in [`GlyphFragment::new`]. - layout_inline_text(c.encode_utf8(&mut [0; 4]), elem.span(), ctx, styles)? - .into() + layout_inline_text(&text, elem.span(), ctx, styles)?.into() } }; 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 { - Some(match c { - '〈' => '⟨', - '〉' => '⟩', - '《' => '⟪', - '》' => '⟫', - 'א' => 'ℵ', - 'ב' => 'ℶ', - 'ג' => 'ℷ', - 'ד' => 'ℸ', - _ => return None, - }) -} - /// The non-dotless version of a dotless character that can be used with the /// `dtls` OpenType feature. pub fn try_dotless(c: char) -> Option { diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs index 2e6d42b13..3d39e2fd2 100644 --- a/crates/typst-library/src/math/mod.rs +++ b/crates/typst-library/src/math/mod.rs @@ -80,6 +80,7 @@ pub fn module() -> Module { math.define_func::(); math.define_func::(); math.define_func::(); + math.define_func::(); math.define_func::(); math.define_func::(); math.define_func::(); diff --git a/crates/typst-library/src/math/style.rs b/crates/typst-library/src/math/style.rs index 9d54d7de3..dd2c243c6 100644 --- a/crates/typst-library/src/math/style.rs +++ b/crates/typst-library/src/math/style.rs @@ -64,20 +64,34 @@ pub fn sans( body.styled(EquationElem::set_variant(Some(MathVariant::SansSerif))) } -/// Calligraphic font style in math. +/// Calligraphic (chancery) font style in math. /// /// ```example /// Let $cal(P)$ be the set of ... /// ``` /// -/// This corresponds both to LaTeX's `\mathcal` and `\mathscr` as both of these -/// styles share the same Unicode codepoints. Switching between the styles is -/// thus only possible if supported by the font via +/// This is the default calligraphic/script style for most math fonts. See +/// [`scr`]($math.scr) for more on how to get the other style (roundhand). +#[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). /// -/// For the default math font, the roundhand style is available through the -/// `ss01` feature. Therefore, you could define your own version of `\mathscr` -/// like this: +/// Say, for example, the roundhand style is available through the `ss01` +/// feature. Then, you could define your own version of `\mathscr` like this: /// /// ```example /// #let scr(it) = text( @@ -90,12 +104,12 @@ pub fn sans( /// /// (The box is not conceptually necessary, but unfortunately currently needed /// due to limitations in Typst's text style handling in math.) -#[func(title = "Calligraphic", keywords = ["mathcal", "mathscr"])] -pub fn cal( +#[func(title = "Script", keywords = ["mathscr", "roundhand"])] +pub fn scr( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(Some(MathVariant::Script))) + body.styled(EquationElem::set_variant(Some(MathVariant::Roundhand))) } /// Fraktur font style in math. diff --git a/docs/reference/groups.yml b/docs/reference/groups.yml index e01d99dc4..a4dccc071 100644 --- a/docs/reference/groups.yml +++ b/docs/reference/groups.yml @@ -5,7 +5,7 @@ title: Variants category: math path: ["math"] - filter: ["serif", "sans", "frak", "mono", "bb", "cal"] + filter: ["serif", "sans", "frak", "mono", "bb", "cal", "scr"] details: | Alternate typefaces within formulas. diff --git a/tests/ref/math-style-hebrew-exceptions.png b/tests/ref/math-style-hebrew-exceptions.png index 723466e8a36f109e890fd77b08b8c78d4ba30caf..a6f511e0e2ce7dbcf6ead15b06bb3429f90bcd12 100644 GIT binary patch delta 476 zcmV<20VDpX0_g*g7k?-S000005P@O&00057NklT05xoO@L6}K_GzgZIkclJ_g%~Cwrua%t)9Y2g;XU_W z|AJ!=?pc1H-}ek3&TzgZ_@PrGi6yZl{%_(O86GiU{Q`xkiGS5r1SSX`);F|UIQo<+ z;v}1?tE=v$A`bRM(QgvAmmxAEs$B@@QAb6L**eS><3x=*50@3?vTz=fRsE!~%1X=4^u+F3qxOj-mN95Jh%3ldnwihi6YtoX zHUy5G@fmot0`$akqp8uuv%|rSSoa&*z2g+c>%BNxx>X+q)UB;8)80Ygrtm^4?qh<-C)x?0ANzI|8eqKAUzooOR)O!7k?WF00000l5Z6H0002*Nkl&=Ye?{nuE>&Jq%{NyHAIb>ja#2_YGWZw5%Sr gc+}!ii!lKJM-vcqv*9Pp#{d8T07*qoM6N<$g4#KY5&!@I diff --git a/tests/suite/math/style.typ b/tests/suite/math/style.typ index daf6c0cd6..2f7d8799e 100644 --- a/tests/suite/math/style.typ +++ b/tests/suite/math/style.typ @@ -47,7 +47,8 @@ $bb(Gamma) , bb(gamma), bb(Pi), bb(pi), bb(sum)$ --- math-style-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 --- _abc $sin(x) "abc"$_ \ From 5e678d6d2479dff873a054fd099a6564e37be8c4 Mon Sep 17 00:00:00 2001 From: mkorje Date: Mon, 30 Jun 2025 21:49:25 +1000 Subject: [PATCH 3/4] Update docs --- crates/typst-library/src/math/style.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/typst-library/src/math/style.rs b/crates/typst-library/src/math/style.rs index dd2c243c6..1fbf1b15f 100644 --- a/crates/typst-library/src/math/style.rs +++ b/crates/typst-library/src/math/style.rs @@ -86,12 +86,14 @@ pub fn cal( /// $ scr(S) $ /// ``` /// -/// Very few math fonts currently support differentiating `cal` and `scr`. Some -/// fonts support switching between the styles via -/// [font features]($text.features). +/// There are two ways that fonts can support differentiating `cal` and `scr`. +/// The first is using Unicode variation sequences. This works out of the box +/// in Typst, however only a few math fonts currently support this. /// -/// Say, for example, the roundhand style is available through the `ss01` -/// feature. Then, you could define your own version of `\mathscr` like this: +/// The other way is using [font features]($text.features). For example, the +/// roundhand style might be available in a font through the `ss01` feature. +/// To use it in Typst, you could then define your own version of `scr` like +/// this: /// /// ```example /// #let scr(it) = text( From 5bee8ff434e25749498461b186136e72e91041ca Mon Sep 17 00:00:00 2001 From: mkorje Date: Thu, 3 Jul 2025 20:36:13 +1000 Subject: [PATCH 4/4] Add test --- Cargo.lock | 2 +- Cargo.toml | 2 +- tests/ref/math-style-script.png | Bin 0 -> 585 bytes tests/suite/math/style.typ | 7 +++++++ 4 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 tests/ref/math-style-script.png diff --git a/Cargo.lock b/Cargo.lock index 5406776f3..14a493eb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2861,7 +2861,7 @@ dependencies = [ [[package]] name = "typst-assets" version = "0.13.1" -source = "git+https://github.com/typst/typst-assets?rev=c1089b4#c1089b46c461bdde579c55caa941a3cc7dec3e8a" +source = "git+https://github.com/typst/typst-assets?rev=edf0d64#edf0d648376e29738a05a933af9ea99bb81557b1" [[package]] name = "typst-cli" diff --git a/Cargo.toml b/Cargo.toml index 272eed1aa..9657f207f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ typst-svg = { path = "crates/typst-svg", version = "0.13.1" } typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" } typst-timing = { path = "crates/typst-timing", version = "0.13.1" } typst-utils = { path = "crates/typst-utils", version = "0.13.1" } -typst-assets = { git = "https://github.com/typst/typst-assets", rev = "c1089b4" } +typst-assets = { git = "https://github.com/typst/typst-assets", rev = "edf0d64" } typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "bfa947f" } arrayvec = "0.7.4" az = "1.2" diff --git a/tests/ref/math-style-script.png b/tests/ref/math-style-script.png new file mode 100644 index 0000000000000000000000000000000000000000..379d270e70b7f94412429b785f9285a7de95939a GIT binary patch literal 585 zcmV-P0=E5$P)L5)nX8u2gyWP4vBPv1TNb`$uLK; z&0uC_gEEz^xmeR$#x}P$iqegACS!Z{tapc<`U8F>@q90z@8|Toee(E6m#4rAtib;v zEX#k@@P}L`Qexr6P-#wJ{3MLc-F;Hh4p=JGohFg6eD!?;08F;)7;EYtq;D6r?tFPs z6A=k_d5l)@>J57P{$H7*dFwcxhjDIkHz$jI0^va8?{hJ@g^7{vhx(e~~>JKF9YW3U&pJZwc1KW%a@Zi@Ewc69p1n>W9)klC@r8 z@Icob*vn_-+Iv&4j}J;@ubk>_eFnTK*BbrGaw)I^E3g7@40zoUhm7C1@CCpV1d>3= z4UiHCe=2oPtj^o-oSkmb6EJ^0KgHqziBXT?CP`gd9NeaV8g-1&-48@&M}0d+ejJn1 zwq?r+Rkr#g!r*k9&Kq8W8Yl27rminpJ92}`jJhX3*i?Zqc=^D)_&PxGutmaeN^Ezk zHb;q>Lshr$Z!(F4H7zR-rh&vE00Jvk+rBeEJmh**S15^to2_z#-OmATr zC{y9uW