typst/library/src/math/style.rs
2023-06-06 22:06:16 +02:00

621 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::*;
/// Bold font style in math.
///
/// ## Example { #example }
/// ```example
/// $ bold(A) := B^+ $
/// ```
///
/// Display: Bold
/// Category: math
#[func]
pub fn bold(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_bold(Some(true)).pack()
}
/// Upright (non-italic) font style in math.
///
/// ## Example { #example }
/// ```example
/// $ upright(A) != A $
/// ```
///
/// Display: Upright
/// Category: math
#[func]
pub fn upright(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_italic(Some(false)).pack()
}
/// Italic font style in math.
///
/// For roman letters and greek lowercase letters, this is already the default.
///
/// Display: Italic
/// Category: math
#[func]
pub fn italic(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_italic(Some(true)).pack()
}
/// Serif (roman) font style in math.
///
/// This is already the default.
///
/// Display: Serif
/// Category: math
#[func]
pub fn serif(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_variant(Some(MathVariant::Serif)).pack()
}
/// Sans-serif font style in math.
///
/// ## Example { #example }
/// ```example
/// $ sans(A B C) $
/// ```
///
/// Display: Sans-serif
/// Category: math
#[func]
pub fn sans(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_variant(Some(MathVariant::Sans)).pack()
}
/// Calligraphic font style in math.
///
/// ## Example { #example }
/// ```example
/// Let $cal(P)$ be the set of ...
/// ```
///
/// Display: Calligraphic
/// Category: math
#[func]
pub fn cal(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_variant(Some(MathVariant::Cal)).pack()
}
/// Fraktur font style in math.
///
/// ## Example { #example }
/// ```example
/// $ frak(P) $
/// ```
///
/// Display: Fraktur
/// Category: math
#[func]
pub fn frak(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_variant(Some(MathVariant::Frak)).pack()
}
/// Monospace font style in math.
///
/// ## Example { #example }
/// ```example
/// $ mono(x + y = z) $
/// ```
///
/// Display: Monospace
/// Category: math
#[func]
pub fn mono(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_variant(Some(MathVariant::Mono)).pack()
}
/// Blackboard bold (double-struck) font style in math.
///
/// For uppercase latin letters, blackboard bold is additionally available
/// through [symbols]($category/symbols/sym) of the form `NN` and `RR`.
///
/// ## Example { #example }
/// ```example
/// $ bb(b) $
/// $ bb(N) = NN $
/// $ f: NN -> RR $
/// ```
///
/// Display: Blackboard Bold
/// Category: math
#[func]
pub fn bb(
/// The content to style.
body: Content,
) -> Content {
MathStyleElem::new(body).with_variant(Some(MathVariant::Bb)).pack()
}
/// Forced display style in math.
///
/// This is the normal size for display equations.
///
/// ## Example { #example }
/// ```example
/// $sum_i x_i/2 = display(sum_i x/2)$
/// ```
///
/// Display: Display Size
/// Category: math
#[func]
pub fn display(
/// The content to size.
body: Content,
/// Whether to impose a height restriction for exponents, like regular sub-
/// and superscripts do.
#[named]
#[default(false)]
cramp: bool,
) -> Content {
MathStyleElem::new(body)
.with_size(Some(MathSize::Display))
.with_cramp(Some(cramp))
.pack()
}
/// Forced inline (text) style in math.
///
/// This is the normal size for inline equations.
///
/// ## Example { #example }
/// ```example
/// $ sum_i x_i/2
/// = inline(sum_i x_i/2) $
/// ```
///
/// Display: Inline Size
/// Category: math
#[func]
pub fn inline(
/// The content to size.
body: Content,
/// Whether to impose a height restriction for exponents, like regular sub-
/// and superscripts do.
#[named]
#[default(false)]
cramp: bool,
) -> Content {
MathStyleElem::new(body)
.with_size(Some(MathSize::Text))
.with_cramp(Some(cramp))
.pack()
}
/// Forced script style in math.
///
/// This is the smaller size used in powers or sub- or superscripts.
///
/// ## Example { #example }
/// ```example
/// $sum_i x_i/2 = script(sum_i x_i/2)$
/// ```
///
/// Display: Script Size
/// Category: math
#[func]
pub fn script(
/// The content to size.
body: Content,
/// Whether to impose a height restriction for exponents, like regular sub-
/// and superscripts do.
#[named]
#[default(true)]
cramp: bool,
) -> Content {
MathStyleElem::new(body)
.with_size(Some(MathSize::Script))
.with_cramp(Some(cramp))
.pack()
}
/// Forced second script style in math.
///
/// This is the smallest size, used in second-level sub- and superscripts
/// (script of the script).
///
/// ## Example { #example }
/// ```example
/// $sum_i x_i/2 = sscript(sum_i x_i/2)$
/// ```
///
/// Display: Script-Script Size
/// Category: math
#[func]
pub fn sscript(
/// The content to size.
body: Content,
/// Whether to impose a height restriction for exponents, like regular sub-
/// and superscripts do.
#[named]
#[default(true)]
cramp: bool,
) -> Content {
MathStyleElem::new(body)
.with_size(Some(MathSize::ScriptScript))
.with_cramp(Some(cramp))
.pack()
}
/// A font variant in math.
///
/// Display: Bold
/// Category: math
#[element(LayoutMath)]
pub struct MathStyleElem {
/// The content to style.
#[required]
pub body: Content,
/// The variant to select.
pub variant: Option<MathVariant>,
/// Whether to use bold glyphs.
pub bold: Option<bool>,
/// Whether to use italic glyphs.
pub italic: Option<bool>,
/// Whether to use forced size
pub size: Option<MathSize>,
/// Whether to limit height of exponents
pub cramp: Option<bool>,
}
impl LayoutMath for MathStyleElem {
#[tracing::instrument(skip(ctx))]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let mut style = ctx.style;
if let Some(variant) = self.variant(StyleChain::default()) {
style = style.with_variant(variant);
}
if let Some(bold) = self.bold(StyleChain::default()) {
style = style.with_bold(bold);
}
if let Some(italic) = self.italic(StyleChain::default()) {
style = style.with_italic(italic);
}
if let Some(size) = self.size(StyleChain::default()) {
style = style.with_size(size);
}
if let Some(cramped) = self.cramp(StyleChain::default()) {
style = style.with_cramped(cramped);
}
ctx.style(style);
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Text properties in math.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct MathStyle {
/// The style variant to select.
pub variant: MathVariant,
/// The size of the glyphs.
pub size: MathSize,
/// Affects the height of exponents.
pub cramped: bool,
/// Whether to use bold glyphs.
pub bold: bool,
/// Whether to use italic glyphs.
pub italic: Smart<bool>,
}
impl MathStyle {
/// This style, with the given `variant`.
pub fn with_variant(self, variant: MathVariant) -> Self {
Self { variant, ..self }
}
/// This style, with the given `size`.
pub fn with_size(self, size: MathSize) -> Self {
Self { size, ..self }
}
/// This style, with `cramped` set to the given value.
pub fn with_cramped(self, cramped: bool) -> Self {
Self { cramped, ..self }
}
/// This style, with `bold` set to the given value.
pub fn with_bold(self, bold: bool) -> Self {
Self { bold, ..self }
}
/// This style, with `italic` set to the given value.
pub fn with_italic(self, italic: bool) -> Self {
Self { italic: Smart::Custom(italic), ..self }
}
/// The style for subscripts in the current style.
pub fn for_subscript(self) -> Self {
self.for_superscript().with_cramped(true)
}
/// The style for superscripts in the current style.
pub fn for_superscript(self) -> Self {
self.with_size(match self.size {
MathSize::Display | MathSize::Text => MathSize::Script,
MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
})
}
/// The style for numerators in the current style.
pub fn for_numerator(self) -> Self {
self.with_size(match self.size {
MathSize::Display => MathSize::Text,
MathSize::Text => MathSize::Script,
MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
})
}
/// The style for denominators in the current style.
pub fn for_denominator(self) -> Self {
self.for_numerator().with_cramped(true)
}
/// Apply the style to a character.
pub fn styled_char(self, c: char) -> char {
styled_char(self, c)
}
}
/// The size of elements in an equation.
///
/// See the TeXbook p. 141.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Cast)]
pub enum MathSize {
/// Second-level sub- and superscripts.
ScriptScript,
/// Sub- and superscripts.
Script,
/// Math in text.
Text,
/// Math on its own line.
Display,
}
impl MathSize {
pub(super) fn factor(self, ctx: &MathContext) -> f64 {
match self {
Self::Display | Self::Text => 1.0,
Self::Script => percent!(ctx, script_percent_scale_down),
Self::ScriptScript => percent!(ctx, script_script_percent_scale_down),
}
}
}
/// A mathematical style variant, as defined by Unicode.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Cast)]
pub enum MathVariant {
Serif,
Sans,
Cal,
Frak,
Mono,
Bb,
}
impl Default for MathVariant {
fn default() -> Self {
Self::Serif
}
}
/// Select the correct styled math letter.
///
/// https://www.w3.org/TR/mathml-core/#new-text-transform-mappings
/// https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols
pub(super) fn styled_char(style: MathStyle, c: char) -> char {
use MathVariant::*;
let MathStyle { variant, bold, .. } = style;
let italic = style.italic.unwrap_or(matches!(
c,
'a'..='z' | 'ı' | 'ȷ' | 'A'..='Z' | 'α'..='ω' |
'∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'
));
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',
_ => 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,
},
// 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, ..) => '',
('h', Serif, false, true) => '',
('e', Cal, false, _) => '',
('g', Cal, false, _) => '',
('o', Cal, false, _) => '',
('ı', Serif, .., true) => '𝚤',
('ȷ', Serif, .., true) => '𝚥',
_ => return None,
})
}
fn greek_exception(
c: char,
variant: MathVariant,
bold: bool,
italic: bool,
) -> Option<char> {
use MathVariant::*;
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],
_ => return None,
})
}