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::*;
use crate::layout::elements::BoxTransform;
use crate::layout::engine::LayoutBorderSide;
use crate::style::computed::{
    BackgroundClip, BackgroundOrigin, BorderStyle, ShapeBox, TransformBox, TransformOrigin,
};
use crate::types::{Color, CornerRadii, CornerRadius, EdgeSizes, PhysicalEdges};
use crate::util::{RasterDimensions, RasterTile};

#[test]
fn pdf_rect_converts_top_coordinates_once() {
    let rect = PdfRect::from_top(10.0, 100.0, 30.0, 40.0);
    assert_eq!(rect, PdfRect::new(10.0, 60.0, 30.0, 40.0));
    assert_eq!(rect.right(), 40.0);
    assert_eq!(rect.top(), 100.0);
}

#[test]
fn pdf_rect_resolves_css_transform_origin_from_the_top_edge() {
    let rect = PdfRect::new(10.0, 20.0, 30.0, 40.0);
    assert_eq!(
        rect.css_transform_origin(TransformOrigin {
            x_fraction: 0.5,
            y_fraction: 1.0,
            ..Default::default()
        }),
        PdfPoint::new(25.0, 20.0)
    );
}

#[test]
fn pdf_rect_maps_top_down_raster_tiles_without_flipping_rows() {
    let rect = PdfRect::new(10.0, 20.0, 100.0, 100.0);
    let dimensions = RasterDimensions {
        width: 4,
        height: 4,
    };
    assert_eq!(
        rect.raster_tile(
            dimensions,
            RasterTile {
                x: 1,
                y: 0,
                width: 2,
                height: 2,
            },
        ),
        PdfRect::new(35.0, 70.0, 50.0, 50.0)
    );
    assert_eq!(
        rect.raster_tile(
            dimensions,
            RasterTile {
                x: 1,
                y: 2,
                width: 2,
                height: 2,
            },
        ),
        PdfRect::new(35.0, 20.0, 50.0, 50.0)
    );
}

#[test]
fn pdf_rect_insets_asymmetric_physical_edges() {
    let rect = PdfRect::from_top(10.0, 100.0, 30.0, 40.0).inset(EdgeSizes::new(1.0, 2.0, 3.0, 4.0));
    assert_eq!(rect, PdfRect::new(14.0, 63.0, 24.0, 36.0));
}

#[test]
fn oversized_insets_keep_the_authored_origin_shift() {
    let rect = PdfRect::new(10.0, 20.0, 3.0, 4.0).inset(EdgeSizes::new(8.0, 9.0, 7.0, 6.0));
    assert_eq!(rect, PdfRect::new(16.0, 27.0, 0.0, 0.0));
    assert!(rect.is_empty());
}

#[test]
fn rectangle_coverage_uses_all_four_derived_edges() {
    let outer = PdfRect::new(10.0, 20.0, 30.0, 40.0);
    let inner = PdfRect::new(12.0, 22.0, 26.0, 36.0);
    assert!(outer.covers_with_margin(inner, 2.0));
    assert!(!outer.covers_with_margin(inner, 2.001));
}

#[test]
fn rectangle_intersection_returns_only_shared_area() {
    let left = PdfRect::new(10.0, 20.0, 30.0, 40.0);
    let right = PdfRect::new(25.0, 5.0, 30.0, 30.0);
    assert_eq!(
        left.intersection(right),
        Some(PdfRect::new(25.0, 20.0, 15.0, 15.0))
    );
    assert_eq!(left.intersection(PdfRect::new(40.0, 20.0, 5.0, 5.0)), None);
}

