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::*;

mod declaration;
mod mapping;
mod values;

use declaration::*;
use mapping::*;
pub(in crate::style::computed) use values::parse_border_style;
use values::*;

pub(super) fn is_border_property(property: &str) -> bool {
    BorderDeclaration::parse(property).is_some()
}

pub(super) fn apply(
    style: &mut ComputedStyle,
    map: &StyleMap,
    parent: &ComputedStyle,
    length_context: crate::style::resolve::LengthResolutionContext,
    font_metrics: FontMetrics<'_>,
) {
    let mapping = FlowMapping::from_style(style);
    let parent_mapping = FlowMapping::from_style(parent);
    for property in &map.declaration_order {
        let Some(declaration) = BorderDeclaration::parse(property) else {
            continue;
        };
        let Some(value) = map.get(property) else {
            continue;
        };
        if let Some(keyword) = CssWide::from_value(value, style) {
            apply_css_wide(style, parent, declaration, keyword, mapping, parent_mapping);
            continue;
        }
        match declaration {
            BorderDeclaration::Sides(declaration) => apply_side_value(
                style,
                declaration,
                value,
                mapping,
                length_context,
                font_metrics,
            ),
            BorderDeclaration::Radius(target) => {
                apply_radius_value(style, target, value, mapping, length_context, font_metrics)
            }
        }
    }
}

fn apply_css_wide(
    style: &mut ComputedStyle,
    parent: &ComputedStyle,
    declaration: BorderDeclaration,
    keyword: CssWide,
    mapping: FlowMapping,
    parent_mapping: FlowMapping,
) {
    match declaration {
        BorderDeclaration::Sides(declaration) => {
            declaration.targets.for_each(|selector| {
                let target = selector.physical(mapping);
                let source = match keyword {
                    CssWide::Initial => BorderSide::default(),
                    CssWide::Inherit => *parent.border.get(selector.physical(parent_mapping)),
                };
                let target = style.border.get_mut(target);
                copy_component(target, source, declaration.component);
            });
        }
        BorderDeclaration::Radius(target) => {
            target.for_each(mapping, |corner| {
                let value = match keyword {
                    CssWide::Initial => SpecifiedCornerRadius::default(),
                    CssWide::Inherit => {
                        let source = target.source_corner(corner, parent_mapping);
                        parent.border_radii.get(source)
                    }
                };
                style.border_radii.set(corner, value);
            });
        }
    }
}

impl SideTargets {
    fn for_each(self, mut visit: impl FnMut(SideSelector)) {
        match self {
            Self::AllPhysical => {
                for side in [
                    PhysicalSide::Top,
                    PhysicalSide::Right,
                    PhysicalSide::Bottom,
                    PhysicalSide::Left,
                ] {
                    visit(SideSelector::Physical(side));
                }
            }
            Self::One(side) => visit(side),
            Self::LogicalAxis(axis) => {
                for side in axis.sides() {
                    visit(SideSelector::Logical(side));
                }
            }
        }
    }
}

fn copy_component(target: &mut BorderSide, source: BorderSide, component: SideComponent) {
    match component {
        SideComponent::All => *target = source,
        SideComponent::Width => target.specified_width = source.specified_width,
        SideComponent::Style => target.style = source.style,
        SideComponent::Color => target.color = source.color,
    }
}

impl RadiusTarget {
    fn for_each(self, mapping: FlowMapping, mut visit: impl FnMut(PhysicalCorner)) {
        match self {
            Self::AllPhysical => {
                for corner in [
                    PhysicalCorner::TopLeft,
                    PhysicalCorner::TopRight,
                    PhysicalCorner::BottomRight,
                    PhysicalCorner::BottomLeft,
                ] {
                    visit(corner);
                }
            }
            Self::Physical(corner) => visit(corner),
            Self::Logical(corner) => visit(mapping.corner(corner)),
        }
    }

    const fn source_corner(
        self,
        target: PhysicalCorner,
        parent_mapping: FlowMapping,
    ) -> PhysicalCorner {
        match self {
            Self::AllPhysical | Self::Physical(_) => target,
            Self::Logical(logical) => parent_mapping.corner(logical),
        }
    }
}

impl SpecifiedCornerRadii {
    const fn get(self, corner: PhysicalCorner) -> SpecifiedCornerRadius {
        match corner {
            PhysicalCorner::TopLeft => self.top_left,
            PhysicalCorner::TopRight => self.top_right,
            PhysicalCorner::BottomRight => self.bottom_right,
            PhysicalCorner::BottomLeft => self.bottom_left,
        }
    }

    fn set(&mut self, corner: PhysicalCorner, value: SpecifiedCornerRadius) {
        match corner {
            PhysicalCorner::TopLeft => self.top_left = value,
            PhysicalCorner::TopRight => self.top_right = value,
            PhysicalCorner::BottomRight => self.bottom_right = value,
            PhysicalCorner::BottomLeft => self.bottom_left = value,
        }
    }
}