Compare commits

...

7 Commits

Author SHA1 Message Date
mkorje
30b98e75e7
Add test 2025-07-07 13:01:06 +10:00
mkorje
2c1976d4ea
Update docs 2025-07-07 13:01:04 +10:00
mkorje
4b13abc728
Add math.scr 2025-07-07 13:01:01 +10:00
mkorje
b37f57bdec
Move math styling to codex 2025-07-07 13:00:57 +10:00
Laurenz
d1deb80bb8
Fix nightly warnings (#6558) 2025-07-05 12:23:48 +00:00
Andrew Voynov
88e451b3dc
Fix typo in PackageStorage (#6556) 2025-07-04 17:02:02 +00:00
Tobias Schmitz
cc3a68ecb1
Remove duplicate language computation (#6557) 2025-07-04 17:00:45 +00:00
19 changed files with 115 additions and 278 deletions

5
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/typst/codex?rev=a5428cb#a5428cb9c81a41354d44b44dbd5a16a710bbd928" source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00"
[[package]] [[package]]
name = "color-print" name = "color-print"
@ -2861,7 +2861,7 @@ dependencies = [
[[package]] [[package]]
name = "typst-assets" name = "typst-assets"
version = "0.13.1" 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]] [[package]]
name = "typst-cli" name = "typst-cli"
@ -3028,6 +3028,7 @@ version = "0.13.1"
dependencies = [ dependencies = [
"az", "az",
"bumpalo", "bumpalo",
"codex",
"comemo", "comemo",
"ecow", "ecow",
"hypher", "hypher",

View File

@ -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-syntax = { path = "crates/typst-syntax", version = "0.13.1" }
typst-timing = { path = "crates/typst-timing", 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-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" } typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "bfa947f" }
arrayvec = "0.7.4" arrayvec = "0.7.4"
az = "1.2" az = "1.2"
@ -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/typst/codex", rev = "a5428cb" } 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

@ -199,7 +199,7 @@ impl PackageStorage {
// The place at which the specific package version will live in the end. // The place at which the specific package version will live in the end.
let package_dir = base_dir.join(format!("{}", spec.version)); let package_dir = base_dir.join(format!("{}", spec.version));
// To prevent multiple Typst instances from interferring, we download // To prevent multiple Typst instances from interfering, we download
// into a temporary directory first and then move this directory to // into a temporary directory first and then move this directory to
// its final destination. // its final destination.
// //

View File

@ -21,6 +21,7 @@ typst-timing = { workspace = true }
typst-utils = { workspace = true } typst-utils = { workspace = true }
az = { workspace = true } az = { workspace = true }
bumpalo = { workspace = true } bumpalo = { workspace = true }
codex = { workspace = true }
comemo = { workspace = true } comemo = { workspace = true }
ecow = { workspace = true } ecow = { workspace = true }
hypher = { workspace = true } hypher = { workspace = true }

View File

@ -1,10 +1,11 @@
use std::f64::consts::SQRT_2; use std::f64::consts::SQRT_2;
use codex::styling::{to_style, MathStyle};
use ecow::EcoString; use ecow::EcoString;
use typst_library::diag::SourceResult; use typst_library::diag::SourceResult;
use typst_library::foundations::{Packed, StyleChain, SymbolElem}; use typst_library::foundations::{Packed, StyleChain, SymbolElem};
use typst_library::layout::{Abs, Size}; use typst_library::layout::{Abs, Size};
use typst_library::math::{EquationElem, MathSize, MathVariant}; use typst_library::math::{EquationElem, MathSize};
use typst_library::text::{ use typst_library::text::{
BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric, BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric,
}; };
@ -64,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());
} }
@ -83,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);
@ -124,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()
@ -134,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);
@ -161,226 +179,6 @@ fn adjust_glyph_layout(
} }
} }
/// Style the character by selecting the unicode codepoint for italic, bold,
/// caligraphic, etc.
///
/// <https://www.w3.org/TR/mathml-core/#new-text-transform-mappings>
/// <https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols>
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;
}
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()
}
fn basic_exception(c: char) -> Option<char> {
Some(match c {
'〈' => '⟨',
'〉' => '⟩',
'《' => '⟪',
'》' => '⟫',
_ => return None,
})
}
fn latin_exception(
c: char,
variant: MathVariant,
bold: bool,
italic: bool,
) -> Option<char> {
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<char> {
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,
})
}
/// 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

@ -94,7 +94,7 @@ impl Array {
} }
/// Iterate over references to the contained values. /// Iterate over references to the contained values.
pub fn iter(&self) -> std::slice::Iter<Value> { pub fn iter(&self) -> std::slice::Iter<'_, Value> {
self.0.iter() self.0.iter()
} }

View File

