summaryrefslogtreecommitdiff
path: root/library/src/math
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-22 13:32:44 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-22 13:32:44 +0100
commit953bdc1859f7acdbecbb7b819bc5b113a50849d0 (patch)
tree84a665faf98291e0e62500c7f857c6db0102565a /library/src/math
parenta0146b5b9b607974d5b3734ee9be0c8727cd7ea9 (diff)
Vector and case layout
Diffstat (limited to 'library/src/math')
-rw-r--r--library/src/math/matrix.rs188
1 files changed, 135 insertions, 53 deletions
diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs
index 1a84d611..aa21e9cf 100644
--- a/library/src/math/matrix.rs
+++ b/library/src/math/matrix.rs
@@ -1,5 +1,8 @@
use super::*;
+const ROW_GAP: Em = Em::new(0.5);
+const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
+
/// # Vector
/// A column vector.
///
@@ -18,7 +21,7 @@ use super::*;
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
pub struct VecNode(Vec<Content>);
@@ -47,52 +50,13 @@ impl VecNode {
}
}
-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(())
+impl LayoutMath for VecNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ let delim = ctx.outer.get(Self::DELIM);
+ layout(ctx, &self.0, Align::Center, Some(delim.open()), Some(delim.close()))
}
}
-/// A vector / matrix delimiter.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Delimiter {
- Paren,
- Bracket,
- Brace,
- Bar,
-}
-
-castable! {
- Delimiter,
- /// Delimit the vector with parentheses.
- "(" => Self::Paren,
- /// Delimit the vector with brackets.
- "[" => Self::Bracket,
- /// Delimit the vector with curly braces.
- "{" => Self::Brace,
- /// Delimit the vector with vertical bars.
- "|" => Self::Bar,
-}
-
/// # Cases
/// A case distinction.
///
@@ -113,7 +77,7 @@ castable! {
/// ## Category
/// math
#[func]
-#[capable(Texify)]
+#[capable(LayoutMath)]
#[derive(Debug, Hash)]
pub struct CasesNode(Vec<Content>);
@@ -133,14 +97,132 @@ impl CasesNode {
}
}
-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("\\\\");
+impl LayoutMath for CasesNode {
+ fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
+ layout(ctx, &self.0, Align::Left, Some('{'), None)
+ }
+}
+
+/// A vector / matrix delimiter.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Delimiter {
+ Paren,
+ Bracket,
+ Brace,
+ Bar,
+ DoubleBar,
+}
+
+impl Delimiter {
+ /// The delimiter's opening character.
+ fn open(self) -> char {
+ match self {
+ Self::Paren => '(',
+ Self::Bracket => '[',
+ Self::Brace => '{',
+ Self::Bar => '|',
+ Self::DoubleBar => '‖',
+ }
+ }
+
+ /// The delimiter's closing character.
+ fn close(self) -> char {
+ match self {
+ Self::Paren => ')',
+ Self::Bracket => ']',
+ Self::Brace => '}',
+ Self::Bar => '|',
+ Self::DoubleBar => '‖',
+ }
+ }
+}
+
+castable! {
+ Delimiter,
+ /// Delimit the vector with parentheses.
+ "(" => Self::Paren,
+ /// Delimit the vector with brackets.
+ "[" => Self::Bracket,
+ /// Delimit the vector with curly braces.
+ "{" => Self::Brace,
+ /// Delimit the vector with vertical bars.
+ "|" => Self::Bar,
+ /// Delimit the vector with double vertical bars.
+ "||" => Self::DoubleBar,
+}
+
+/// Layout a matrix.
+fn layout(
+ ctx: &mut MathContext,
+ elements: &[Content],
+ align: Align,
+ left: Option<char>,
+ right: Option<char>,
+) -> SourceResult<()> {
+ let axis = scaled!(ctx, axis_height);
+ let gap = ROW_GAP.scaled(ctx);
+
+ ctx.style(ctx.style.for_denominator());
+ let mut rows = vec![];
+ for element in elements {
+ rows.push(ctx.layout_row(element)?);
+ }
+ ctx.unstyle();
+
+ if let Some(left) = left {
+ ctx.push(GlyphFragment::new(ctx, left));
+ }
+
+ let mut frame = stack(ctx, rows, align, gap, 0);
+ frame.set_baseline(frame.height() / 2.0 + axis);
+
+ ctx.push(frame);
+
+ if let Some(right) = right {
+ ctx.push(GlyphFragment::new(ctx, right));
+ }
+
+ Ok(())
+}
+
+/// Stack rows on top of each other.
+///
+/// Add a `gap` between each row and uses the baseline of the `baseline`th
+/// row for the whole frame.
+pub(super) fn stack(
+ ctx: &MathContext,
+ rows: Vec<MathRow>,
+ align: Align,
+ gap: Abs,
+ baseline: usize,
+) -> Frame {
+ let mut width = Abs::zero();
+ let mut height = rows.len().saturating_sub(1) as f64 * gap;
+
+ let points = alignments(&rows);
+ let rows: Vec<_> =
+ rows.into_iter().map(|row| row.to_line_frame(ctx, &points)).collect();
+
+ for row in &rows {
+ height += row.height();
+ width.set_max(row.width());
+ }
+
+ let extra = VERTICAL_PADDING.of(height);
+ height += extra;
+
+ let mut y = extra / 2.0;
+ let mut frame = Frame::new(Size::new(width, height));
+
+ for (i, row) in rows.into_iter().enumerate() {
+ let x = align.position(width - row.width());
+ let pos = Point::new(x, y);
+ if i == baseline {
+ frame.set_baseline(y + row.baseline());
}
- t.push_str("\\end{cases}");
- Ok(())
+ y += row.height() + gap;
+ frame.push_frame(pos, row);
}
+
+ frame
}