Lines
30.12 %
Functions
5.41 %
Branches
100 %
//! CSS properties for flowing content around shapes (CSS Shapes Module).
//!
//! Defines [`ShapeOutside`], [`ShapeInside`], [`ClipPath`], [`ShapeMargin`],
//! and [`ShapeImageThreshold`]. Note: `ClipPath` belongs to CSS Masking but
//! is co-located here for convenience.
use alloc::string::{String, ToString};
use crate::{
props::{
basic::{
length::{parse_float_value, FloatValue},
pixel::{
parse_pixel_value, CssPixelValueParseError,
PixelValue,
},
formatter::PrintAsCssValue,
shape::CssShape,
};
/// CSS shape-outside property for wrapping text around shapes
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
#[derive(Default)]
pub enum ShapeOutside {
#[default]
None,
Shape(CssShape),
}
impl Eq for ShapeOutside {}
impl core::hash::Hash for ShapeOutside {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
if let ShapeOutside::Shape(s) = self {
s.hash(state);
impl PartialOrd for ShapeOutside {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
impl Ord for ShapeOutside {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (self, other) {
(ShapeOutside::None, ShapeOutside::None) => core::cmp::Ordering::Equal,
(ShapeOutside::None, ShapeOutside::Shape(_)) => core::cmp::Ordering::Less,
(ShapeOutside::Shape(_), ShapeOutside::None) => core::cmp::Ordering::Greater,
(ShapeOutside::Shape(a), ShapeOutside::Shape(b)) => a.cmp(b),
impl PrintAsCssValue for ShapeOutside {
fn print_as_css_value(&self) -> String {
match self {
Self::None => "none".to_string(),
Self::Shape(shape) => shape.print_as_css_value(),
/// CSS shape-inside property for flowing text within shapes
pub enum ShapeInside {
impl Eq for ShapeInside {}
impl core::hash::Hash for ShapeInside {
if let ShapeInside::Shape(s) = self {
impl PartialOrd for ShapeInside {
impl Ord for ShapeInside {
(ShapeInside::None, ShapeInside::None) => core::cmp::Ordering::Equal,
(ShapeInside::None, ShapeInside::Shape(_)) => core::cmp::Ordering::Less,
(ShapeInside::Shape(_), ShapeInside::None) => core::cmp::Ordering::Greater,
(ShapeInside::Shape(a), ShapeInside::Shape(b)) => a.cmp(b),
impl PrintAsCssValue for ShapeInside {
/// CSS clip-path property for clipping element rendering
pub enum ClipPath {
impl Eq for ClipPath {}
impl core::hash::Hash for ClipPath {
if let ClipPath::Shape(s) = self {
impl PartialOrd for ClipPath {
impl Ord for ClipPath {
(ClipPath::None, ClipPath::None) => core::cmp::Ordering::Equal,
(ClipPath::None, ClipPath::Shape(_)) => core::cmp::Ordering::Less,
(ClipPath::Shape(_), ClipPath::None) => core::cmp::Ordering::Greater,
(ClipPath::Shape(a), ClipPath::Shape(b)) => a.cmp(b),
impl PrintAsCssValue for ClipPath {
/// CSS `shape-margin` property — adds margin to the shape-outside exclusion area.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct ShapeMargin {
pub inner: PixelValue,
impl Default for ShapeMargin {
fn default() -> Self {
Self {
inner: PixelValue::zero(),
impl PrintAsCssValue for ShapeMargin {
self.inner.print_as_css_value()
/// CSS `shape-image-threshold` property — alpha threshold for image-based shapes.
pub struct ShapeImageThreshold {
pub inner: FloatValue,
impl Default for ShapeImageThreshold {
inner: FloatValue::const_new(0),
impl PrintAsCssValue for ShapeImageThreshold {
self.inner.to_string()
// Formatting to Rust code
impl crate::format_rust_code::FormatAsRustCode for ShapeOutside {
fn format_as_rust_code(&self, _tabs: usize) -> String {
ShapeOutside::None => String::from("ShapeOutside::None"),
ShapeOutside::Shape(s) => {
let mut r = String::from("ShapeOutside::Shape(");
r.push_str(&s.format_as_rust_code());
r.push(')');
r
impl crate::format_rust_code::FormatAsRustCode for ShapeInside {
ShapeInside::None => String::from("ShapeInside::None"),
ShapeInside::Shape(s) => {
let mut r = String::from("ShapeInside::Shape(");
impl crate::format_rust_code::FormatAsRustCode for ClipPath {
ClipPath::None => String::from("ClipPath::None"),
ClipPath::Shape(s) => {
let mut r = String::from("ClipPath::Shape(");
impl crate::format_rust_code::FormatAsRustCode for ShapeMargin {
format!(
"ShapeMargin {{ inner: {} }}",
crate::format_rust_code::format_pixel_value(&self.inner)
)
impl crate::format_rust_code::FormatAsRustCode for ShapeImageThreshold {
"ShapeImageThreshold {{ inner: {} }}",
crate::format_rust_code::format_float_value(&self.inner)
// --- PARSERS ---
#[cfg(feature = "parser")]
pub mod parser {
use core::num::ParseFloatError;
use super::*;
use crate::shape_parser::{parse_shape, ShapeParseError};
/// Parser for shape-outside property
pub fn parse_shape_outside(input: &str) -> Result<ShapeOutside, ShapeParseError> {
let trimmed = input.trim();
if trimmed == "none" {
Ok(ShapeOutside::None)
} else {
let shape = parse_shape(trimmed)?;
Ok(ShapeOutside::Shape(shape))
/// Parser for shape-inside property
pub fn parse_shape_inside(input: &str) -> Result<ShapeInside, ShapeParseError> {
Ok(ShapeInside::None)
Ok(ShapeInside::Shape(shape))
/// Parser for clip-path property
pub fn parse_clip_path(input: &str) -> Result<ClipPath, ShapeParseError> {
Ok(ClipPath::None)
Ok(ClipPath::Shape(shape))
/// Parser for shape-margin property
pub fn parse_shape_margin(input: &str) -> Result<ShapeMargin, CssPixelValueParseError<'_>> {
Ok(ShapeMargin {
inner: parse_pixel_value(input)?,
})
/// Parser for shape-image-threshold property
pub fn parse_shape_image_threshold(
input: &str,
) -> Result<ShapeImageThreshold, ParseFloatError> {
let val = parse_float_value(input)?;
// value should be clamped between 0.0 and 1.0
let clamped = val.get().clamp(0.0, 1.0);
Ok(ShapeImageThreshold {
inner: FloatValue::new(clamped),
pub use parser::*;
#[cfg(all(test, feature = "parser"))]
mod tests {
#[test]
fn test_parse_shape_properties() {
// Test shape-outside
assert!(matches!(
parse_shape_outside("none").unwrap(),
ShapeOutside::None
));
parse_shape_outside("circle(50px)").unwrap(),
ShapeOutside::Shape(_)
// Test shape-inside
parse_shape_inside("none").unwrap(),
ShapeInside::None
parse_shape_inside("circle(100px at 50px 50px)").unwrap(),
ShapeInside::Shape(_)
// Test clip-path
assert!(matches!(parse_clip_path("none").unwrap(), ClipPath::None));
parse_clip_path("polygon(0 0, 100px 0, 100px 100px, 0 100px)").unwrap(),
ClipPath::Shape(_)
// Test existing properties
assert_eq!(
parse_shape_margin("10px").unwrap().inner,
PixelValue::px(10.0)
);
assert_eq!(parse_shape_image_threshold("0.5").unwrap().inner.get(), 0.5);