ironpress 1.4.4

Pure Rust HTML/CSS/Markdown to PDF converter with layout engine, LaTeX math, tables, images, custom fonts, and streaming output. No browser, no system dependencies.
Documentation
use super::conflict::collapsed_style_rank;
use super::model::GridEdgeAxis;
use crate::layout::elements::{
    CollapsedBorderEdge, CollapsedBorderJoint, CollapsedBorderJoints, CollapsedBorderLine,
    CollapsedTableBorders,
};
use crate::layout::engine::LayoutBorderSide;

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub(super) struct BorderPaintOrder {
    pub(super) row: usize,
    pub(super) column: usize,
    pub(super) cell: usize,
}

#[derive(Debug, Clone, Copy, Default)]
pub(super) struct ResolvedGridEdge {
    pub(super) side: LayoutBorderSide,
    pub(super) paint_order: Option<BorderPaintOrder>,
}

impl ResolvedGridEdge {
    fn compare_for_paint(self, other: Self) -> EdgePaintComparison {
        match (self.side.paints(), other.side.paints()) {
            (false, false) => return EdgePaintComparison::Tie,
            (true, false) => return EdgePaintComparison::First,
            (false, true) => return EdgePaintComparison::Second,
            (true, true) => {}
        }
        match self
            .side
            .width
            .partial_cmp(&other.side.width)
            .unwrap_or(std::cmp::Ordering::Equal)
        {
            std::cmp::Ordering::Greater => EdgePaintComparison::First,
            std::cmp::Ordering::Less => EdgePaintComparison::Second,
            std::cmp::Ordering::Equal => {
                match collapsed_style_rank(self.side.style)
                    .cmp(&collapsed_style_rank(other.side.style))
                {
                    std::cmp::Ordering::Greater => EdgePaintComparison::First,
                    std::cmp::Ordering::Less => EdgePaintComparison::Second,
                    std::cmp::Ordering::Equal => match (self.paint_order, other.paint_order) {
                        (Some(first), Some(second)) if first < second => EdgePaintComparison::First,
                        (Some(first), Some(second)) if first > second => {
                            EdgePaintComparison::Second
                        }
                        _ => EdgePaintComparison::Tie,
                    },
                }
            }
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EdgePaintComparison {
    First,
    Second,
    Tie,
}

impl EdgePaintComparison {
    fn winner(self, first: ResolvedGridEdge, second: ResolvedGridEdge) -> ResolvedGridEdge {
        if self == Self::First { first } else { second }
    }
}

#[derive(Debug)]
pub(super) struct ResolvedBorderGrid {
    row_count: usize,
    column_count: usize,
    horizontal: Vec<ResolvedGridEdge>,
    vertical: Vec<ResolvedGridEdge>,
}

impl ResolvedBorderGrid {
    pub(super) fn new(row_count: usize, column_count: usize) -> Self {
        Self {
            row_count,
            column_count,
            horizontal: vec![
                ResolvedGridEdge::default();
                row_count.saturating_add(1).saturating_mul(column_count)
            ],
            vertical: vec![
                ResolvedGridEdge::default();
                row_count.saturating_mul(column_count.saturating_add(1))
            ],
        }
    }

    fn index(&self, axis: GridEdgeAxis, line: usize, track: usize) -> Option<usize> {
        match axis {
            GridEdgeAxis::Horizontal if line <= self.row_count && track < self.column_count => line
                .checked_mul(self.column_count)
                .and_then(|offset| offset.checked_add(track)),
            GridEdgeAxis::Vertical if line <= self.column_count && track < self.row_count => track
                .checked_mul(self.column_count.saturating_add(1))
                .and_then(|offset| offset.checked_add(line)),
            _ => None,
        }
    }

    fn get(&self, axis: GridEdgeAxis, line: usize, track: usize) -> ResolvedGridEdge {
        let Some(index) = self.index(axis, line, track) else {
            return ResolvedGridEdge::default();
        };
        match axis {
            GridEdgeAxis::Horizontal => self.horizontal.get(index).copied().unwrap_or_default(),
            GridEdgeAxis::Vertical => self.vertical.get(index).copied().unwrap_or_default(),
        }
    }

    pub(super) fn set(
        &mut self,
        axis: GridEdgeAxis,
        line: usize,
        track: usize,
        edge: ResolvedGridEdge,
    ) {
        let Some(index) = self.index(axis, line, track) else {
            return;
        };
        let slot = match axis {
            GridEdgeAxis::Horizontal => self.horizontal.get_mut(index),
            GridEdgeAxis::Vertical => self.vertical.get_mut(index),
        };
        if let Some(slot) = slot {
            *slot = edge;
        }
    }

    fn intersection(&self, row_line: usize, column_line: usize) -> GridIntersection {
        GridIntersection {
            before: column_line
                .checked_sub(1)
                .map(|column| self.get(GridEdgeAxis::Horizontal, row_line, column))
                .unwrap_or_default(),
            after: self.get(GridEdgeAxis::Horizontal, row_line, column_line),
            over: row_line
                .checked_sub(1)
                .map(|row| self.get(GridEdgeAxis::Vertical, column_line, row))
                .unwrap_or_default(),
            under: self.get(GridEdgeAxis::Vertical, column_line, row_line),
        }
    }

    fn edge_with_joints(
        &self,
        axis: GridEdgeAxis,
        line: usize,
        track: usize,
    ) -> CollapsedBorderEdge {
        let edge = self.get(axis, line, track);
        let (start, end) = match axis {
            GridEdgeAxis::Horizontal => (
                self.intersection(line, track)
                    .joint_for(axis, EdgeEndpoint::Start),
                self.intersection(line, track.saturating_add(1))
                    .joint_for(axis, EdgeEndpoint::End),
            ),
            GridEdgeAxis::Vertical => (
                self.intersection(track, line)
                    .joint_for(axis, EdgeEndpoint::Start),
                self.intersection(track.saturating_add(1), line)
                    .joint_for(axis, EdgeEndpoint::End),
            ),
        };
        CollapsedBorderEdge::new(edge.side, CollapsedBorderJoints { start, end })
    }

    pub(super) fn into_rows(self) -> Vec<CollapsedTableBorders> {
        (0..self.row_count)
            .map(|row| {
                let block_start = (0..self.column_count)
                    .map(|column| self.edge_with_joints(GridEdgeAxis::Horizontal, row, column))
                    .collect();
                let block_axis = (0..=self.column_count)
                    .map(|column_line| {
                        self.edge_with_joints(GridEdgeAxis::Vertical, column_line, row)
                    })
                    .collect();
                let block_end = if row.saturating_add(1) == self.row_count {
                    (0..self.column_count)
                        .map(|column| {
                            self.edge_with_joints(GridEdgeAxis::Horizontal, self.row_count, column)
                        })
                        .collect()
                } else {
                    Vec::new()
                };
                CollapsedTableBorders::new(
                    CollapsedBorderLine::new(block_start),
                    CollapsedBorderLine::new(block_axis),
                    CollapsedBorderLine::new(block_end),
                )
            })
            .collect()
    }
}

#[derive(Debug, Clone, Copy)]
struct GridIntersection {
    before: ResolvedGridEdge,
    after: ResolvedGridEdge,
    over: ResolvedGridEdge,
    under: ResolvedGridEdge,
}

impl GridIntersection {
    fn joint_for(self, axis: GridEdgeAxis, endpoint: EdgeEndpoint) -> CollapsedBorderJoint {
        let inline_comparison = self.before.compare_for_paint(self.after);
        let block_comparison = self.over.compare_for_paint(self.under);
        let inline_winner = inline_comparison.winner(self.before, self.after);
        let block_winner = block_comparison.winner(self.over, self.under);
        let inline_vs_block = inline_winner.compare_for_paint(block_winner);
        let edge_owns_joint = match (axis, endpoint) {
            (GridEdgeAxis::Horizontal, EdgeEndpoint::Start) => {
                inline_vs_block != EdgePaintComparison::Second
                    && inline_comparison != EdgePaintComparison::First
            }
            (GridEdgeAxis::Horizontal, EdgeEndpoint::End) => {
                inline_vs_block != EdgePaintComparison::Second
                    && inline_comparison != EdgePaintComparison::Second
            }
            (GridEdgeAxis::Vertical, EdgeEndpoint::Start) => {
                inline_vs_block != EdgePaintComparison::First
                    && block_comparison != EdgePaintComparison::First
            }
            (GridEdgeAxis::Vertical, EdgeEndpoint::End) => {
                inline_vs_block != EdgePaintComparison::First
                    && block_comparison != EdgePaintComparison::Second
            }
        };
        let perpendicular_width = match axis {
            GridEdgeAxis::Horizontal => block_winner.side.width,
            GridEdgeAxis::Vertical => inline_winner.side.width,
        };
        CollapsedBorderJoint::resolve(perpendicular_width, edge_owns_joint)
    }
}

#[derive(Debug, Clone, Copy)]
enum EdgeEndpoint {
    Start,
    End,
}