diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-12-07 12:33:48 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-12-07 14:01:20 +0100 |
| commit | 11c7ceb29e762f6dd1d093d9fdb57e9612a92df0 (patch) | |
| tree | cf68d1d206a4718dd20f4dd8de8e6edc931012e1 /library/src | |
| parent | e1c0cda6c8505ad2efc347afc80868296e56cb62 (diff) | |
Extend math library
Diffstat (limited to 'library/src')
| -rw-r--r-- | library/src/lib.rs | 12 | ||||
| -rw-r--r-- | library/src/math/matrix.rs | 84 | ||||
| -rw-r--r-- | library/src/math/mod.rs | 186 | ||||
| -rw-r--r-- | library/src/math/style.rs | 161 | ||||
| -rw-r--r-- | library/src/math/tex.rs | 4 |
5 files changed, 386 insertions, 61 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs index 29a6cc94..e41e7c0d 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -51,10 +51,22 @@ fn scope() -> Scope { // Math. std.def_node::<math::MathNode>("math"); std.def_node::<math::AtomNode>("atom"); + std.def_node::<math::AccNode>("acc"); std.def_node::<math::FracNode>("frac"); + std.def_node::<math::BinomNode>("binom"); std.def_node::<math::SqrtNode>("sqrt"); + std.def_node::<math::FloorNode>("floor"); + std.def_node::<math::CeilNode>("ceil"); std.def_node::<math::VecNode>("vec"); std.def_node::<math::CasesNode>("cases"); + std.def_node::<math::SerifNode>("serif"); + std.def_node::<math::SansNode>("sans"); + std.def_node::<math::BoldNode>("bold"); + std.def_node::<math::ItalNode>("ital"); + std.def_node::<math::CalNode>("cal"); + std.def_node::<math::FrakNode>("frak"); + std.def_node::<math::MonoNode>("mono"); + std.def_node::<math::BbNode>("bb"); // Layout. std.def_node::<layout::PageNode>("page"); diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs new file mode 100644 index 00000000..d835b348 --- /dev/null +++ b/library/src/math/matrix.rs @@ -0,0 +1,84 @@ +use super::*; + +/// A column vector in a mathematical formula. +#[derive(Debug, Hash)] +pub struct VecNode(Vec<Content>); + +#[node(Texify)] +impl VecNode { + /// The kind of delimiter. + pub const DELIM: Delimiter = Delimiter::Paren; + + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.all()?).pack()) + } +} + +impl Texify for VecNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + let kind = match t.styles.get(Self::DELIM) { + Delimiter::Paren => "pmatrix", + Delimiter::Bracket => "bmatrix", + Delimiter::Brace => "Bmatrix", + Delimiter::Bar => "vmatrix", + }; + + t.push_str("\\begin{"); + t.push_str(kind); + t.push_str("}"); + + for component in &self.0 { + component.texify(t)?; + t.push_str("\\\\"); + } + t.push_str("\\end{"); + t.push_str(kind); + t.push_str("}"); + + Ok(()) + } +} + +/// A vector / matrix delimiter. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Delimiter { + Paren, + Bracket, + Brace, + Bar, +} + +castable! { + Delimiter, + Expected: "type of bracket or bar", + Value::Str(s) => match s.as_str() { + "(" => Self::Paren, + "[" => Self::Bracket, + "{" => Self::Brace, + "|" => Self::Bar, + _ => Err("expected \"(\", \"[\", \"{\", or \"|\"")?, + }, +} + +/// A case distinction in a mathematical formula. +#[derive(Debug, Hash)] +pub struct CasesNode(Vec<Content>); + +#[node(Texify)] +impl CasesNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.all()?).pack()) + } +} + +impl Texify for CasesNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\begin{cases}"); + for component in &self.0 { + component.texify(t)?; + t.push_str("\\\\"); + } + t.push_str("\\end{cases}"); + Ok(()) + } +} diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index c613ea2a..4eb72e23 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -1,7 +1,12 @@ //! Mathematical formulas. +mod matrix; +mod style; mod tex; +pub use self::matrix::*; +pub use self::style::*; + use typst::model::{Guard, SequenceNode}; use unicode_segmentation::UnicodeSegmentation; @@ -272,6 +277,79 @@ impl Texify for AtomNode { } } +/// An accented node. +#[derive(Debug, Hash)] +pub struct AccNode { + /// The accent base. + pub base: Content, + /// The Unicode accent character. + pub accent: char, +} + +#[node(Texify)] +impl AccNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + let base = args.expect("base")?; + let Spanned { v, span } = args.expect::<Spanned<Content>>("accent")?; + let accent = match extract(&v) { + Some(Ok(c)) => c, + Some(Err(msg)) => bail!(span, "{}", msg), + None => bail!(span, "not an accent"), + }; + Ok(Self { base, accent }.pack()) + } +} + +#[rustfmt::skip] +fn extract(content: &Content) -> Option<Result<char, &'static str>> { + let MathNode { children, .. } = content.to::<MathNode>()?; + let [child] = children.as_slice() else { return None }; + let c = if let Some(atom) = child.to::<AtomNode>() { + let mut chars = atom.0.chars(); + chars.next().filter(|_| chars.next().is_none())? + } else if let Some(symbol) = child.to::<SymbolNode>() { + match symmie::get(&symbol.0) { + Some(c) => c, + None => return Some(Err("unknown symbol")), + } + } else { + return None; + }; + + Some(Ok(match c { + '`' | '\u{300}' => '\u{300}', // Grave + '´' | '\u{301}' => '\u{301}', // Acute + '^' | '\u{302}' => '\u{302}', // Circumflex + '~' | '\u{223C}' | '\u{303}' => '\u{303}', // Tilde + '¯' | '\u{304}' => '\u{304}', // Macron + '‾' | '\u{305}' => '\u{305}', // Overline + '˘' | '\u{306}' => '\u{306}', // Breve + '.' | '\u{22C5}' | '\u{307}' => '\u{307}', // Dot + '¨' | '\u{308}' => '\u{308}', // Diaeresis + 'ˇ' | '\u{30C}' => '\u{30C}', // Caron + '→' | '\u{20D7}' => '\u{20D7}', // Arrow + _ => return None, + })) +} + +impl Texify for AccNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + if let Some(sym) = unicode_math::SYMBOLS.iter().find(|sym| { + sym.codepoint == self.accent + && sym.atom_type == unicode_math::AtomType::Accent + }) { + t.push_str("\\"); + t.push_str(sym.name); + t.push_str("{"); + self.base.texify(t)?; + t.push_str("}"); + } else { + self.base.texify(t)?; + } + Ok(()) + } +} + /// A fraction in a mathematical formula. #[derive(Debug, Hash)] pub struct FracNode { @@ -301,6 +379,35 @@ impl Texify for FracNode { } } +/// A binomial in a mathematical formula. +#[derive(Debug, Hash)] +pub struct BinomNode { + /// The upper index. + pub upper: Content, + /// The lower index. + pub lower: Content, +} + +#[node(Texify)] +impl BinomNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + let upper = args.expect("upper index")?; + let lower = args.expect("lower index")?; + Ok(Self { upper, lower }.pack()) + } +} + +impl Texify for BinomNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\binom{"); + self.upper.texify(t)?; + t.push_str("}{"); + self.lower.texify(t)?; + t.push_str("}"); + Ok(()) + } +} + /// A sub- and/or superscript in a mathematical formula. #[derive(Debug, Hash)] pub struct ScriptNode { @@ -348,7 +455,7 @@ impl Texify for AlignNode { } } -/// A square root. +/// A square root in a mathematical formula. #[derive(Debug, Hash)] pub struct SqrtNode(Content); @@ -362,91 +469,48 @@ impl SqrtNode { impl Texify for SqrtNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { t.push_str("\\sqrt{"); - self.0.texify_unparen(t)?; + self.0.texify(t)?; t.push_str("}"); Ok(()) } } -/// A column vector. +/// A floored expression in a mathematical formula. #[derive(Debug, Hash)] -pub struct VecNode(Vec<Content>); +pub struct FloorNode(Content); #[node(Texify)] -impl VecNode { - /// The kind of delimiter. - pub const DELIM: Delimiter = Delimiter::Paren; - +impl FloorNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.all()?).pack()) + Ok(Self(args.expect("body")?).pack()) } } -impl Texify for VecNode { +impl Texify for FloorNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - let kind = match t.styles.get(Self::DELIM) { - Delimiter::Paren => "pmatrix", - Delimiter::Bracket => "bmatrix", - Delimiter::Brace => "Bmatrix", - Delimiter::Bar => "vmatrix", - }; - - t.push_str("\\begin{"); - t.push_str(kind); - t.push_str("}"); - - for component in &self.0 { - component.texify_unparen(t)?; - t.push_str("\\\\"); - } - t.push_str("\\end{"); - t.push_str(kind); - t.push_str("}"); - + t.push_str("\\left\\lfloor "); + self.0.texify(t)?; + t.push_str("\\right\\rfloor "); Ok(()) } } -/// A vector / matrix delimiter. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Delimiter { - Paren, - Bracket, - Brace, - Bar, -} - -castable! { - Delimiter, - Expected: "type of bracket or bar", - Value::Str(s) => match s.as_str() { - "(" => Self::Paren, - "[" => Self::Bracket, - "{" => Self::Brace, - "|" => Self::Bar, - _ => Err("expected \"(\", \"[\", \"{\", or \"|\"")?, - }, -} - -/// A case distinction. +/// A ceiled expression in a mathematical formula. #[derive(Debug, Hash)] -pub struct CasesNode(Vec<Content>); +pub struct CeilNode(Content); #[node(Texify)] -impl CasesNode { +impl CeilNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.all()?).pack()) + Ok(Self(args.expect("body")?).pack()) } } -impl Texify for CasesNode { +impl Texify for CeilNode { fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - t.push_str("\\begin{cases}"); - for component in &self.0 { - component.texify_unparen(t)?; - t.push_str("\\\\"); - } - t.push_str("\\end{cases}"); + t.push_str("\\left\\lceil "); + self.0.texify(t)?; + t.push_str("\\right\\rceil "); Ok(()) } } diff --git a/library/src/math/style.rs b/library/src/math/style.rs new file mode 100644 index 00000000..3db2e631 --- /dev/null +++ b/library/src/math/style.rs @@ -0,0 +1,161 @@ +use super::*; + +/// Serif (roman) font style. +#[derive(Debug, Hash)] +pub struct SerifNode(Content); + +#[node(Texify)] +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 font style. +#[derive(Debug, Hash)] +pub struct SansNode(Content); + +#[node(Texify)] +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 font style. +#[derive(Debug, Hash)] +pub struct BoldNode(Content); + +#[node(Texify)] +impl BoldNode { + 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("}"); + Ok(()) + } +} + +/// Italic font style. +#[derive(Debug, Hash)] +pub struct ItalNode(Content); + +#[node(Texify)] +impl ItalNode { + 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("}"); + Ok(()) + } +} + +/// Calligraphic font style. +#[derive(Debug, Hash)] +pub struct CalNode(Content); + +#[node(Texify)] +impl CalNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Texify for CalNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\mathcal{"); + self.0.texify_unparen(t)?; + t.push_str("}"); + Ok(()) + } +} + +/// Fraktur font style. +#[derive(Debug, Hash)] +pub struct FrakNode(Content); + +#[node(Texify)] +impl FrakNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Texify for FrakNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\mathfrak{"); + self.0.texify_unparen(t)?; + t.push_str("}"); + Ok(()) + } +} + +/// Monospace font style. +#[derive(Debug, Hash)] +pub struct MonoNode(Content); + +#[node(Texify)] +impl MonoNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Texify for MonoNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\mathtt{"); + self.0.texify_unparen(t)?; + t.push_str("}"); + Ok(()) + } +} + +/// Blackboard bold (double-struck) font style. +#[derive(Debug, Hash)] +pub struct BbNode(Content); + +#[node(Texify)] +impl BbNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl Texify for BbNode { + fn texify(&self, t: &mut Texifier) -> SourceResult<()> { + t.push_str("\\mathbb{"); + self.0.texify_unparen(t)?; + t.push_str("}"); + Ok(()) + } +} diff --git a/library/src/math/tex.rs b/library/src/math/tex.rs index e8917f30..f17134b7 100644 --- a/library/src/math/tex.rs +++ b/library/src/math/tex.rs @@ -125,6 +125,10 @@ impl Backend for FrameBackend { } fn rule(&mut self, pos: Cursor, width: f64, height: f64) { + if height == 0.0 { + return; + } + self.frame.push( self.transform(pos) + Point::with_y(Abs::pt(height) / 2.0), Element::Shape(Shape { |
