use crate::compat::Feature; use crate::context::PropertyHandlerContext; use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::error::{ParserError, PrinterError}; use crate::logical::PropertyCategory; use crate::macros::{define_shorthand, rect_shorthand, size_shorthand}; use crate::printer::Printer; use crate::properties::{Property, PropertyId}; use crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss}; use crate::values::{length::LengthPercentageOrAuto, rect::Rect, size::Size2D}; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; rect_shorthand! { /// A value for the [margin](https://drafts.csswg.org/css-box-4/#propdef-margin) shorthand property. pub struct Margin { MarginTop, MarginRight, MarginBottom, MarginLeft } } rect_shorthand! { /// A value for the [padding](https://drafts.csswg.org/css-box-4/#propdef-padding) shorthand property. pub struct Padding { PaddingTop, PaddingRight, PaddingBottom, PaddingLeft } } rect_shorthand! { /// A value for the [scroll-margin](https://drafts.csswg.org/css-scroll-snap/#scroll-margin) shorthand property. pub struct ScrollMargin { ScrollMarginTop, ScrollMarginRight, ScrollMarginBottom, ScrollMarginLeft } } rect_shorthand! { /// A value for the [scroll-padding](https://drafts.csswg.org/css-scroll-snap/#scroll-padding) shorthand property. pub struct ScrollPadding { ScrollPaddingTop, ScrollPaddingRight, ScrollPaddingBottom, ScrollPaddingLeft } } rect_shorthand! { /// A value for the [inset](https://drafts.csswg.org/css-logical/#propdef-inset) shorthand property. pub struct Inset { Top, Right, Bottom, Left } } size_shorthand! { /// A value for the [margin-block](https://drafts.csswg.org/css-logical/#propdef-margin-block) shorthand property. pub struct MarginBlock { /// The block start value. block_start: MarginBlockStart, /// The block end value. block_end: MarginBlockEnd, } } size_shorthand! { /// A value for the [margin-inline](https://drafts.csswg.org/css-logical/#propdef-margin-inline) shorthand property. pub struct MarginInline { /// The inline start value. inline_start: MarginInlineStart, /// The inline end value. inline_end: MarginInlineEnd, } } size_shorthand! { /// A value for the [padding-block](https://drafts.csswg.org/css-logical/#propdef-padding-block) shorthand property. pub struct PaddingBlock { /// The block start value. block_start: PaddingBlockStart, /// The block end value. block_end: PaddingBlockEnd, } } size_shorthand! { /// A value for the [padding-inline](https://drafts.csswg.org/css-logical/#propdef-padding-inline) shorthand property. pub struct PaddingInline { /// The inline start value. inline_start: PaddingInlineStart, /// The inline end value. inline_end: PaddingInlineEnd, } } size_shorthand! { /// A value for the [scroll-margin-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-block) shorthand property. pub struct ScrollMarginBlock { /// The block start value. block_start: ScrollMarginBlockStart, /// The block end value. block_end: ScrollMarginBlockEnd, } } size_shorthand! { /// A value for the [scroll-margin-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-inline) shorthand property. pub struct ScrollMarginInline { /// The inline start value. inline_start: ScrollMarginInlineStart, /// The inline end value. inline_end: ScrollMarginInlineEnd, } } size_shorthand! { /// A value for the [scroll-padding-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-block) shorthand property. pub struct ScrollPaddingBlock { /// The block start value. block_start: ScrollPaddingBlockStart, /// The block end value. block_end: ScrollPaddingBlockEnd, } } size_shorthand! { /// A value for the [scroll-padding-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-inline) shorthand property. pub struct ScrollPaddingInline { /// The inline start value. inline_start: ScrollPaddingInlineStart, /// The inline end value. inline_end: ScrollPaddingInlineEnd, } } size_shorthand! { /// A value for the [inset-block](https://drafts.csswg.org/css-logical/#propdef-inset-block) shorthand property. pub struct InsetBlock { /// The block start value. block_start: InsetBlockStart, /// The block end value. block_end: InsetBlockEnd, } } size_shorthand! { /// A value for the [inset-inline](https://drafts.csswg.org/css-logical/#propdef-inset-inline) shorthand property. pub struct InsetInline { /// The inline start value. inline_start: InsetInlineStart, /// The inline end value. inline_end: InsetInlineEnd, } } macro_rules! side_handler { ($name: ident, $top: ident, $bottom: ident, $left: ident, $right: ident, $block_start: ident, $block_end: ident, $inline_start: ident, $inline_end: ident, $shorthand: ident, $block_shorthand: ident, $inline_shorthand: ident, $shorthand_category: ident $(, $feature: ident, $shorthand_feature: ident)?) => { #[derive(Debug, Default)] pub(crate) struct $name<'i> { top: Option, bottom: Option, left: Option, right: Option, block_start: Option>, block_end: Option>, inline_start: Option>, inline_end: Option>, has_any: bool, category: PropertyCategory } impl<'i> PropertyHandler<'i> for $name<'i> { fn handle_property(&mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) -> bool { use Property::*; macro_rules! flush { ($key: ident, $val: expr, $category: ident) => {{ // If the category changes betweet logical and physical, // or if the value contains syntax that isn't supported across all targets, // preserve the previous value as a fallback. if PropertyCategory::$category != self.category || (self.$key.is_some() && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets))) { self.flush(dest, context); } }} } macro_rules! property { ($key: ident, $val: ident, $category: ident) => {{ flush!($key, $val, $category); self.$key = Some($val.clone()); self.category = PropertyCategory::$category; self.has_any = true; }}; } macro_rules! logical_property { ($prop: ident, $val: expr) => {{ // Assume unparsed properties might contain unsupported syntax that we must preserve as a fallback. if self.category != PropertyCategory::Logical || (self.$prop.is_some() && matches!($val, Property::Unparsed(_))) { self.flush(dest, context); } self.$prop = Some($val); self.category = PropertyCategory::Logical; self.has_any = true; }}; } match &property { $top(val) => property!(top, val, Physical), $bottom(val) => property!(bottom, val, Physical), $left(val) => property!(left, val, Physical), $right(val) => property!(right, val, Physical), $block_start(val) => { flush!(block_start, val, Logical); logical_property!(block_start, property.clone()); }, $block_end(val) => { flush!(block_end, val, Logical); logical_property!(block_end, property.clone()); }, $inline_start(val) => { flush!(inline_start, val, Logical); logical_property!(inline_start, property.clone()) }, $inline_end(val) => { flush!(inline_end, val, Logical); logical_property!(inline_end, property.clone()); }, $block_shorthand(val) => { flush!(block_start, val.block_start, Logical); flush!(block_end, val.block_end, Logical); logical_property!(block_start, Property::$block_start(val.block_start.clone())); logical_property!(block_end, Property::$block_end(val.block_end.clone())); }, $inline_shorthand(val) => { flush!(inline_start, val.inline_start, Logical); flush!(inline_end, val.inline_end, Logical); logical_property!(inline_start, Property::$inline_start(val.inline_start.clone())); logical_property!(inline_end, Property::$inline_end(val.inline_end.clone())); }, $shorthand(val) => { flush!(top, val.top, $shorthand_category); flush!(right, val.right, $shorthand_category); flush!(bottom, val.bottom, $shorthand_category); flush!(left, val.left, $shorthand_category); self.top = Some(val.top.clone()); self.right = Some(val.right.clone()); self.bottom = Some(val.bottom.clone()); self.left = Some(val.left.clone()); self.block_start = None; self.block_end = None; self.inline_start = None; self.inline_end = None; self.has_any = true; } Unparsed(val) if matches!(val.property_id, PropertyId::$top | PropertyId::$bottom | PropertyId::$left | PropertyId::$right | PropertyId::$block_start | PropertyId::$block_end | PropertyId::$inline_start | PropertyId::$inline_end | PropertyId::$block_shorthand | PropertyId::$inline_shorthand | PropertyId::$shorthand) => { // Even if we weren't able to parse the value (e.g. due to var() references), // we can still add vendor prefixes to the property itself. match &val.property_id { PropertyId::$block_start => logical_property!(block_start, property.clone()), PropertyId::$block_end => logical_property!(block_end, property.clone()), PropertyId::$inline_start => logical_property!(inline_start, property.clone()), PropertyId::$inline_end => logical_property!(inline_end, property.clone()), _ => { self.flush(dest, context); dest.push(property.clone()); } } } _ => return false } true } fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { self.flush(dest, context); } } impl<'i> $name<'i> { fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) { if !self.has_any { return } self.has_any = false; let top = std::mem::take(&mut self.top); let bottom = std::mem::take(&mut self.bottom); let left = std::mem::take(&mut self.left); let right = std::mem::take(&mut self.right); let logical_supported = true $(&& !context.should_compile_logical(Feature::$feature))?; if (PropertyCategory::$shorthand_category != PropertyCategory::Logical || logical_supported) && top.is_some() && bottom.is_some() && left.is_some() && right.is_some() { dest.push(Property::$shorthand($shorthand { top: top.unwrap(), right: right.unwrap(), bottom: bottom.unwrap(), left: left.unwrap() })); } else { if let Some(val) = top { dest.push(Property::$top(val)); } if let Some(val) = bottom { dest.push(Property::$bottom(val)); } if let Some(val) = left { dest.push(Property::$left(val)); } if let Some(val) = right { dest.push(Property::$right(val)); } } let block_start = std::mem::take(&mut self.block_start); let block_end = std::mem::take(&mut self.block_end); let inline_start = std::mem::take(&mut self.inline_start); let inline_end = std::mem::take(&mut self.inline_end); macro_rules! logical_side { ($start: ident, $end: ident, $shorthand_prop: ident, $start_prop: ident, $end_prop: ident) => { let shorthand_supported = logical_supported $(&& !context.should_compile_logical(Feature::$shorthand_feature))?; if let (Some(Property::$start_prop(start)), Some(Property::$end_prop(end)), true) = (&$start, &$end, shorthand_supported) { dest.push(Property::$shorthand_prop($shorthand_prop { $start: start.clone(), $end: end.clone() })); } else { if let Some(val) = $start { dest.push(val); } if let Some(val) = $end { dest.push(val); } } }; } macro_rules! prop { ($val: ident, $logical: ident, $physical: ident) => { match $val { Some(Property::$logical(val)) => { dest.push(Property::$physical(val)); } Some(Property::Unparsed(val)) => { dest.push(Property::Unparsed(val.with_property_id(PropertyId::$physical))); } _ => {} } } } if logical_supported { logical_side!(block_start, block_end, $block_shorthand, $block_start, $block_end); } else { prop!(block_start, $block_start, $top); prop!(block_end, $block_end, $bottom); } if logical_supported { logical_side!(inline_start, inline_end, $inline_shorthand, $inline_start, $inline_end); } else if inline_start.is_some() || inline_end.is_some() { if matches!((&inline_start, &inline_end), (Some(Property::$inline_start(start)), Some(Property::$inline_end(end))) if start == end) { prop!(inline_start, $inline_start, $left); prop!(inline_end, $inline_end, $right); } else { macro_rules! logical_prop { ($val: ident, $logical: ident, $ltr: ident, $rtl: ident) => { match $val { Some(Property::$logical(val)) => { context.add_logical_rule( Property::$ltr(val.clone()), Property::$rtl(val) ); } Some(Property::Unparsed(val)) => { context.add_logical_rule( Property::Unparsed(val.with_property_id(PropertyId::$ltr)), Property::Unparsed(val.with_property_id(PropertyId::$rtl)) ); } _ => {} } } } logical_prop!(inline_start, $inline_start, $left, $right); logical_prop!(inline_end, $inline_end, $right, $left); } } } } }; } side_handler!( MarginHandler, MarginTop, MarginBottom, MarginLeft, MarginRight, MarginBlockStart, MarginBlockEnd, MarginInlineStart, MarginInlineEnd, Margin, MarginBlock, MarginInline, Physical, LogicalMargin, LogicalMarginShorthand ); side_handler!( PaddingHandler, PaddingTop, PaddingBottom, PaddingLeft, PaddingRight, PaddingBlockStart, PaddingBlockEnd, PaddingInlineStart, PaddingInlineEnd, Padding, PaddingBlock, PaddingInline, Physical, LogicalPadding, LogicalPaddingShorthand ); side_handler!( ScrollMarginHandler, ScrollMarginTop, ScrollMarginBottom, ScrollMarginLeft, ScrollMarginRight, ScrollMarginBlockStart, ScrollMarginBlockEnd, ScrollMarginInlineStart, ScrollMarginInlineEnd, ScrollMargin, ScrollMarginBlock, ScrollMarginInline, Physical ); side_handler!( ScrollPaddingHandler, ScrollPaddingTop, ScrollPaddingBottom, ScrollPaddingLeft, ScrollPaddingRight, ScrollPaddingBlockStart, ScrollPaddingBlockEnd, ScrollPaddingInlineStart, ScrollPaddingInlineEnd, ScrollPadding, ScrollPaddingBlock, ScrollPaddingInline, Physical ); side_handler!( InsetHandler, Top, Bottom, Left, Right, InsetBlockStart, InsetBlockEnd, InsetInlineStart, InsetInlineEnd, Inset, InsetBlock, InsetInline, Logical, LogicalInset, LogicalInset );