mirror of
https://github.com/typst/typst
synced 2025-08-19 17:38:32 +08:00
Move math styling to codex
This commit is contained in:
parent
5db1cf2c70
commit
f97e5b919b
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -413,8 +413,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "codex"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "724d27a0ee38b700e5e164350e79aba601a0db673ac47fce1cb74c3e38864036"
|
||||
source = "git+https://github.com/mkorje/codex?rev=1311ada#1311ada4fe2e152f0c2f5c579df30dba2fa36233"
|
||||
|
||||
[[package]]
|
||||
name = "color-print"
|
||||
@ -3029,6 +3028,7 @@ version = "0.13.1"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bumpalo",
|
||||
"codex",
|
||||
"comemo",
|
||||
"ecow",
|
||||
"hypher",
|
||||
|
@ -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 = "0.1.1"
|
||||
codex = { git = "https://github.com/mkorje/codex", rev = "1311ada" }
|
||||
color-print = "0.3.6"
|
||||
comemo = "0.4"
|
||||
csv = "1"
|
||||
|
@ -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 }
|
||||
|
@ -1,10 +1,11 @@
|
||||
use std::f64::consts::SQRT_2;
|
||||
|
||||
use codex::styling::{resolve_style, to_style};
|
||||
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,
|
||||
};
|
||||
@ -177,127 +178,22 @@ fn layout_glyph(
|
||||
}
|
||||
}
|
||||
|
||||
/// Style the character by selecting the unicode codepoint for italic, bold,
|
||||
/// 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;
|
||||
}
|
||||
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 = resolve_style(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<char> {
|
||||
@ -306,93 +202,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,
|
||||
})
|
||||
}
|
||||
|
@ -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<MathVariant>,
|
||||
|
||||
/// Affects the height of exponents.
|
||||
#[internal]
|
||||
@ -131,7 +132,7 @@ pub struct EquationElem {
|
||||
/// Whether to use italic glyphs.
|
||||
#[internal]
|
||||
#[ghost]
|
||||
pub italic: Smart<bool>,
|
||||
pub italic: Option<bool>,
|
||||
|
||||
/// A forced class to use for all fragment.
|
||||
#[internal]
|
||||
|
@ -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,
|
||||
}
|
||||
|
BIN
tests/ref/math-style-fallback.png
Normal file
BIN
tests/ref/math-style-fallback.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 935 B |
@ -12,6 +12,15 @@ $A, italic(A), upright(A), bold(A), bold(upright(A)), \
|
||||
bb("hello") + bold(cal("world")), \
|
||||
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 ---
|
||||
// Test styling dotless i and j.
|
||||
$ dotless.i dotless.j,
|
||||
@ -21,7 +30,7 @@ $ dotless.i dotless.j,
|
||||
bb(dotless.i) bb(dotless.j),
|
||||
cal(dotless.i) cal(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)),
|
||||
italic(bb(dotless.i)) frak(sans(dotless.j)) $
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user