math-core-renderer-internal 0.6.1

Internal crate used by math-core
Documentation
use std::{fmt::Write, num::NonZeroU16};

#[cfg(feature = "serde")]
use serde::Serialize;

use crate::fmt::new_line_and_indent;

#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum ColumnAlignment {
    LeftJustified = 0,
    Centered = 1,
    RightJustified = 2,
}

#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum LineType {
    Solid = 3,
    Dashed = 4,
}

#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum ColumnSpec {
    WithContent(ColumnAlignment, Option<LineType>),
    OnlyLine(LineType),
}

#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ArraySpec<'arena> {
    pub beginning_line: Option<LineType>,
    pub is_sub: bool,
    pub column_spec: &'arena [ColumnSpec],
}

#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum Alignment {
    Centered,
    Cases,
    Alternating,
}

enum AlignmentType<'arena> {
    Predefined(Alignment),
    Custom(&'arena ArraySpec<'arena>),
    MultLine(NonZeroU16),
}

const MTD_OPEN_STYLE: &str = "<mtd style=\"";
const MTD_CLOSE_STYLE: &str = "\">";
const LEFT_ALIGN: &str = "text-align: left;justify-items: start;";
pub const RIGHT_ALIGN: &str = "text-align: right;justify-items: end;";
const PADDING_RIGHT_ZERO: &str = "padding-right: 0;";
const PADDING_LEFT_ZERO: &str = "padding-left: 0;";
const PADDING_TOP_BOTTOM_ZERO: &str = "padding-top: 0;padding-bottom: 0;";
const BORDER_RIGHT_SOLID: &str = "border-right: 0.05em solid currentcolor;";
const BORDER_RIGHT_DASHED: &str = "border-right: 0.05em dashed currentcolor;";
const SIMPLE_CENTERED: &str = "<mtd>";

pub struct ColumnGenerator<'arena> {
    typ: AlignmentType<'arena>,
    column_idx: usize,
    row_idx: usize,
}

impl<'arena> ColumnGenerator<'arena> {
    pub fn new_predefined(align: Alignment) -> Self {
        ColumnGenerator {
            typ: AlignmentType::Predefined(align),
            column_idx: 0,
            row_idx: 0,
        }
    }

    pub fn new_custom(array_spec: &'arena ArraySpec<'arena>) -> Self {
        ColumnGenerator {
            typ: AlignmentType::Custom(array_spec),
            column_idx: 0,
            row_idx: 0,
        }
    }

    pub fn new_multline(num_rows: NonZeroU16) -> Self {
        ColumnGenerator {
            typ: AlignmentType::MultLine(num_rows),
            column_idx: 0,
            row_idx: 0,
        }
    }

    pub fn reset_to_new_row(&mut self) {
        self.column_idx = 0;
        self.row_idx += 1;
    }

    pub fn write_next_mtd(
        &mut self,
        s: &mut String,
        indent_num: usize,
    ) -> Result<(), std::fmt::Error> {
        new_line_and_indent(s, indent_num);
        let column_idx = self.column_idx;
        self.column_idx += 1;
        match self.typ {
            AlignmentType::Predefined(align) => {
                let is_even = column_idx.is_multiple_of(2);
                match align {
                    Alignment::Cases => {
                        write!(s, "{MTD_OPEN_STYLE}{LEFT_ALIGN}{PADDING_RIGHT_ZERO}")?;
                        if !is_even {
                            write!(s, "padding-left:1em;")?;
                        }
                        write!(s, "{MTD_CLOSE_STYLE}")?;
                    }
                    Alignment::Centered => {
                        write!(s, "{SIMPLE_CENTERED}")?;
                    }
                    Alignment::Alternating => {
                        write!(s, "{MTD_OPEN_STYLE}")?;
                        if is_even {
                            write!(s, "{RIGHT_ALIGN}{PADDING_RIGHT_ZERO}")?;
                        } else {
                            write!(s, "{LEFT_ALIGN}{PADDING_LEFT_ZERO}")?;
                        }
                        write!(s, "{MTD_CLOSE_STYLE}")?;
                    }
                }
            }
            AlignmentType::Custom(array_spec) => {
                let mut column_spec = array_spec
                    .column_spec
                    .get(column_idx)
                    .unwrap_or(&ColumnSpec::WithContent(ColumnAlignment::Centered, None));
                while let ColumnSpec::OnlyLine(line_type) = column_spec {
                    column_spec = array_spec
                        .column_spec
                        .get(self.column_idx)
                        .unwrap_or(&ColumnSpec::WithContent(ColumnAlignment::Centered, None));
                    self.column_idx += 1;
                    write!(s, "{MTD_OPEN_STYLE}")?;
                    match line_type {
                        LineType::Solid => {
                            write!(s, "{BORDER_RIGHT_SOLID}")?;
                        }
                        LineType::Dashed => {
                            write!(s, "{BORDER_RIGHT_DASHED}")?;
                        }
                    }
                    if array_spec.is_sub {
                        write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
                    }
                    write!(s, "padding-left: 0.1em;padding-right: 0.1em;")?;
                    write!(s, "\"></mtd>")?;
                    new_line_and_indent(s, indent_num);
                }
                match column_spec {
                    ColumnSpec::WithContent(alignment, line_type) => {
                        if matches!(alignment, ColumnAlignment::Centered)
                            && line_type.is_none()
                            && !array_spec.is_sub
                        {
                            write!(s, "{SIMPLE_CENTERED}")?;
                            return Ok(());
                        }
                        write!(s, "{MTD_OPEN_STYLE}")?;
                        match alignment {
                            ColumnAlignment::LeftJustified => {
                                write!(s, "{LEFT_ALIGN}")?;
                            }
                            ColumnAlignment::Centered => {}
                            ColumnAlignment::RightJustified => {
                                write!(s, "{RIGHT_ALIGN}")?;
                            }
                        }
                        match line_type {
                            Some(LineType::Solid) => {
                                write!(s, "{BORDER_RIGHT_SOLID}")?;
                            }
                            Some(LineType::Dashed) => {
                                write!(s, "{BORDER_RIGHT_DASHED}")?;
                            }
                            _ => {}
                        }
                        if array_spec.is_sub {
                            write!(s, "{PADDING_TOP_BOTTOM_ZERO}")?;
                        }
                        write!(s, "{MTD_CLOSE_STYLE}")?;
                    }
                    ColumnSpec::OnlyLine(_) => {}
                }
            }
            AlignmentType::MultLine(num_rows) => {
                let row_idx = self.row_idx;
                // Multline is left-aligned for the first row, right-aligned for the last row,
                // and centered for all other rows.
                if row_idx == 0 {
                    write!(s, "{MTD_OPEN_STYLE}{LEFT_ALIGN}{MTD_CLOSE_STYLE}")?;
                } else if row_idx + 1 == (num_rows.get() as usize) {
                    write!(s, "{MTD_OPEN_STYLE}{RIGHT_ALIGN}{MTD_CLOSE_STYLE}")?;
                } else {
                    write!(s, "{SIMPLE_CENTERED}")?;
                }
            }
        }
        Ok(())
    }
}