summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--library/src/lib.rs12
-rw-r--r--library/src/math/matrix.rs84
-rw-r--r--library/src/math/mod.rs186
-rw-r--r--library/src/math/style.rs161
-rw-r--r--library/src/math/tex.rs4
-rw-r--r--src/model/eval.rs2
-rw-r--r--src/model/realize.rs4
-rw-r--r--tests/ref/math/accents.pngbin0 -> 3077 bytes
-rw-r--r--tests/ref/math/matrix.pngbin4854 -> 6079 bytes
-rw-r--r--tests/ref/math/simple.pngbin6560 -> 8293 bytes
-rw-r--r--tests/ref/math/style.pngbin0 -> 15288 bytes
-rw-r--r--tests/typ/math/accents.typ28
-rw-r--r--tests/typ/math/matrix.typ15
-rw-r--r--tests/typ/math/simple.typ6
-rw-r--r--tests/typ/math/style.typ15
16 files changed, 451 insertions, 68 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8fe3dba2..9de15487 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1027,7 +1027,7 @@ dependencies = [
[[package]]
name = "symmie"
version = "0.1.0"
-source = "git+https://github.com/typst/symmie#8504bf7ec0d8996d160832c2724ba024ab6e988a"
+source = "git+https://github.com/typst/symmie#6280fb20455cb63e6886ba5bb35b95a4b376da68"
[[package]]
name = "syn"
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 {
diff --git a/src/model/eval.rs b/src/model/eval.rs
index 204dfbd4..0ae8a0b1 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -434,7 +434,7 @@ impl Eval for ast::MathNode {
Self::Expr(v) => {
if let ast::Expr::Ident(ident) = v {
if self.as_untyped().len() == ident.len()
- && !vm.scopes.get(ident).is_ok()
+ && matches!(vm.scopes.get(ident), Ok(Value::Func(_)) | Err(_))
{
let node = (vm.items.symbol)(ident.get().clone() + ":op".into());
return Ok(node.spanned(self.span()));
diff --git a/src/model/realize.rs b/src/model/realize.rs
index b4e38e30..862bc8b6 100644
--- a/src/model/realize.rs
+++ b/src/model/realize.rs
@@ -7,6 +7,10 @@ pub fn applicable(target: &Content, styles: StyleChain) -> bool {
return true;
}
+ if target.has::<dyn Show>() && target.is_pristine() {
+ return true;
+ }
+
// Find out how many recipes there are.
let mut n = styles.recipes().count();
diff --git a/tests/ref/math/accents.png b/tests/ref/math/accents.png
new file mode 100644
index 00000000..7e93d9b5
--- /dev/null
+++ b/tests/ref/math/accents.png
Binary files differ
diff --git a/tests/ref/math/matrix.png b/tests/ref/math/matrix.png
index ed763710..3bd17715 100644
--- a/tests/ref/math/matrix.png
+++ b/tests/ref/math/matrix.png
Binary files differ
diff --git a/tests/ref/math/simple.png b/tests/ref/math/simple.png
index 6c7fde55..ebd55dcb 100644
--- a/tests/ref/math/simple.png
+++ b/tests/ref/math/simple.png
Binary files differ
diff --git a/tests/ref/math/style.png b/tests/ref/math/style.png
new file mode 100644
index 00000000..8dc11b31
--- /dev/null
+++ b/tests/ref/math/style.png
Binary files differ
diff --git a/tests/typ/math/accents.typ b/tests/typ/math/accents.typ
new file mode 100644
index 00000000..04e6e723
--- /dev/null
+++ b/tests/typ/math/accents.typ
@@ -0,0 +1,28 @@
+// Test math accents.
+
+---
+#set page(width: auto)
+
+$ acc(a,`),
+ acc(a,´),
+ acc(a,\^),
+ acc(a,~),
+ acc(a,¯),
+ acc(a,‾),
+ acc(a,˘),
+ acc(a,.),
+ acc(a,¨),
+ acc(a,ˇ),
+ acc(a,->) $
+
+$ acc(a, grave),
+ acc(a, acute),
+ acc(a, circum),
+ acc(a, tilde),
+ acc(a, macron),
+ acc(a, overline),
+ acc(a, breve),
+ acc(a, dot),
+ acc(a, dia),
+ acc(a, caron),
+ acc(a, arrow) $
diff --git a/tests/typ/math/matrix.typ b/tests/typ/math/matrix.typ
index ec84778c..3d67800d 100644
--- a/tests/typ/math/matrix.typ
+++ b/tests/typ/math/matrix.typ
@@ -4,12 +4,11 @@
$ v = vec(1, 2+3, 4) $
---
-#set vec(delim: "|")
-$ vec(1, 2) $
+$ binom(n, 1) = 1/2 n (n-1) $
---
-// Error: 17-20 expected "(", "[", "{", or "|"
-#set vec(delim: "%")
+#set vec(delim: "|")
+$ vec(1, 2) $
---
$ f(x, y) := cases(
@@ -18,3 +17,11 @@ $ f(x, y) := cases(
3 "if" x "is even",
4 "else",
) $
+
+---
+// Error: 17-20 expected "(", "[", "{", or "|"
+#set vec(delim: "%")
+
+---
+// Error: 9-12 missing argument: lower index
+$ binom(x^2) $
diff --git a/tests/typ/math/simple.typ b/tests/typ/math/simple.typ
index 55a853cf..1b63cbfc 100644
--- a/tests/typ/math/simple.typ
+++ b/tests/typ/math/simple.typ
@@ -12,8 +12,12 @@ Prove by induction:
$ sum_(k=0)^n k = (n(n+1))/2 $
---
+We know that:
+$ floor(x/2) <= ceil(x/2) $
+
+---
// Test that blackboard style looks nice.
-$ f: NN arrow RR $
+$ f: NN -> RR $
---
// Error: 1:3 expected dollar sign
diff --git a/tests/typ/math/style.typ b/tests/typ/math/style.typ
new file mode 100644
index 00000000..c9238a9a
--- /dev/null
+++ b/tests/typ/math/style.typ
@@ -0,0 +1,15 @@
+#let part = $ a b A B $
+#let kinds = (serif, sans, cal, frak, mono, bb)
+#let modifiers = (v => v, ital, bold, v => ital(bold(v)))
+
+#let cells = ([:triangle:nested:], [--], [`ital`], [`bold`], [both])
+#for k in kinds {
+ cells.push(raw(repr(k).trim("<function ").trim(">")))
+ for m in modifiers {
+ cells.push($ #m(#k(part)) $)
+ }
+}
+
+#set page(width: auto)
+#set par(align: center)
+#table(columns: 1 + modifiers.len(), ..cells)