Vectors and cases
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -53,6 +53,8 @@ fn scope() -> Scope {
|
||||
std.def_node::<math::AtomNode>("atom");
|
||||
std.def_node::<math::FracNode>("frac");
|
||||
std.def_node::<math::SqrtNode>("sqrt");
|
||||
std.def_node::<math::VecNode>("vec");
|
||||
std.def_node::<math::CasesNode>("cases");
|
||||
|
||||
// Layout.
|
||||
std.def_node::<layout::PageNode>("page");
|
||||
|
@ -54,7 +54,7 @@ impl Layout for MathNode {
|
||||
styles: StyleChain,
|
||||
_: &Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
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<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(())
|
||||
}
|
||||
}
|
||||
|
@ -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<RGBA>,
|
||||
}
|
||||
|
||||
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) }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
BIN
tests/ref/math/matrix.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
20
tests/typ/math/matrix.typ
Normal 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",
|
||||
) $
|