@ -114,7 +114,7 @@ impl Dict {
} }
/// Iterate over pairs of references to the contained keys and values. /// Iterate over pairs of references to the contained keys and values.
pub fn iter(&self) -> indexmap::map::Iter<Str, Value> { pub fn iter(&self) -> indexmap::map::Iter<'_, Str, Value> {
self.0.iter() self.0.iter()
} }

View File

@ -497,7 +497,8 @@ mod callbacks {
macro_rules! callback { macro_rules! callback {
($name:ident = ($($param:ident: $param_ty:ty),* $(,)?) -> $ret:ty) => { ($name:ident = ($($param:ident: $param_ty:ty),* $(,)?) -> $ret:ty) => {
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, Hash)]
#[allow(clippy::derived_hash_with_manual_eq)]
pub struct $name { pub struct $name {
captured: Content, captured: Content,
f: fn(&Content, $($param_ty),*) -> $ret, f: fn(&Content, $($param_ty),*) -> $ret,
@ -535,6 +536,19 @@ mod callbacks {
(self.f)(&self.captured, $($param),*) (self.f)(&self.captured, $($param),*)
} }
} }
impl PartialEq for $name {
fn eq(&self, other: &Self) -> bool {
// Comparing function pointers is problematic. Since for
// each type of content, there is typically just one
// callback, we skip it. It barely matters anyway since
// getting into a comparison codepath for inline & block
// elements containing callback bodies is close to
// impossible (as these are generally generated in show
// rules).
self.captured.eq(&other.captured)
}
}
}; };
} }

View File

@ -47,12 +47,12 @@ impl Fragment {
} }
/// Iterate over the contained frames. /// Iterate over the contained frames.
pub fn iter(&self) -> std::slice::Iter<Frame> { pub fn iter(&self) -> std::slice::Iter<'_, Frame> {
self.0.iter() self.0.iter()
} }
/// Iterate over the contained frames. /// Iterate over the contained frames.
pub fn iter_mut(&mut self) -> std::slice::IterMut<Frame> { pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Frame> {
self.0.iter_mut() self.0.iter_mut()
} }
} }

View File

@ -1,5 +1,6 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use codex::styling::MathVariant;
use typst_utils::NonZeroExt; use typst_utils::NonZeroExt;
use unicode_math_class::MathClass; use unicode_math_class::MathClass;
@ -14,7 +15,7 @@ use crate::layout::{
AlignElem, Alignment, BlockElem, InlineElem, OuterHAlignment, SpecificAlignment, AlignElem, Alignment, BlockElem, InlineElem, OuterHAlignment, SpecificAlignment,
VAlignment, VAlignment,
}; };
use crate::math::{MathSize, MathVariant}; use crate::math::MathSize;
use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement}; use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement};
use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem}; use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
@ -114,7 +115,7 @@ pub struct EquationElem {
/// The style variant to select. /// The style variant to select.
#[internal] #[internal]
#[ghost] #[ghost]
pub variant: MathVariant, pub variant: Option<MathVariant>,
/// Affects the height of exponents. /// Affects the height of exponents.
#[internal] #[internal]
@ -131,7 +132,7 @@ pub struct EquationElem {
/// Whether to use italic glyphs. /// Whether to use italic glyphs.
#[internal] #[internal]
#[ghost] #[ghost]
pub italic: Smart<bool>, pub italic: Option<bool>,
/// A forced class to use for all fragment. /// A forced class to use for all fragment.
#[internal] #[internal]

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

@ -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; use crate::math::EquationElem;
/// Bold font style in math. /// Bold font style in math.
@ -24,7 +26,7 @@ pub fn upright(
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
body.styled(EquationElem::set_italic(Smart::Custom(false))) body.styled(EquationElem::set_italic(Some(false)))
} }
/// Italic font style in math. /// Italic font style in math.
@ -35,7 +37,7 @@ pub fn italic(
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
body.styled(EquationElem::set_italic(Smart::Custom(true))) body.styled(EquationElem::set_italic(Some(true)))
} }
/// Serif (roman) font style in math. /// Serif (roman) font style in math.
@ -46,7 +48,7 @@ pub fn serif(
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
body.styled(EquationElem::set_variant(MathVariant::Serif)) body.styled(EquationElem::set_variant(Some(MathVariant::Plain)))
} }
/// Sans-serif font style in math. /// Sans-serif font style in math.
@ -59,23 +61,39 @@ pub fn sans(
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
body.styled(EquationElem::set_variant(MathVariant::Sans)) 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"])]
/// [font features]($text.features). pub fn cal(
/// The content to style.
body: Content,
) -> Content {
body.styled(EquationElem::set_variant(Some(MathVariant::Chancery)))
}
/// Script (roundhand) font style in math.
/// ///
/// For the default math font, the roundhand style is available through the /// ```example
/// `ss01` feature. Therefore, you could define your own version of `\mathscr` /// $ scr(S) $
/// like this: /// ```
///
/// 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.
///
/// 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 /// ```example
/// #let scr(it) = text( /// #let scr(it) = text(
@ -88,12 +106,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(MathVariant::Cal)) body.styled(EquationElem::set_variant(Some(MathVariant::Roundhand)))
} }
/// Fraktur font style in math. /// Fraktur font style in math.
@ -106,7 +124,7 @@ pub fn frak(
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
body.styled(EquationElem::set_variant(MathVariant::Frak)) body.styled(EquationElem::set_variant(Some(MathVariant::Fraktur)))
} }
/// Monospace font style in math. /// Monospace font style in math.
@ -119,7 +137,7 @@ pub fn mono(
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> 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. /// Blackboard bold (double-struck) font style in math.
@ -137,7 +155,7 @@ pub fn bb(
/// The content to style. /// The content to style.
body: Content, body: Content,
) -> Content { ) -> Content {
body.styled(EquationElem::set_variant(MathVariant::Bb)) body.styled(EquationElem::set_variant(Some(MathVariant::DoubleStruck)))
} }
/// Forced display style in math. /// Forced display style in math.
@ -240,15 +258,3 @@ pub enum MathSize {
/// Math on its own line. /// Math on its own line.
Display, 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,
}

