Move math styling to codex

This commit is contained in:
mkorje 2025-06-24 21:13:47 +10:00
parent d1deb80bb8
commit b37f57bdec
No known key found for this signature in database
8 changed files with 44 additions and 229 deletions

3
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/mkorje/codex?rev=f02fa4a#f02fa4a04f2a9937f043892ee865dd06628264ba"
[[package]] [[package]]
name = "color-print" name = "color-print"
@ -3028,6 +3028,7 @@ version = "0.13.1"
dependencies = [ dependencies = [
"az", "az",
"bumpalo", "bumpalo",
"codex",
"comemo", "comemo",
"ecow", "ecow",
"hypher", "hypher",

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/typst/codex", rev = "a5428cb" } codex = { git = "https://github.com/mkorje/codex", rev = "f02fa4a" }
color-print = "0.3.6" color-print = "0.3.6"
comemo = "0.4" comemo = "0.4"
csv = "1" csv = "1"

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,
}; };
@ -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. /// 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 { 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) { if let Some(c) = basic_exception(c) {
return c; return c;
} }
if let Some(c) = latin_exception(c, variant, bold, italic) { let variant = EquationElem::variant_in(styles);
return c; 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) { // At the moment we are only using styles that output a single character,
return c; // so we just grab the first character in the ToStyle iterator.
} to_style(c, style).next().unwrap()
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> { fn basic_exception(c: char) -> Option<char> {
@ -290,93 +186,10 @@ fn basic_exception(c: char) -> Option<char> {
'〉' => '⟩', '〉' => '⟩',
'《' => '⟪', '《' => '⟪',
'》' => '⟫', '》' => '⟫',
_ => 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, _ => return None,
}) })
} }

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

@ -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,7 +61,7 @@ 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 font style in math.
@ -93,7 +95,7 @@ pub fn cal(
/// 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::Script)))
} }
/// Fraktur font style in math. /// Fraktur font style in math.
@ -106,7 +108,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 +121,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 +139,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 +242,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,
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 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)) $