#[test]
fn box_geometry_derives_every_box_from_one_border_box() {
    let border_box = PdfRect::new(10.0, 20.0, 100.0, 80.0);
    let border = EdgeSizes::new(1.0, 2.0, 3.0, 4.0);
    let padding = EdgeSizes::new(5.0, 6.0, 7.0, 8.0);
    let geometry = PaintBoxGeometry::new(border_box, border, padding);
    let layout = LayoutBoxGeometry::new(border_box, border, padding);
    assert_eq!(geometry.padding_box(), PdfRect::new(14.0, 23.0, 94.0, 76.0));
    assert_eq!(geometry.content_box(), PdfRect::new(22.0, 30.0, 80.0, 64.0));
    assert_eq!(geometry.shape_box(ShapeBox::Border), geometry.border_box);
    assert_eq!(
        geometry.shape_box(ShapeBox::Padding),
        geometry.padding_box()
    );
    assert_eq!(
        geometry.shape_box(ShapeBox::Content),
        geometry.content_box()
    );
    assert_eq!(
        layout.background_origin_box(BackgroundOrigin::Border),
        layout.border_box
    );
    assert_eq!(
        layout.background_origin_box(BackgroundOrigin::Padding),
        layout.padding_box()
    );
    assert_eq!(
        layout.background_origin_box(BackgroundOrigin::Content),
        layout.content_box()
    );
}

#[test]
fn box_paint_uses_distinct_intrinsic_and_generated_background_geometry() {
    let border = PhysicalEdges::uniform(LayoutBorderSide::solid(0.75, Color::BLACK));
    let layout = LayoutBoxGeometry::from_layout(
        PdfRect::from_top(4.125, 100.125, 108.75, 30.375),
        &border,
        EdgeSizes::new(3.75, 0.0, 0.0, 5.625),
        None,
    );
    let page = super::super::transforms::PageContentTransform::print(PdfVector::new(150.0, 150.0));
    let geometry = layout.for_paint(page, BoxPaintGrid::Page);
    let background = geometry.background(
        BackgroundOrigin::Padding,
        BackgroundClip::Border,
        CornerRadii::ZERO,
    );

    assert_eq!(
        geometry.painting().border_box,
        page.snap_layout_box(layout.border_box)
    );
    assert_eq!(
        background.positioning_area.intrinsic_image_box(),
        layout.background_origin_box(BackgroundOrigin::Padding)
    );
    assert_eq!(
        background.positioning_area.generated_image_box(),
        geometry.painting().padding_box()
    );
    assert_eq!(background.painting_box.rect, geometry.painting().border_box);
    assert_eq!(
        background.image_destination_box,
        geometry.painting().padding_box()
    );
    assert_ne!(
        background.positioning_area.intrinsic_image_box(),
        background.positioning_area.generated_image_box()
    );
    assert_eq!(
        background.positioning_area.generated_image_box().width,
        geometry.painting().padding_box().width
    );
}

#[test]
fn unobscured_background_still_snaps_generated_image_geometry() {
    let layout = LayoutBoxGeometry::new(
        PdfRect::from_top(4.125, 100.125, 108.75, 30.375),
        EdgeSizes::uniform(0.75),
        EdgeSizes::ZERO,
    );
    let page = super::super::transforms::PageContentTransform::print(PdfVector::new(150.0, 150.0));
    let geometry = layout.for_paint(page, BoxPaintGrid::Page);
    let background = geometry.background(
        BackgroundOrigin::Padding,
        BackgroundClip::Border,
        CornerRadii::ZERO,
    );

    assert_eq!(
        background.positioning_area.generated_image_box(),
        geometry.painting().padding_box()
    );
    assert_eq!(
        background.positioning_area.intrinsic_image_box(),
        layout.padding_box()
    );
    assert_eq!(
        background.image_destination_box,
        geometry.painting().border_box
    );
}

#[test]
fn rounded_background_retains_its_css_painting_box() {
    let border = PhysicalEdges::uniform(LayoutBorderSide::solid(6.0, Color::BLACK));
    let layout = LayoutBoxGeometry::from_layout(
        PdfRect::from_top(18.75, 95.25, 127.5, 75.0),
        &border,
        EdgeSizes::ZERO,
        None,
    );
    let page = super::super::transforms::PageContentTransform::print(PdfVector::new(168.0, 114.0));
    let geometry = layout.for_paint(page, BoxPaintGrid::Page);
    let background = geometry.background(
        BackgroundOrigin::Padding,
        BackgroundClip::Border,
        CornerRadii::circular(30.0),
    );

    assert_eq!(
        background.image_destination_box,
        background.painting_box.rect
    );
}