View File

@ -936,7 +936,7 @@ cast! {
} }
/// Resolve a prioritized iterator over the font families. /// Resolve a prioritized iterator over the font families.
pub fn families(styles: StyleChain) -> impl Iterator<Item = &FontFamily> + Clone { pub fn families(styles: StyleChain<'_>) -> impl Iterator<Item = &'_ FontFamily> + Clone {
let fallbacks = singleton!(Vec<FontFamily>, { let fallbacks = singleton!(Vec<FontFamily>, {
[ [
"libertinus serif", "libertinus serif",

View File

@ -22,8 +22,6 @@ pub(crate) fn build_metadata(gc: &GlobalContext) -> Metadata {
.keywords(gc.document.info.keywords.iter().map(EcoString::to_string).collect()) .keywords(gc.document.info.keywords.iter().map(EcoString::to_string).collect())
.authors(gc.document.info.author.iter().map(EcoString::to_string).collect()); .authors(gc.document.info.author.iter().map(EcoString::to_string).collect());
let lang = gc.languages.iter().max_by_key(|(_, &count)| count).map(|(&l, _)| l);
if let Some(lang) = lang { if let Some(lang) = lang {
metadata = metadata.language(lang.as_str().to_string()); metadata = metadata.language(lang.as_str().to_string());
} }

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.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

View File

@ -12,6 +12,15 @@ $A, italic(A), upright(A), bold(A), bold(upright(A)), \
bb("hello") + bold(cal("world")), \ bb("hello") + bold(cal("world")), \
mono("SQRT")(x) wreath mono(123 + 456)$ mono("SQRT")(x) wreath mono(123 + 456)$
--- math-style-fallback ---
// Test how math styles fallback.
$upright(frak(bold(alpha))) = upright(bold(alpha)) \
bold(mono(ϝ)) = bold(ϝ) \
sans(Theta) = bold(sans(Theta)) \
bold(upright(planck)) != planck \
bb(e) != italic(bb(e)) \
serif(sans(A)) != serif(A)$
--- math-style-dotless --- --- math-style-dotless ---
// Test styling dotless i and j. // Test styling dotless i and j.
$ dotless.i dotless.j, $ dotless.i dotless.j,
@ -21,7 +30,7 @@ $ dotless.i dotless.j,
bb(dotless.i) bb(dotless.j), bb(dotless.i) bb(dotless.j),
cal(dotless.i) cal(dotless.j), cal(dotless.i) cal(dotless.j),
frak(dotless.i) frak(dotless.j), frak(dotless.i) frak(dotless.j),
mono(dotless.i) mono(dotless.j), mono(dotless.i) mono(dotless.j),
bold(frak(dotless.i)) upright(sans(dotless.j)), bold(frak(dotless.i)) upright(sans(dotless.j)),
italic(bb(dotless.i)) frak(sans(dotless.j)) $ italic(bb(dotless.i)) frak(sans(dotless.j)) $
@ -38,7 +47,15 @@ $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)$
--- math-style-script ---
// Test variation selectors for scr and cal.
$cal(A) scr(A) bold(cal(O)) scr(bold(O))$
#show math.equation: set text(font: "Noto Sans Math")
$scr(E) cal(E) bold(scr(Y)) cal(bold(Y))$
--- issue-3650-italic-equation --- --- issue-3650-italic-equation ---
_abc $sin(x) "abc"$_ \ _abc $sin(x) "abc"$_ \