Math styles

This commit is contained in:
Laurenz 2023-01-22 13:30:45 +01:00
parent 13cc16b3cc
commit 57a636b370

View File

@ -1,81 +1,8 @@
use super::*; use super::*;
/// # Serif
/// Serif (roman) font style in math.
///
/// This is already the default.
///
/// _Note:_ In the future this might be unified with text styling.
///
/// ## Parameters
/// - body: Content (positional, required)
/// The piece of formula to style.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct SerifNode(pub Content);
#[node]
impl SerifNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for SerifNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathrm{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// # Sans-serif
/// Sans-serif font style in math.
///
/// _Note:_ In the future this might be unified with text styling.
///
/// ## Example
/// ```
/// $ sans(A B C) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The piece of formula to style.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct SansNode(pub Content);
#[node]
impl SansNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for SansNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\mathsf{");
self.0.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// # Bold /// # Bold
/// Bold font style in math. /// Bold font style in math.
/// ///
/// _Note:_ In the future this might be unified with text styling.
///
/// ## Example /// ## Example
/// ``` /// ```
/// $ bold(A) := B^+ $ /// $ bold(A) := B^+ $
@ -88,7 +15,7 @@ impl Texify for SansNode {
/// ## Category /// ## Category
/// math /// math
#[func] #[func]
#[capable(Texify)] #[capable(LayoutMath)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct BoldNode(pub Content); pub struct BoldNode(pub Content);
@ -99,11 +26,11 @@ impl BoldNode {
} }
} }
impl Texify for BoldNode { impl LayoutMath for BoldNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
t.push_str("\\mathbf{"); ctx.style(ctx.style.with_italic(false).with_bold_toggled());
self.0.texify_unparen(t)?; self.0.layout_math(ctx)?;
t.push_str("}"); ctx.unstyle();
Ok(()) Ok(())
} }
} }
@ -113,7 +40,37 @@ impl Texify for BoldNode {
/// ///
/// This is already the default. /// This is already the default.
/// ///
/// _Note:_ In the future this might be unified with text styling. /// ## Parameters
/// - body: Content (positional, required)
/// The piece of formula to style.
///
/// ## Category
/// math
#[func]
#[capable(LayoutMath)]
#[derive(Debug, Hash)]
pub struct ItalicNode(pub Content);
#[node]
impl ItalicNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl LayoutMath for ItalicNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_italic_toggled());
self.0.layout_math(ctx)?;
ctx.unstyle();
Ok(())
}
}
/// # Serif
/// Serif (roman) font style in math.
///
/// This is already the default.
/// ///
/// ## Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
@ -122,22 +79,57 @@ impl Texify for BoldNode {
/// ## Category /// ## Category
/// math /// math
#[func] #[func]
#[capable(Texify)] #[capable(LayoutMath)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct ItalNode(pub Content); pub struct SerifNode(pub Content);
#[node] #[node]
impl ItalNode { impl SerifNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack()) Ok(Self(args.expect("body")?).pack())
} }
} }
impl Texify for ItalNode { impl LayoutMath for SerifNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
t.push_str("\\mathit{"); ctx.style(ctx.style.with_variant(MathVariant::Serif));
self.0.texify_unparen(t)?; self.0.layout_math(ctx)?;
t.push_str("}"); ctx.unstyle();
Ok(())
}
}
/// # Sans-serif
/// Sans-serif font style in math.
///
/// ## Example
/// ```
/// $ sans(A B C) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The piece of formula to style.
///
/// ## Category
/// math
#[func]
#[capable(LayoutMath)]
#[derive(Debug, Hash)]
pub struct SansNode(pub Content);
#[node]
impl SansNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl LayoutMath for SansNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
ctx.style(ctx.style.with_variant(MathVariant::Sans));
self.0.layout_math(ctx)?;
ctx.unstyle();
Ok(()) Ok(())
} }
} }
@ -145,8 +137,6 @@ impl Texify for ItalNode {
/// # Calligraphic /// # Calligraphic
/// Calligraphic font style in math. /// Calligraphic font style in math.
/// ///
/// _Note:_ In the future this might be unified with text styling.
///
/// ## Example /// ## Example
/// ``` /// ```
/// Let $cal(P)$ be the set of ... /// Let $cal(P)$ be the set of ...
@ -159,7 +149,7 @@ impl Texify for ItalNode {
/// ## Category /// ## Category
/// math /// math
#[func] #[func]
#[capable(Texify)] #[capable(LayoutMath)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct CalNode(pub Content); pub struct CalNode(pub Content);
@ -170,11 +160,11 @@ impl CalNode {
} }
} }
impl Texify for CalNode { impl LayoutMath for CalNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
t.push_str("\\mathcal{"); ctx.style(ctx.style.with_variant(MathVariant::Cal));
self.0.texify_unparen(t)?; self.0.layout_math(ctx)?;
t.push_str("}"); ctx.unstyle();
Ok(()) Ok(())
} }
} }
@ -182,8 +172,6 @@ impl Texify for CalNode {
/// # Fraktur /// # Fraktur
/// Fraktur font style in math. /// Fraktur font style in math.
/// ///
/// _Note:_ In the future this might be unified with text styling.
///
/// ## Example /// ## Example
/// ``` /// ```
/// $ frak(P) $ /// $ frak(P) $
@ -196,7 +184,7 @@ impl Texify for CalNode {
/// ## Category /// ## Category
/// math /// math
#[func] #[func]
#[capable(Texify)] #[capable(LayoutMath)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct FrakNode(pub Content); pub struct FrakNode(pub Content);
@ -207,11 +195,11 @@ impl FrakNode {
} }
} }
impl Texify for FrakNode { impl LayoutMath for FrakNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
t.push_str("\\mathfrak{"); ctx.style(ctx.style.with_variant(MathVariant::Frak));
self.0.texify_unparen(t)?; self.0.layout_math(ctx)?;
t.push_str("}"); ctx.unstyle();
Ok(()) Ok(())
} }
} }
@ -219,8 +207,6 @@ impl Texify for FrakNode {
/// # Monospace /// # Monospace
/// Monospace font style in math. /// Monospace font style in math.
/// ///
/// _Note:_ In the future this might be unified with text styling.
///
/// ## Example /// ## Example
/// ``` /// ```
/// $ mono(x + y = z) $ /// $ mono(x + y = z) $
@ -233,7 +219,7 @@ impl Texify for FrakNode {
/// ## Category /// ## Category
/// math /// math
#[func] #[func]
#[capable(Texify)] #[capable(LayoutMath)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct MonoNode(pub Content); pub struct MonoNode(pub Content);
@ -244,11 +230,11 @@ impl MonoNode {
} }
} }
impl Texify for MonoNode { impl LayoutMath for MonoNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
t.push_str("\\mathtt{"); ctx.style(ctx.style.with_variant(MathVariant::Mono));
self.0.texify_unparen(t)?; self.0.layout_math(ctx)?;
t.push_str("}"); ctx.unstyle();
Ok(()) Ok(())
} }
} }
@ -259,8 +245,6 @@ impl Texify for MonoNode {
/// For uppercase latin letters, blackboard bold is additionally available /// For uppercase latin letters, blackboard bold is additionally available
/// through [symmie symbols](@symbol) of the form `NN` and `RR`. /// through [symmie symbols](@symbol) of the form `NN` and `RR`.
/// ///
/// _Note:_ In the future this might be unified with text styling.
///
/// ## Example /// ## Example
/// ``` /// ```
/// $ bb(b) $ /// $ bb(b) $
@ -274,7 +258,7 @@ impl Texify for MonoNode {
/// ## Category /// ## Category
/// math /// math
#[func] #[func]
#[capable(Texify)] #[capable(LayoutMath)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct BbNode(pub Content); pub struct BbNode(pub Content);
@ -285,11 +269,249 @@ impl BbNode {
} }
} }
impl Texify for BbNode { impl LayoutMath for BbNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
t.push_str("\\mathbb{"); ctx.style(ctx.style.with_variant(MathVariant::Bb));
self.0.texify_unparen(t)?; self.0.layout_math(ctx)?;
t.push_str("}"); ctx.unstyle();
Ok(()) Ok(())
} }
} }
/// The style in a formula.
#[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,
/// Wherher to use italic glyphs.
pub italic: bool,
}
/// The size of elements in a formula.
///
/// 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 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 given value.
pub fn with_cramped(self, cramped: bool) -> Self {
Self { cramped, ..self }
}
/// This style, with `bold` set to given value.
pub fn with_bold(self, bold: bool) -> Self {
Self { bold, ..self }
}
/// This style, with `italic` set to given value.
pub fn with_italic(self, italic: bool) -> Self {
Self { italic, ..self }
}
/// This style, with boldness inverted.
pub fn with_bold_toggled(self) -> Self {
self.with_bold(!self.bold)
}
/// This style, with italicness inverted.
pub fn with_italic_toggled(self) -> Self {
self.with_italic(!self.italic)
}
/// 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)
}
}
/// 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 tuple = (style.variant, style.bold, style.italic);
let base = match c {
'a'..='z' => 'a',
'A'..='Z' => 'A',
'α'..='ω' => 'α',
'Α'..='Ω' => 'Α',
'0'..='9' => '0',
'-' => return '',
_ => return c,
};
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,
},
_ => return c,
};
// Map and fix up codepoints that are defined in previous Unicode Blocks.
let code = start + (c as u32 - base as u32);
match code {
0x1D455 => '\u{210E}',
0x1D49D => '\u{212C}',
0x1D4A0 => '\u{2130}',
0x1D4A1 => '\u{2131}',
0x1D4A3 => '\u{210B}',
0x1D4A4 => '\u{2110}',
0x1D4A7 => '\u{2112}',
0x1D4A8 => '\u{2133}',
0x1D4AD => '\u{211B}',
0x1D4BA => '\u{212F}',
0x1D4BC => '\u{210A}',
0x1D4C4 => '\u{2134}',
0x1D506 => '\u{212D}',
0x1D50B => '\u{210C}',
0x1D50C => '\u{2111}',
0x1D515 => '\u{211C}',
0x1D51D => '\u{2128}',
0x1D53A => '\u{2102}',
0x1D53F => '\u{210D}',
0x1D545 => '\u{2115}',
0x1D547 => '\u{2119}',
0x1D548 => '\u{211A}',
0x1D549 => '\u{211D}',
0x1D551 => '\u{2124}',
code => std::char::from_u32(code).unwrap(),
}
}