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 crate::layout::elements::{GridRow, LayoutVisitor, TableRow, TextBlock};
use crate::layout::engine::{
    FlexRow, Page, TextRun, layout_with_rules, layout_with_rules_and_fonts, visit_layout_tree,
};
use crate::parser::css::parse_stylesheet;
use crate::parser::html::parse_html_with_styles;
use crate::types::{Margin, PageSize};
use std::collections::HashMap;

#[derive(Default)]
struct RunCollector(Vec<TextRun>);

impl LayoutVisitor for RunCollector {
    fn visit_text_block(&mut self, block: &TextBlock) {
        self.0.extend(
            block
                .lines
                .iter()
                .flat_map(|line| line.runs.iter().cloned()),
        );
    }

    fn visit_flex_row(&mut self, row: &FlexRow) {
        self.0.extend(
            row.content
                .cells
                .iter()
                .flat_map(|cell| cell.lines.iter())
                .flat_map(|line| line.runs.iter().cloned()),
        );
    }

    fn visit_grid_row(&mut self, row: &GridRow) {
        self.0.extend(
            row.content
                .cells
                .iter()
                .flat_map(|cell| &cell.layout.content.lines)
                .flat_map(|line| line.runs.iter().cloned()),
        );
    }

    fn visit_table_row(&mut self, row: &TableRow) {
        self.0.extend(
            row.content
                .cells
                .iter()
                .flat_map(|cell| &cell.layout.content.lines)
                .flat_map(|line| line.runs.iter().cloned()),
        );
    }
}

pub(super) fn layout_pages(markup: &str) -> Vec<Page> {
    layout_pages_at(markup, PageSize::new(390.0, 150.0))
}

pub(super) fn layout_pages_at(markup: &str, page_size: PageSize) -> Vec<Page> {
    let document = parse_html_with_styles(markup).expect("valid layout fixture");
    let rules = document
        .stylesheets
        .iter()
        .flat_map(|stylesheet| parse_stylesheet(stylesheet))
        .collect::<Vec<_>>();
    layout_with_rules(&document.nodes, page_size, Margin::uniform(0.0), &rules)
}

pub(super) fn layout_pages_with_fonts(
    markup: &str,
    fonts: &HashMap<String, crate::parser::ttf::TtfFont>,
) -> Vec<Page> {
    layout_pages_at_with_fonts(
        markup,
        PageSize::new(390.0, 150.0),
        Margin::uniform(0.0),
        fonts,
    )
}

pub(super) fn layout_pages_at_with_fonts(
    markup: &str,
    page_size: PageSize,
    margin: Margin,
    fonts: &HashMap<String, crate::parser::ttf::TtfFont>,
) -> Vec<Page> {
    let document = parse_html_with_styles(markup).expect("valid layout fixture");
    let rules = document
        .stylesheets
        .iter()
        .flat_map(|stylesheet| parse_stylesheet(stylesheet))
        .collect::<Vec<_>>();
    layout_with_rules_and_fonts(
        &document.nodes,
        page_size,
        margin,
        &rules,
        fonts,
        None,
        300.0,
        crate::layout::paginate::FootnoteAreaLayout::default(),
    )
}

fn collected_runs(markup: &str) -> Vec<TextRun> {
    let mut collector = RunCollector::default();
    for page in layout_pages(markup) {
        for (_, element) in page.elements {
            visit_layout_tree(element.as_ref(), &mut collector);
        }
    }
    collector.0
}

pub(super) fn visible_runs(markup: &str) -> Vec<TextRun> {
    collected_runs(markup)
        .into_iter()
        .filter(|run| !run.text.trim().is_empty())
        .collect()
}

pub(super) fn visible_inline_box_runs(markup: &str) -> Vec<TextRun> {
    collected_runs(markup)
        .into_iter()
        .flat_map(|run| {
            run.inline_box
                .into_iter()
                .flat_map(|inline| inline.lines.into_iter())
                .flat_map(|line| line.runs)
        })
        .filter(|run| !run.text.trim().is_empty())
        .collect()
}