#[test]
fn content_box_transform_reference_retains_used_border_and_padding() {
    let geometry = PaintBoxGeometry::new(
        PdfRect::new(10.0, 20.0, 100.0, 80.0),
        EdgeSizes::new(1.0, 2.0, 3.0, 4.0),
        EdgeSizes::new(5.0, 6.0, 7.0, 8.0),
    );
    let top_left = geometry.transform_reference(&BoxTransform {
        origin: TransformOrigin {
            x_fraction: 0.0,
            y_fraction: 0.0,
            ..Default::default()
        },
        reference_box: TransformBox::Content,
        ..Default::default()
    });

    assert_eq!(top_left.size(), PdfVector::new(80.0, 64.0));
    assert_eq!(top_left.pivot(), PdfPoint::new(22.0, 94.0));
    assert_eq!(top_left.local_pivot(), PdfVector::new(12.0, 6.0));

    let center = geometry.transform_reference(&BoxTransform {
        reference_box: TransformBox::Content,
        ..Default::default()
    });
    assert_eq!(center.pivot(), PdfPoint::new(62.0, 62.0));
    assert_eq!(center.local_pivot(), PdfVector::new(52.0, 38.0));
}

#[test]
fn clip_rectangle_and_radii_share_the_same_asymmetric_inset() {
    let border = EdgeSizes::new(1.0, 2.0, 3.0, 4.0);
    let radii = CornerRadii::new(
        CornerRadius::new(10.0, 20.0),
        CornerRadius::new(30.0, 40.0),
        CornerRadius::new(50.0, 60.0),
        CornerRadius::new(70.0, 80.0),
    );
    let geometry = PaintBoxGeometry::new(
        PdfRect::new(10.0, 20.0, 100.0, 100.0),
        border,
        EdgeSizes::ZERO,
    );
    let clip = geometry.background_clip_box(BackgroundClip::Padding, radii);
    assert_eq!(clip.rect, geometry.padding_box());
    assert_eq!(clip.radii, radii.fit_to(100.0, 100.0).inset(border));
    assert_eq!(
        geometry
            .background_clip_box(BackgroundClip::Text, radii)
            .rect,
        geometry.border_box
    );
}

#[test]
fn border_box_background_uses_the_outer_border_shape() {
    let geometry = PaintBoxGeometry::new(
        PdfRect::new(10.0, 20.0, 100.0, 80.0),
        EdgeSizes::uniform(6.0),
        EdgeSizes::ZERO,
    );
    let radii = CornerRadii::uniform(CornerRadius::new(20.0, 12.0));

    assert_eq!(
        geometry.background_clip_box(BackgroundClip::Border, radii),
        geometry.rounded_border_box(radii)
    );
}

#[test]
fn semantic_layout_border_carries_bleed_avoidance_to_paint_geometry() {
    let border = PhysicalEdges::uniform(LayoutBorderSide {
        width: 6.0,
        color: Color::BLACK,
        style: BorderStyle::Double,
    });
    let layout = LayoutBoxGeometry::from_layout(
        PdfRect::new(10.0, 20.0, 100.0, 80.0),
        &border,
        EdgeSizes::ZERO,
        None,
    );
    let painting = layout
        .for_paint(
            super::super::transforms::PageContentTransform::default(),
            BoxPaintGrid::Page,
        )
        .painting();
    let radii = CornerRadii::circular(12.0);

    assert_eq!(
        painting.background_clip_box(BackgroundClip::Border, radii),
        painting
            .rounded_border_box(radii)
            .inset(EdgeSizes::uniform(1.0))
    );
}