oxipdf-ir 0.1.0

Intermediate representation types for the oxipdf PDF engine
Documentation
//! Visual style properties: backgrounds, borders, opacity.

use crate::node::ImageFormat;
use crate::units::Pt;

pub use crate::color::Color;

/// Visual style properties for painting and decoration.
#[derive(Debug, Clone, PartialEq)]
pub struct VisualStyle {
    /// Background color. `None` means transparent (no background painted).
    pub background_color: Option<Color>,
    /// Background image. Rendered after background color, before borders.
    pub background_image: Option<BackgroundImage>,
    /// Background gradient. Rendered instead of background color when set.
    pub background_gradient: Option<Gradient>,
    /// Box shadows. Rendered BEFORE background (behind the element).
    /// Multiple shadows are rendered in reverse order (last in list = bottommost).
    pub box_shadows: Vec<BoxShadow>,
    /// Opacity (0.0 = fully transparent, 1.0 = fully opaque).
    /// Applied to the entire node and its subtree via PDF ExtGState.
    pub opacity: f32,

    // --- Borders ---
    pub border_top: BorderSide,
    pub border_right: BorderSide,
    pub border_bottom: BorderSide,
    pub border_left: BorderSide,

    // --- Border radius ---
    /// Horizontal radius for each corner. Use as circular radius when
    /// the corresponding `_ry` field is zero.
    pub border_radius_top_left: Pt,
    pub border_radius_top_right: Pt,
    pub border_radius_bottom_right: Pt,
    pub border_radius_bottom_left: Pt,
    /// Vertical radius for elliptical corners. When zero (default),
    /// the horizontal radius is used for both axes (circular corner).
    pub border_radius_top_left_ry: Pt,
    pub border_radius_top_right_ry: Pt,
    pub border_radius_bottom_right_ry: Pt,
    pub border_radius_bottom_left_ry: Pt,
}

/// Background image for a container node.
#[derive(Debug, Clone, PartialEq)]
pub struct BackgroundImage {
    /// Raw image bytes.
    pub data: Vec<u8>,
    /// Image format (JPEG or PNG).
    pub format: ImageFormat,
    /// How the image is sized within the container.
    pub size: BackgroundSize,
    /// Horizontal position of the image within the container.
    pub position_x: BackgroundPosition,
    /// Vertical position of the image within the container.
    pub position_y: BackgroundPosition,
    /// Whether and how the background image is tiled.
    pub repeat: BackgroundRepeat,
}

/// Background image repeat/tiling mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BackgroundRepeat {
    /// Single image, no tiling (default).
    #[default]
    NoRepeat,
    /// Tile horizontally only.
    RepeatX,
    /// Tile vertically only.
    RepeatY,
    /// Tile in both directions.
    Repeat,
}

/// A gradient background for a container.
#[derive(Debug, Clone, PartialEq)]
pub enum Gradient {
    /// Linear gradient from start point to end point.
    Linear(LinearGradient),
    /// Radial gradient from center outward.
    Radial(RadialGradient),
}

/// A linear (axial) gradient.
#[derive(Debug, Clone, PartialEq)]
pub struct LinearGradient {
    /// Angle in degrees (0 = left-to-right, 90 = bottom-to-top).
    pub angle_degrees: f64,
    /// Color stops in order.
    pub stops: Vec<GradientStop>,
}

/// A radial gradient.
#[derive(Debug, Clone, PartialEq)]
pub struct RadialGradient {
    /// Center position as fraction of container (0.5, 0.5 = center).
    pub center_x: f64,
    pub center_y: f64,
    /// Radius as fraction of the smaller container dimension.
    pub radius: f64,
    /// Color stops in order.
    pub stops: Vec<GradientStop>,
}

/// A single color stop in a gradient.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GradientStop {
    /// Position along the gradient (0.0 = start, 1.0 = end).
    pub position: f32,
    /// Color at this position.
    pub color: Color,
}

