Vectors and cases

This commit is contained in:
Laurenz 2022-12-07 11:28:52 +01:00
parent b2572f9d48
commit 5a0053c729
10 changed files with 126 additions and 19 deletions

2
Cargo.lock generated
View File

@ -846,7 +846,7 @@ dependencies = [
[[package]] [[package]]
name = "rex" name = "rex"
version = "0.1.2" version = "0.1.2"
source = "git+https://github.com/laurmaedje/ReX#672c321a947f945e9ba936ae9fbd982c4e043f1c" source = "git+https://github.com/laurmaedje/ReX#523b29bd39a4daf50f9bb3ee48689c66b209c4ff"
dependencies = [ dependencies = [
"itertools", "itertools",
"nom", "nom",

View File

@ -53,6 +53,8 @@ fn scope() -> Scope {
std.def_node::<math::AtomNode>("atom"); std.def_node::<math::AtomNode>("atom");
std.def_node::<math::FracNode>("frac"); std.def_node::<math::FracNode>("frac");
std.def_node::<math::SqrtNode>("sqrt"); std.def_node::<math::SqrtNode>("sqrt");
std.def_node::<math::VecNode>("vec");
std.def_node::<math::CasesNode>("cases");
// Layout. // Layout.
std.def_node::<layout::PageNode>("page"); std.def_node::<layout::PageNode>("page");

View File

@ -54,7 +54,7 @@ impl Layout for MathNode {
styles: StyleChain, styles: StyleChain,
_: &Regions, _: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut t = Texifier::new(); let mut t = Texifier::new(styles);
self.texify(&mut t)?; self.texify(&mut t)?;
layout_tex(vt, &t.finish(), self.display, styles) layout_tex(vt, &t.finish(), self.display, styles)
} }
@ -71,7 +71,7 @@ trait Texify {
/// Texify the node, but trim parentheses.. /// Texify the node, but trim parentheses..
fn texify_unparen(&self, t: &mut Texifier) -> SourceResult<()> { fn texify_unparen(&self, t: &mut Texifier) -> SourceResult<()> {
let s = { let s = {
let mut sub = Texifier::new(); let mut sub = Texifier::new(t.styles);
self.texify(&mut sub)?; self.texify(&mut sub)?;
sub.finish() sub.finish()
}; };
@ -88,19 +88,21 @@ trait Texify {
} }
/// Builds the TeX representation of the formula. /// Builds the TeX representation of the formula.
struct Texifier { struct Texifier<'a> {
tex: EcoString, tex: EcoString,
support: bool, support: bool,
space: bool, space: bool,
styles: StyleChain<'a>,
} }
impl Texifier { impl<'a> Texifier<'a> {
/// Create a new texifier. /// Create a new texifier.
fn new() -> Self { fn new(styles: StyleChain<'a>) -> Self {
Self { Self {
tex: EcoString::new(), tex: EcoString::new(),
support: false, support: false,
space: false, space: false,
styles,
} }
} }
@ -346,7 +348,7 @@ impl Texify for AlignNode {
} }
} }
/// A square root node. /// A square root.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct SqrtNode(Content); pub struct SqrtNode(Content);
@ -365,3 +367,86 @@ impl Texify for SqrtNode {
Ok(()) Ok(())
} }
} }
/// A column vector.
#[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_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<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_unparen(t)?;
t.push_str("\\\\");
}
t.push_str("\\end{cases}");
Ok(())
}
}

View File

@ -69,7 +69,7 @@ pub fn layout_tex(
}, },
baseline: top, baseline: top,
font: font.clone(), font: font.clone(),
fill: styles.get(TextNode::FILL), paint: styles.get(TextNode::FILL),
lang: styles.get(TextNode::LANG), lang: styles.get(TextNode::LANG),
colors: vec![], colors: vec![],
}; };
@ -85,18 +85,18 @@ struct FrameBackend {
frame: Frame, frame: Frame,
baseline: Abs, baseline: Abs,
font: Font, font: Font,
fill: Paint, paint: Paint,
lang: Lang, lang: Lang,
colors: Vec<RGBA>, colors: Vec<RGBA>,
} }
impl FrameBackend { impl FrameBackend {
/// The currently active fill paint. /// The currently active paint.
fn fill(&self) -> Paint { fn paint(&self) -> Paint {
self.colors self.colors
.last() .last()
.map(|&RGBA(r, g, b, a)| RgbaColor::new(r, g, b, a).into()) .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. /// Convert a cursor to a point.
@ -112,7 +112,7 @@ impl Backend for FrameBackend {
Element::Text(Text { Element::Text(Text {
font: self.font.clone(), font: self.font.clone(),
size: Abs::pt(scale), size: Abs::pt(scale),
fill: self.fill(), fill: self.paint(),
lang: self.lang, lang: self.lang,
glyphs: vec![Glyph { glyphs: vec![Glyph {
id: gid, id: gid,
@ -126,11 +126,11 @@ impl Backend for FrameBackend {
fn rule(&mut self, pos: Cursor, width: f64, height: f64) { fn rule(&mut self, pos: Cursor, width: f64, height: f64) {
self.frame.push( self.frame.push(
self.transform(pos), self.transform(pos) + Point::with_y(Abs::pt(height) / 2.0),
Element::Shape(Shape { Element::Shape(Shape {
geometry: Geometry::Rect(Size::new(Abs::pt(width), Abs::pt(height))), geometry: Geometry::Line(Point::new(Abs::pt(width), Abs::zero())),
fill: Some(self.fill()), fill: None,
stroke: None, stroke: Some(Stroke { paint: self.paint(), thickness: Abs::pt(height) }),
}), }),
); );
} }

View File

@ -426,7 +426,7 @@ impl Eval for ast::MathNode {
Self::Escape(v) => (vm.items.math_atom)(v.get().into()), Self::Escape(v) => (vm.items.math_atom)(v.get().into()),
Self::Shorthand(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::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::Script(v) => v.eval(vm)?,
Self::Frac(v) => v.eval(vm)?, Self::Frac(v) => v.eval(vm)?,
Self::Align(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() if self.as_untyped().len() == ident.len()
&& !vm.scopes.get(ident).is_ok() && !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())); return Ok(node.spanned(self.span()));
} }
} }

BIN
tests/ref/math/matrix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

20
tests/typ/math/matrix.typ Normal file
View File

@ -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",
) $