summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-22 13:30:45 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-22 13:30:45 +0100
commit57a636b3704e7002b0f895c0945ec2f720d75584 (patch)
treebbed596cebf2d84ff94b17f9b6c32da5214e07c8
parent13cc16b3ccaa94cdf5dca2bf3195067d1d12f9b1 (diff)
Math styles
-rw-r--r--library/src/math/style.rs402
1 files changed, 312 insertions, 90 deletions
diff --git a/library/src/math/style.rs b/library/src/math/style.rs
index 0ec6853a..cebb1534 100644
--- a/library/src/math/style.rs
+++ b/library/src/math/style.rs
@@ -1,11 +1,12 @@
use super::*;
-/// # Serif
-/// Serif (roman) font style in math.
-///
-/// This is already the default.
+/// # Bold
+/// Bold font style in math.
///
-/// _Note:_ In the future this might be unified with text styling.
+/// ## Example
+/// ```
+/// $ bold(A) := B^+ $
+/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
@@ -14,35 +15,30 @@ use super::*;
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
-pub struct SerifNode(pub Content);
+pub struct BoldNode(pub Content);
#[node]
-impl SerifNode {
+impl BoldNode {
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("}");
+impl LayoutMath for BoldNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ ctx.style(ctx.style.with_italic(false).with_bold_toggled());
+ self.0.layout_math(ctx)?;
+ ctx.unstyle();
Ok(())
}
}
-/// # Sans-serif
-/// Sans-serif font style in math.
-///
-/// _Note:_ In the future this might be unified with text styling.
+/// # Italic
+/// Italic font style in math.
///
-/// ## Example
-/// ```
-/// $ sans(A B C) $
-/// ```
+/// This is already the default.
///
/// ## Parameters
/// - body: Content (positional, required)
@@ -51,35 +47,30 @@ impl Texify for SerifNode {
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
-pub struct SansNode(pub Content);
+pub struct ItalicNode(pub Content);
#[node]
-impl SansNode {
+impl ItalicNode {
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("}");
+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(())
}
}
-/// # Bold
-/// Bold font style in math.
-///
-/// _Note:_ In the future this might be unified with text styling.
+/// # Serif
+/// Serif (roman) font style in math.
///
-/// ## Example
-/// ```
-/// $ bold(A) := B^+ $
-/// ```
+/// This is already the default.
///
/// ## Parameters
/// - body: Content (positional, required)
@@ -88,32 +79,33 @@ impl Texify for SansNode {
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
-pub struct BoldNode(pub Content);
+pub struct SerifNode(pub Content);
#[node]
-impl BoldNode {
+impl SerifNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
-impl Texify for BoldNode {
- fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
- t.push_str("\\mathbf{");
- self.0.texify_unparen(t)?;
- t.push_str("}");
+impl LayoutMath for SerifNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ ctx.style(ctx.style.with_variant(MathVariant::Serif));
+ self.0.layout_math(ctx)?;
+ ctx.unstyle();
Ok(())
}
}
-/// # Italic
-/// Italic font style in math.
-///
-/// This is already the default.
+/// # 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)
@@ -122,22 +114,22 @@ impl Texify for BoldNode {
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
-pub struct ItalNode(pub Content);
+pub struct SansNode(pub Content);
#[node]
-impl ItalNode {
+impl SansNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
-impl Texify for ItalNode {
- fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
- t.push_str("\\mathit{");
- self.0.texify_unparen(t)?;
- t.push_str("}");
+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(())
}
}
@@ -145,8 +137,6 @@ impl Texify for ItalNode {
/// # Calligraphic
/// Calligraphic font style in math.
///
-/// _Note:_ In the future this might be unified with text styling.
-///
/// ## Example
/// ```
/// Let $cal(P)$ be the set of ...
@@ -159,7 +149,7 @@ impl Texify for ItalNode {
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
pub struct CalNode(pub Content);
@@ -170,11 +160,11 @@ impl CalNode {
}
}
-impl Texify for CalNode {
- fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
- t.push_str("\\mathcal{");
- self.0.texify_unparen(t)?;
- t.push_str("}");
+impl LayoutMath for CalNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ ctx.style(ctx.style.with_variant(MathVariant::Cal));
+ self.0.layout_math(ctx)?;
+ ctx.unstyle();
Ok(())
}
}
@@ -182,8 +172,6 @@ impl Texify for CalNode {
/// # Fraktur
/// Fraktur font style in math.
///
-/// _Note:_ In the future this might be unified with text styling.
-///
/// ## Example
/// ```
/// $ frak(P) $
@@ -196,7 +184,7 @@ impl Texify for CalNode {
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
pub struct FrakNode(pub Content);
@@ -207,11 +195,11 @@ impl FrakNode {
}
}
-impl Texify for FrakNode {
- fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
- t.push_str("\\mathfrak{");
- self.0.texify_unparen(t)?;
- t.push_str("}");
+impl LayoutMath for FrakNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ ctx.style(ctx.style.with_variant(MathVariant::Frak));
+ self.0.layout_math(ctx)?;
+ ctx.unstyle();
Ok(())
}
}
@@ -219,8 +207,6 @@ impl Texify for FrakNode {
/// # Monospace
/// Monospace font style in math.
///
-/// _Note:_ In the future this might be unified with text styling.
-///
/// ## Example
/// ```
/// $ mono(x + y = z) $
@@ -233,7 +219,7 @@ impl Texify for FrakNode {
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
pub struct MonoNode(pub Content);
@@ -244,11 +230,11 @@ impl MonoNode {
}
}
-impl Texify for MonoNode {
- fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
- t.push_str("\\mathtt{");
- self.0.texify_unparen(t)?;
- t.push_str("}");
+impl LayoutMath for MonoNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ ctx.style(ctx.style.with_variant(MathVariant::Mono));
+ self.0.layout_math(ctx)?;
+ ctx.unstyle();
Ok(())
}
}
@@ -259,8 +245,6 @@ impl Texify for MonoNode {
/// For uppercase latin letters, blackboard bold is additionally available
/// through [symmie symbols](@symbol) of the form `NN` and `RR`.
///
-/// _Note:_ In the future this might be unified with text styling.
-///
/// ## Example
/// ```
/// $ bb(b) $
@@ -274,7 +258,7 @@ impl Texify for MonoNode {
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
pub struct BbNode(pub Content);
@@ -285,11 +269,249 @@ impl BbNode {
}
}
-impl Texify for BbNode {
- fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
- t.push_str("\\mathbb{");
- self.0.texify_unparen(t)?;
- t.push_str("}");
+impl LayoutMath for BbNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ ctx.style(ctx.style.with_variant(MathVariant::Bb));
+ self.0.layout_math(ctx)?;
+ ctx.unstyle();
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(),
+ }
+}