/// A box shadow effect on a container.
#[derive(Debug, Clone, PartialEq)]
pub struct BoxShadow {
    /// Horizontal offset from the element edge.
    pub offset_x: Pt,
    /// Vertical offset from the element edge.
    pub offset_y: Pt,
    /// Blur radius. Larger values produce softer, more spread-out shadows.
    /// Approximated via layered translucent rectangles.
    pub blur_radius: Pt,
    /// Spread radius. Positive expands the shadow, negative shrinks it.
    pub spread_radius: Pt,
    /// Shadow color (including alpha for transparency).
    pub color: Color,
    /// If `true`, the shadow is drawn inside the element (inset shadow).
    /// If `false`, it's a drop shadow drawn behind the element.
    pub inset: bool,
}

impl BoxShadow {
    /// Create a simple drop shadow.
    #[must_use]
    pub fn drop_shadow(offset_x: Pt, offset_y: Pt, blur: Pt, color: Color) -> Self {
        Self {
            offset_x,
            offset_y,
            blur_radius: blur,
            spread_radius: Pt::ZERO,
            color,
            inset: false,
        }
    }
}

/// How a background image is sized within its container.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum BackgroundSize {
    /// Scale to cover the entire container, preserving aspect ratio.
    /// May crop the image.
    #[default]
    Cover,
    /// Scale to fit entirely within the container, preserving aspect ratio.
    /// May leave gaps (background color shows through).
    Contain,
    /// Use the image's intrinsic dimensions (no scaling).
    Auto,
    /// Explicit width and height. `None` means auto for that axis.
    Explicit {
        width: Option<Pt>,
        height: Option<Pt>,
    },
}

/// Positioning of a background image along one axis.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum BackgroundPosition {
    /// Start edge (left for X, top for Y).
    #[default]
    Start,
    /// Centered.
    Center,
    /// End edge (right for X, bottom for Y).
    End,
    /// Explicit offset from the start edge.
    Length(Pt),
}

impl Default for VisualStyle {
    fn default() -> Self {
        Self {
            background_color: None,
            background_image: None,
            background_gradient: None,
            box_shadows: Vec::new(),
            opacity: 1.0,
            border_top: BorderSide::NONE,
            border_right: BorderSide::NONE,
            border_bottom: BorderSide::NONE,
            border_left: BorderSide::NONE,
            border_radius_top_left: Pt::ZERO,
            border_radius_top_right: Pt::ZERO,
            border_radius_bottom_right: Pt::ZERO,
            border_radius_bottom_left: Pt::ZERO,
            border_radius_top_left_ry: Pt::ZERO,
            border_radius_top_right_ry: Pt::ZERO,
            border_radius_bottom_right_ry: Pt::ZERO,
            border_radius_bottom_left_ry: Pt::ZERO,
        }
    }
}

impl VisualStyle {
    /// Returns `true` if any corner has a non-zero border radius.
    #[must_use]
    pub fn has_border_radius(&self) -> bool {
        self.border_radius_top_left.get() > 0.0
            || self.border_radius_top_right.get() > 0.0
            || self.border_radius_bottom_right.get() > 0.0
            || self.border_radius_bottom_left.get() > 0.0
    }
}

/// A single side of a border.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BorderSide {
    /// Width of this border edge.
    pub width: Pt,
    /// Line style.
    pub style: BorderStyle,
    /// Border color.
    pub color: Color,
}

impl BorderSide {
    /// A border side with zero width (not rendered).
    pub const NONE: Self = Self {
        width: Pt::ZERO,
        style: BorderStyle::None,
        color: Color::BLACK,
    };

    /// Returns `true` if this border side should be rendered.
    #[must_use]
    pub fn is_visible(&self) -> bool {
        self.width.get() > 0.0 && self.style != BorderStyle::None
    }
}

/// Border line style.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BorderStyle {
    #[default]
    None,
    Solid,
    Dashed,
    Dotted,
}