use crate::compat::{Arc, Vec};
use crate::core::{Color, Font, HorizontalAlignment, Point, Rect};
#[cfg(feature = "3d")]
use crate::render::math3d::Mat4;
#[cfg(feature = "3d")]
use crate::render::mesh3d::{Camera, Mesh3D};
use crate::style::Gradient;
#[derive(Debug, Clone, PartialEq)]
pub struct CompoundPath {
points: Arc<[Point]>,
contours: Arc<[(usize, usize)]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompoundPathError {
NoContours,
InvalidContour(usize),
}
impl CompoundPath {
pub fn new(
points: Vec<Point>,
contours: Vec<(usize, usize)>,
) -> Result<Self, CompoundPathError> {
Self::validate_ranges(points.len(), &contours)?;
Ok(Self { points: points.into(), contours: contours.into() })
}
pub(crate) fn validate_ranges(
points_len: usize,
contours: &[(usize, usize)],
) -> Result<(), CompoundPathError> {
if contours.is_empty() {
return Err(CompoundPathError::NoContours);
}
let mut previous_end = 0;
for (index, &(start, end)) in contours.iter().enumerate() {
if start < previous_end || start >= end || end > points_len || end - start < 3 {
return Err(CompoundPathError::InvalidContour(index));
}
previous_end = end;
}
Ok(())
}
pub fn points(&self) -> &[Point] {
&self.points
}
pub fn contours(&self) -> &[(usize, usize)] {
&self.contours
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlendMode {
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LayerEffect {
BackdropBlur {
radius: u32,
},
BackdropBlurTint {
radius: u32,
tint: Color,
tint_weight: f32,
},
}
impl LayerEffect {
pub fn radius(self) -> u32 {
match self {
Self::BackdropBlur { radius } | Self::BackdropBlurTint { radius, .. } => radius,
}
}
pub fn tint(self) -> Option<(Color, f32)> {
match self {
Self::BackdropBlur { .. } => None,
Self::BackdropBlurTint { tint, tint_weight, .. } => Some((tint, tint_weight)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatteMode {
Alpha,
AlphaInverted,
Luma,
LumaInverted,
}
#[derive(Debug, Clone)]
pub enum RenderCommand {
FillRect {
rect: Rect,
color: Color,
},
#[cfg(feature = "3d")]
FillQuad {
corners: [Point; 4],
color: Color,
},
#[cfg(feature = "3d")]
FillQuadAA {
corners: [Point; 4],
color: Color,
},
#[cfg(feature = "3d")]
DrawMesh3D {
mesh: Mesh3D,
model: Mat4,
camera: Camera,
viewport: Rect,
},
#[cfg(feature = "3d")]
StrokeQuad {
corners: [Point; 4],
color: Color,
width: u32,
},
#[cfg(feature = "3d")]
DrawImageQuad {
corners: [Point; 4],
width: u32,
height: u32,
data: Arc<[u8]>,
},
DrawRect {
rect: Rect,
color: Color,
},
DrawRectStroke {
rect: Rect,
color: Color,
width: u32,
},
FillRoundedRect {
rect: Rect,
radius: u32,
color: Color,
},
FillRoundedRectAA {
rect: Rect,
radius: u32,
color: Color,
},
DrawRoundedRectStroke {
rect: Rect,
radius: u32,
color: Color,
width: u32,
},
DrawRoundedRectStrokeAA {
rect: Rect,
radius: u32,
color: Color,
width: u32,
},
DrawLine {
from: Point,
to: Point,
color: Color,
},
DrawLineAA {
from: Point,
to: Point,
color: Color,
},
DrawLineStrokeAA {
from: Point,
to: Point,
color: Color,
width: u32,
},
DrawLineStroke {
from: Point,
to: Point,
color: Color,
width: u32,
},
FillCircle {
center: Point,
radius: u32,
color: Color,
},
FillCircleAA {
center: Point,
radius: u32,
color: Color,
},
DrawCircle {
center: Point,
radius: u32,
color: Color,
},
DrawCircleStroke {
center: Point,
radius: u32,
color: Color,
width: u32,
},
DrawText {
origin: Point,
text: Arc<str>,
font: Font,
color: Color,
alignment: HorizontalAlignment,
},
DrawImage {
x: i32,
y: i32,
width: u32,
height: u32,
data: Arc<[u8]>,
},
PushClip {
x: i32,
y: i32,
width: u32,
height: u32,
},
PopClip,
PushLayer {
bounds: Rect,
effect: Option<LayerEffect>,
},
PopLayer,
PushLayerGroup {
bounds: Rect,
effect: Option<LayerEffect>,
opacity: f32,
cache_key: Option<u64>,
},
DrawGradient {
rect: Rect,
gradient: Arc<Gradient>,
},
DrawArc {
center: Point,
radius: u32,
start_angle: f32,
end_angle: f32,
color: Color,
filled: bool,
},
DrawPath {
points: Arc<[Point]>,
closed: bool,
color: Color,
filled: bool,
width: u32,
},
FillPathNonZero {
path: CompoundPath,
color: Color,
},
BoxShadow {
rect: Rect,
color: Color,
offset_x: i32,
offset_y: i32,
blur_radius: u32,
spread: i32,
},
Blur {
radius: u32,
},
ClipPath {
points: Arc<[Point]>,
},
SetBlendMode {
mode: BlendMode,
},
PushMatte {
bounds: Rect,
mode: MatteMode,
},
PopMatte,
EndMatte,
DrawConicGradient {
center: Point,
start_angle: f32,
stops: Arc<[(f32, Color)]>,
},
}
impl RenderCommand {
pub fn bounds(&self) -> Option<Rect> {
match self {
Self::FillRect { rect, .. }
| Self::DrawRect { rect, .. }
| Self::DrawRectStroke { rect, .. }
| Self::FillRoundedRect { rect, .. }
| Self::FillRoundedRectAA { rect, .. }
| Self::DrawRoundedRectStroke { rect, .. }
| Self::DrawRoundedRectStrokeAA { rect, .. }
| Self::BoxShadow { rect, .. }
| Self::DrawGradient { rect, .. }
| Self::PushLayer { bounds: rect, .. }
| Self::PushLayerGroup { bounds: rect, .. }
| Self::PushMatte { bounds: rect, .. } => Some(*rect),
Self::DrawImage { x, y, width, height, .. } => Some(Rect::new(*x, *y, *width, *height)),
#[cfg(feature = "3d")]
Self::FillQuad { corners, .. }
| Self::FillQuadAA { corners, .. }
| Self::StrokeQuad { corners, .. } => Some(bounds_of_corners(corners)),
#[cfg(feature = "3d")]
Self::DrawImageQuad { corners, width, height, .. } => {
let mut rect = bounds_of_corners(corners);
if rect.width == 0 {
rect.width = *width;
}
if rect.height == 0 {
rect.height = *height;
}
Some(rect)
}
#[cfg(feature = "3d")]
Self::DrawMesh3D { viewport, .. } => Some(*viewport),
Self::DrawLine { from, to, .. } | Self::DrawLineAA { from, to, .. } => {
Some(bounds_of_points(&[*from, *to], 1))
}
Self::DrawLineStroke { from, to, width, .. }
| Self::DrawLineStrokeAA { from, to, width, .. } => {
Some(bounds_of_points(&[*from, *to], *width))
}
Self::FillCircle { center, radius, .. }
| Self::FillCircleAA { center, radius, .. }
| Self::DrawCircle { center, radius, .. } => {
Some(bounds_around_point(*center, *radius))
}
Self::DrawCircleStroke { center, radius, width, .. } => {
Some(bounds_around_point(*center, radius.saturating_add(*width)))
}
Self::DrawArc { center, radius, .. } => Some(bounds_around_point(*center, *radius)),
Self::DrawText { origin, text, font, alignment, .. } => {
Some(bounds_of_text(*origin, text, font, *alignment))
}
Self::PushClip { x, y, width, height } => Some(Rect::new(*x, *y, *width, *height)),
Self::DrawPath { points, width, .. } => Some(bounds_of_points(points, *width)),
Self::FillPathNonZero { path, .. } => Some(bounds_of_points(path.points(), 0)),
Self::ClipPath { points } => Some(bounds_of_points(points, 0)),
Self::PopClip
| Self::PopLayer
| Self::PopMatte
| Self::EndMatte
| Self::Blur { .. }
| Self::SetBlendMode { .. }
| Self::DrawConicGradient { .. } => None,
}
}
}
#[cfg(feature = "3d")]
fn bounds_of_corners(corners: &[Point; 4]) -> Rect {
let min_x = corners.iter().map(|point| point.x).min().unwrap_or(0);
let min_y = corners.iter().map(|point| point.y).min().unwrap_or(0);
let max_x = corners.iter().map(|point| point.x).max().unwrap_or(min_x);
let max_y = corners.iter().map(|point| point.y).max().unwrap_or(min_y);
Rect::new(
min_x,
min_y,
max_x.saturating_sub(min_x).max(0) as u32,
max_y.saturating_sub(min_y).max(0) as u32,
)
}
fn bounds_of_points(points: &[Point], stroke: u32) -> Rect {
let Some(first) = points.first() else {
return Rect::new(0, 0, 0, 0);
};
let mut min_x = first.x;
let mut min_y = first.y;
let mut max_x = first.x;
let mut max_y = first.y;
for point in &points[1..] {
min_x = min_x.min(point.x);
min_y = min_y.min(point.y);
max_x = max_x.max(point.x);
max_y = max_y.max(point.y);
}
let pad = stroke as i32;
Rect::new(
min_x.saturating_sub(pad),
min_y.saturating_sub(pad),
max_x.saturating_sub(min_x).max(0) as u32 + stroke.saturating_mul(2),
max_y.saturating_sub(min_y).max(0) as u32 + stroke.saturating_mul(2),
)
}
fn bounds_around_point(center: Point, radius: u32) -> Rect {
let radius = radius as i32;
Rect::new(
center.x.saturating_sub(radius),
center.y.saturating_sub(radius),
(radius.saturating_mul(2)).max(0) as u32,
(radius.saturating_mul(2)).max(0) as u32,
)
}
fn bounds_of_text(origin: Point, text: &str, font: &Font, alignment: HorizontalAlignment) -> Rect {
let em = font.size().max(1.0);
let scalars = text.chars().count().max(1) as f32;
let width = (scalars * em + em * 2.0).round().max(1.0) as u32;
let height = (em * 2.0).round().max(1.0) as u32;
let x = match alignment {
HorizontalAlignment::Left => origin.x,
HorizontalAlignment::Center => origin.x.saturating_sub(width as i32 / 2),
HorizontalAlignment::Right => origin.x.saturating_sub(width as i32),
};
Rect::new(x.saturating_sub(em as i32), origin.y.saturating_sub(em as i32), width, height)
}