diff --git a/Cargo.lock b/Cargo.lock index 37fe60d17..416d989b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -846,7 +846,7 @@ dependencies = [ [[package]] name = "rex" version = "0.1.2" -source = "git+https://github.com/laurmaedje/ReX#672c321a947f945e9ba936ae9fbd982c4e043f1c" +source = "git+https://github.com/laurmaedje/ReX#523b29bd39a4daf50f9bb3ee48689c66b209c4ff" dependencies = [ "itertools", "nom", diff --git a/library/src/lib.rs b/library/src/lib.rs index af5c252bc..29a6cc949 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -53,6 +53,8 @@ fn scope() -> Scope { std.def_node::("atom"); std.def_node::("frac"); std.def_node::("sqrt"); + std.def_node::("vec"); + std.def_node::("cases"); // Layout. std.def_node::("page"); diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 1e8145cce..c613ea2a9 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -54,7 +54,7 @@ impl Layout for MathNode { styles: StyleChain, _: &Regions, ) -> SourceResult { - let mut t = Texifier::new(); + let mut t = Texifier::new(styles); self.texify(&mut t)?; layout_tex(vt, &t.finish(), self.display, styles) } @@ -71,7 +71,7 @@ trait Texify { /// Texify the node, but trim parentheses.. fn texify_unparen(&self, t: &mut Texifier) -> SourceResult<()> { let s = { - let mut sub = Texifier::new(); + let mut sub = Texifier::new(t.styles); self.texify(&mut sub)?; sub.finish() }; @@ -88,19 +88,21 @@ trait Texify { } /// Builds the TeX representation of the formula. -struct Texifier { +struct Texifier<'a> { tex: EcoString, support: bool, space: bool, + styles: StyleChain<'a>, } -impl Texifier { +impl<'a> Texifier<'a> { /// Create a new texifier. - fn new() -> Self { + fn new(styles: StyleChain<'a>) -> Self { Self { tex: EcoString::new(), support: false, space: false, + styles, } } @@ -346,7 +348,7 @@ impl Texify for AlignNode { } } -/// A square root node. +/// A square root. #[derive(Debug, Hash)] pub struct SqrtNode(Content); @@ -365,3 +367,86 @@ impl Texify for SqrtNode { Ok(()) } } + +/// A column vector. +#[derive(Debug, Hash)] +pub struct VecNode(Vec); + +#[node(Texify)] +impl VecNode { + /// The kind of delimiter. + pub const DELIM: Delimiter = Delimiter::Paren; + + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + 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_unparen(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. +#[derive(Debug, Hash)] +pub struct CasesNode(Vec); + +#[node(Texify)] +impl CasesNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + 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_unparen(t)?; + t.push_str("\\\\"); + } + t.push_str("\\end{cases}"); + Ok(()) + } +} diff --git a/library/src/math/tex.rs b/library/src/math/tex.rs index da07f1d62..e8917f309 100644 --- a/library/src/math/tex.rs +++ b/library/src/math/tex.rs @@ -69,7 +69,7 @@ pub fn layout_tex( }, baseline: top, font: font.clone(), - fill: styles.get(TextNode::FILL), + paint: styles.get(TextNode::FILL), lang: styles.get(TextNode::LANG), colors: vec![], }; @@ -85,18 +85,18 @@ struct FrameBackend { frame: Frame, baseline: Abs, font: Font, - fill: Paint, + paint: Paint, lang: Lang, colors: Vec, } impl FrameBackend { - /// The currently active fill paint. - fn fill(&self) -> Paint { + /// The currently active paint. + fn paint(&self) -> Paint { self.colors .last() .map(|&RGBA(r, g, b, a)| RgbaColor::new(r, g, b, a).into()) - .unwrap_or(self.fill) + .unwrap_or(self.paint) } /// Convert a cursor to a point. @@ -112,7 +112,7 @@ impl Backend for FrameBackend { Element::Text(Text { font: self.font.clone(), size: Abs::pt(scale), - fill: self.fill(), + fill: self.paint(), lang: self.lang, glyphs: vec![Glyph { id: gid, @@ -126,11 +126,11 @@ impl Backend for FrameBackend { fn rule(&mut self, pos: Cursor, width: f64, height: f64) { self.frame.push( - self.transform(pos), + self.transform(pos) + Point::with_y(Abs::pt(height) / 2.0), Element::Shape(Shape { - geometry: Geometry::Rect(Size::new(Abs::pt(width), Abs::pt(height))), - fill: Some(self.fill()), - stroke: None, + geometry: Geometry::Line(Point::new(Abs::pt(width), Abs::zero())), + fill: None, + stroke: Some(Stroke { paint: self.paint(), thickness: Abs::pt(height) }), }), ); } diff --git a/src/model/eval.rs b/src/model/eval.rs index f3281dacf..204dfbd46 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -426,7 +426,7 @@ impl Eval for ast::MathNode { Self::Escape(v) => (vm.items.math_atom)(v.get().into()), Self::Shorthand(v) => (vm.items.math_atom)(v.get().into()), Self::Atom(v) => v.eval(vm)?, - Self::Symbol(v) => (vm.items.symbol)(v.get().clone()), + Self::Symbol(v) => (vm.items.symbol)(v.get().clone() + ":op".into()), Self::Script(v) => v.eval(vm)?, Self::Frac(v) => v.eval(vm)?, Self::Align(v) => v.eval(vm)?, @@ -436,7 +436,7 @@ impl Eval for ast::MathNode { if self.as_untyped().len() == ident.len() && !vm.scopes.get(ident).is_ok() { - let node = (vm.items.symbol)(ident.get().clone()); + let node = (vm.items.symbol)(ident.get().clone() + ":op".into()); return Ok(node.spanned(self.span())); } } diff --git a/tests/ref/math/matrix.png b/tests/ref/math/matrix.png new file mode 100644 index 000000000..ed763710a Binary files /dev/null and b/tests/ref/math/matrix.png differ diff --git a/tests/ref/math/shorthand.png b/tests/ref/math/shorthand.png index 4b14006bd..840feac26 100644 Binary files a/tests/ref/math/shorthand.png and b/tests/ref/math/shorthand.png differ diff --git a/tests/ref/math/simple.png b/tests/ref/math/simple.png index 72f9c1c6d..6c7fde558 100644 Binary files a/tests/ref/math/simple.png and b/tests/ref/math/simple.png differ diff --git a/tests/ref/math/syntax.png b/tests/ref/math/syntax.png index eaf185280..1000adb5e 100644 Binary files a/tests/ref/math/syntax.png and b/tests/ref/math/syntax.png differ diff --git a/tests/typ/math/matrix.typ b/tests/typ/math/matrix.typ new file mode 100644 index 000000000..ec84778c7 --- /dev/null +++ b/tests/typ/math/matrix.typ @@ -0,0 +1,20 @@ +// Test vectors, matrices, and cases. + +--- +$ v = vec(1, 2+3, 4) $ + +--- +#set vec(delim: "|") +$ vec(1, 2) $ + +--- +// Error: 17-20 expected "(", "[", "{", or "|" +#set vec(delim: "%") + +--- +$ f(x, y) := cases( + 1 "if" (x dot y)/2 <= 0, + 2 "if" x in NN, + 3 "if" x "is even", + 4 "else", +) $