Vector and case layout

This commit is contained in:
Laurenz 2023-01-22 13:32:44 +01:00
parent a0146b5b9b
commit 953bdc1859

View File

@ -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("\\\\");
}
t.push_str("\\end{cases}");
Ok(())
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());
}
y += row.height() + gap;
frame.push_frame(pos, row);
}
frame
}