typst/library/src/math/style.rs
2023-03-18 19:27:31 +01:00

532 lines
14 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
/// $ bold(A) := B^+ $
/// ```
///
/// Display: Bold
/// Category: math
#[node(LayoutMath)]
pub struct BoldNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for BoldNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_bold(true));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Upright (non-italic) font style in math.
///
/// ## Example
/// ```example
/// $ upright(A) != A $
/// ```
///
/// Display: Upright
/// Category: math
#[node(LayoutMath)]
pub struct UprightNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for UprightNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_italic(false));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Italic font style in math.
///
/// For roman letters and greek lowercase letters, this is already the default.
///
/// Display: Italic
/// Category: math
#[node(LayoutMath)]
pub struct ItalicNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for ItalicNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_italic(true));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Serif (roman) font style in math.
///
/// This is already the default.
///
/// Display: Serif
/// Category: math
#[node(LayoutMath)]
pub struct SerifNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for SerifNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_variant(MathVariant::Serif));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Sans-serif font style in math.
///
/// ## Example
/// ```example
/// $ sans(A B C) $
/// ```
///
/// Display: Sans-serif
/// Category: math
#[node(LayoutMath)]
pub struct SansNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for SansNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_variant(MathVariant::Sans));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Calligraphic font style in math.
///
/// ## Example
/// ```example
/// Let $cal(P)$ be the set of ...
/// ```
///
/// Display: Calligraphic
/// Category: math
#[node(LayoutMath)]
pub struct CalNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for CalNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_variant(MathVariant::Cal));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Fraktur font style in math.
///
/// ## Example
/// ```example
/// $ frak(P) $
/// ```
///
/// Display: Fraktur
/// Category: math
#[node(LayoutMath)]
pub struct FrakNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for FrakNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_variant(MathVariant::Frak));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// Monospace font style in math.
///
/// ## Example
/// ```example
/// $ mono(x + y = z) $
/// ```
///
/// Display: Monospace
/// Category: math
#[node(LayoutMath)]
pub struct MonoNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for MonoNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_variant(MathVariant::Mono));
self.body().layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// 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
/// $ bb(b) $
/// $ bb(N) = NN $
/// $ f: NN -> RR $
/// ```
///
/// Display: Blackboard Bold
/// Category: math
#[node(LayoutMath)]
pub struct BbNode {
/// The content to style.
#[required]
pub body: Content,
}
impl LayoutMath for BbNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_variant(MathVariant::Bb));
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)]
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)]
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,
})
}