use cssparser::{match_ignore_ascii_case, CowRcStr, ParseError, Parser, Token};
use palette::{FromColor, Lab, Lch, Oklab, Oklch, Srgb};
use super::color_mix::{self, HueMethod, Space as ColorSpace};
use super::values::{
AlignContent, AlignItems, AlignSelf, AspectRatio, BackgroundAttachment, BackgroundRepeat,
BorderCollapse, BorderStyle, BoxSizing, BreakBetween, BreakInside, CaptionSide, Clear, Color,
ContentPart, Display, EmphasisPosition, EmphasisShape, EmphasisStyle, EmptyCells,
FlexDirection, FlexWrap, Float, FontStyle, FontWeight, GridArea, GridAutoFlow, GridLine,
Hyphens, JustifyContent, ListStylePosition, ListStyleType, ObjectFit, Overflow, OverflowWrap,
Position, QuotePair, RepeatCount, SpecifiedBackgroundPosition, SpecifiedBackgroundSize,
SpecifiedBoxShadow, SpecifiedCalc, SpecifiedCornerRadius, SpecifiedFlexBasis, SpecifiedLength,
SpecifiedLengthPercentage, SpecifiedLengthPercentageOrAuto, SpecifiedLineHeight,
SpecifiedMaxSize, SpecifiedSpacing, SpecifiedTextShadow, SpecifiedTrackBreadth,
SpecifiedTrackComponent, SpecifiedTrackList, SpecifiedTrackSize, SpecifiedTransformFunction,
SpecifiedVerticalAlign, TableLayout, TextAlign, TextDecorationLine, TextOverflow,
TextTransform, Visibility, WhiteSpace, WordBreak, ZIndex,
};
#[derive(Debug, Clone, PartialEq)]
pub enum PropertyDeclaration {
Display(Display),
Width(SpecifiedLengthPercentageOrAuto),
Height(SpecifiedLengthPercentageOrAuto),
MinWidth(SpecifiedLengthPercentage),
MinHeight(SpecifiedLengthPercentage),
MaxWidth(SpecifiedMaxSize),
MaxHeight(SpecifiedMaxSize),
AspectRatio(AspectRatio),
MarginTop(SpecifiedLengthPercentageOrAuto),
MarginRight(SpecifiedLengthPercentageOrAuto),
MarginBottom(SpecifiedLengthPercentageOrAuto),
MarginLeft(SpecifiedLengthPercentageOrAuto),
PaddingTop(SpecifiedLengthPercentage),
PaddingRight(SpecifiedLengthPercentage),
PaddingBottom(SpecifiedLengthPercentage),
PaddingLeft(SpecifiedLengthPercentage),
BorderTopWidth(SpecifiedLength),
BorderRightWidth(SpecifiedLength),
BorderBottomWidth(SpecifiedLength),
BorderLeftWidth(SpecifiedLength),
BorderTopColor(Color),
BorderRightColor(Color),
BorderBottomColor(Color),
BorderLeftColor(Color),
BorderTopStyle(BorderStyle),
BorderRightStyle(BorderStyle),
BorderBottomStyle(BorderStyle),
BorderLeftStyle(BorderStyle),
BorderTopLeftRadius(SpecifiedCornerRadius),
BorderTopRightRadius(SpecifiedCornerRadius),
BorderBottomRightRadius(SpecifiedCornerRadius),
BorderBottomLeftRadius(SpecifiedCornerRadius),
FontSize(SpecifiedLength),
FontFamily(Vec<String>),
FontWeight(FontWeight),
FontStyle(FontStyle),
Color(Color),
BackgroundColor(Color),
BackgroundImage(Option<String>),
BackgroundPosition(SpecifiedBackgroundPosition),
BackgroundSize(SpecifiedBackgroundSize),
BackgroundRepeat(BackgroundRepeat),
BackgroundAttachment(BackgroundAttachment),
TextDecorationLine(TextDecorationLine),
Content(Option<Vec<ContentPart>>),
BreakBefore(BreakBetween),
BreakAfter(BreakBetween),
BreakInside(BreakInside),
Orphans(u32),
Widows(u32),
Float(Float),
Clear(Clear),
Position(Position),
Top(SpecifiedLengthPercentageOrAuto),
Right(SpecifiedLengthPercentageOrAuto),
Bottom(SpecifiedLengthPercentageOrAuto),
Left(SpecifiedLengthPercentageOrAuto),
TextAlign(TextAlign),
LineHeight(SpecifiedLineHeight),
TextIndent(SpecifiedLengthPercentage),
WhiteSpace(WhiteSpace),
LetterSpacing(SpecifiedSpacing),
WordSpacing(SpecifiedSpacing),
TextTransform(TextTransform),
BorderCollapse(BorderCollapse),
BorderSpacing(SpecifiedLength, SpecifiedLength),
CaptionSide(CaptionSide),
TableLayout(TableLayout),
EmptyCells(EmptyCells),
VerticalAlign(SpecifiedVerticalAlign),
ListStyleType(ListStyleType),
ListStylePosition(ListStylePosition),
ListStyleImage(Option<String>),
Overflow(Overflow),
BoxSizing(BoxSizing),
ZIndex(ZIndex),
Visibility(Visibility),
OutlineWidth(SpecifiedLength),
OutlineStyle(BorderStyle),
OutlineColor(Color),
CounterReset(Vec<(String, i32)>),
CounterIncrement(Vec<(String, i32)>),
Quotes(Option<Vec<QuotePair>>),
ObjectFit(ObjectFit),
ObjectPosition(SpecifiedBackgroundPosition),
BoxShadow(Vec<SpecifiedBoxShadow>),
FlexDirection(FlexDirection),
FlexWrap(FlexWrap),
JustifyContent(JustifyContent),
AlignItems(AlignItems),
AlignContent(AlignContent),
AlignSelf(AlignSelf),
FlexGrow(f32),
FlexShrink(f32),
FlexBasis(SpecifiedFlexBasis),
RowGap(SpecifiedLengthPercentage),
ColumnGap(SpecifiedLengthPercentage),
Transform(Vec<SpecifiedTransformFunction>),
TransformOrigin(SpecifiedBackgroundPosition),
Opacity(f32),
TextShadow(Vec<SpecifiedTextShadow>),
TextOverflow(TextOverflow),
WordBreak(WordBreak),
OverflowWrap(OverflowWrap),
Hyphens(Hyphens),
TextEmphasisStyle(EmphasisStyle),
TextEmphasisColor(Color),
TextEmphasisPosition(EmphasisPosition),
GridTemplateColumns(SpecifiedTrackList),
GridTemplateRows(SpecifiedTrackList),
GridAutoColumns(Vec<SpecifiedTrackSize>),
GridAutoRows(Vec<SpecifiedTrackSize>),
GridAutoFlow(GridAutoFlow),
GridTemplateAreas(Vec<GridArea>),
GridRowStart(GridLine),
GridRowEnd(GridLine),
GridColumnStart(GridLine),
GridColumnEnd(GridLine),
JustifyItems(AlignItems),
JustifySelf(AlignSelf),
Custom(super::custom_properties::CustomDeclaration),
Unparsed(super::custom_properties::UnparsedDeclaration),
CssWide(super::css_wide::CssWideDeclaration),
}
pub fn parse_declaration<'i>(
name: &CowRcStr<'i>,
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
match_ignore_ascii_case! { name,
"display" => Ok(vec![D::Display(parse_display(input)?)]),
"width" => Ok(vec![D::Width(parse_length_percentage_or_auto(input)?)]),
"height" => Ok(vec![D::Height(parse_length_percentage_or_auto(input)?)]),
"min-width" => Ok(vec![D::MinWidth(parse_length_percentage(input)?)]),
"min-height" => Ok(vec![D::MinHeight(parse_length_percentage(input)?)]),
"max-width" => Ok(vec![D::MaxWidth(parse_max_size(input)?)]),
"max-height" => Ok(vec![D::MaxHeight(parse_max_size(input)?)]),
"aspect-ratio" => Ok(vec![D::AspectRatio(parse_aspect_ratio(input)?)]),
"margin" => parse_margin_shorthand(input),
"margin-top" => Ok(vec![D::MarginTop(parse_length_percentage_or_auto(input)?)]),
"margin-right" => Ok(vec![D::MarginRight(parse_length_percentage_or_auto(input)?)]),
"margin-bottom" => Ok(vec![D::MarginBottom(parse_length_percentage_or_auto(input)?)]),
"margin-left" => Ok(vec![D::MarginLeft(parse_length_percentage_or_auto(input)?)]),
"padding" => parse_padding_shorthand(input),
"padding-top" => Ok(vec![D::PaddingTop(parse_non_negative_length_percentage(input)?)]),
"padding-right" => Ok(vec![D::PaddingRight(parse_non_negative_length_percentage(input)?)]),
"padding-bottom" => Ok(vec![D::PaddingBottom(parse_non_negative_length_percentage(input)?)]),
"padding-left" => Ok(vec![D::PaddingLeft(parse_non_negative_length_percentage(input)?)]),
"border" => parse_border_shorthand(input),
"border-width" => parse_border_width_shorthand(input),
"border-color" => parse_border_color_shorthand(input),
"border-style" => parse_border_style_shorthand(input),
"border-top" => parse_border_top_shorthand(input),
"border-right" => parse_border_right_shorthand(input),
"border-bottom" => parse_border_bottom_shorthand(input),
"border-left" => parse_border_left_shorthand(input),
"border-top-width" => Ok(vec![D::BorderTopWidth(parse_length(input)?)]),
"border-right-width" => Ok(vec![D::BorderRightWidth(parse_length(input)?)]),
"border-bottom-width" => Ok(vec![D::BorderBottomWidth(parse_length(input)?)]),
"border-left-width" => Ok(vec![D::BorderLeftWidth(parse_length(input)?)]),
"border-top-color" => Ok(vec![D::BorderTopColor(parse_color(input)?)]),
"border-right-color" => Ok(vec![D::BorderRightColor(parse_color(input)?)]),
"border-bottom-color" => Ok(vec![D::BorderBottomColor(parse_color(input)?)]),
"border-left-color" => Ok(vec![D::BorderLeftColor(parse_color(input)?)]),
"border-top-style" => Ok(vec![D::BorderTopStyle(parse_border_style_keyword(input)?)]),
"border-right-style" => Ok(vec![D::BorderRightStyle(parse_border_style_keyword(input)?)]),
"border-bottom-style" => {
Ok(vec![D::BorderBottomStyle(parse_border_style_keyword(input)?)])
},
"border-left-style" => Ok(vec![D::BorderLeftStyle(parse_border_style_keyword(input)?)]),
"border-radius" => parse_border_radius_shorthand(input),
"border-top-left-radius" => Ok(vec![D::BorderTopLeftRadius(parse_corner_radius(input)?)]),
"border-top-right-radius" => Ok(vec![D::BorderTopRightRadius(parse_corner_radius(input)?)]),
"border-bottom-right-radius" => {
Ok(vec![D::BorderBottomRightRadius(parse_corner_radius(input)?)])
},
"border-bottom-left-radius" => {
Ok(vec![D::BorderBottomLeftRadius(parse_corner_radius(input)?)])
},
"font-size" => Ok(vec![D::FontSize(parse_length(input)?)]),
"font-family" => Ok(vec![D::FontFamily(parse_font_family(input)?)]),
"font-weight" => Ok(vec![D::FontWeight(parse_font_weight(input)?)]),
"font-style" => Ok(vec![D::FontStyle(parse_font_style(input)?)]),
"color" => Ok(vec![D::Color(parse_color(input)?)]),
"background-color" => Ok(vec![D::BackgroundColor(parse_color(input)?)]),
"background-image" => Ok(vec![D::BackgroundImage(parse_background_image(input)?)]),
"background-position" => {
Ok(vec![D::BackgroundPosition(parse_background_position(input)?)])
},
"background-size" => Ok(vec![D::BackgroundSize(parse_background_size(input)?)]),
"background-repeat" => Ok(vec![D::BackgroundRepeat(parse_background_repeat(input)?)]),
"background-attachment" => {
Ok(vec![D::BackgroundAttachment(parse_background_attachment(input)?)])
},
"background" => parse_background_shorthand(input),
"text-decoration" | "text-decoration-line" => {
Ok(vec![D::TextDecorationLine(parse_text_decoration_line(input)?)])
},
"content" => Ok(vec![D::Content(parse_content(input)?)]),
"break-before" | "page-break-before" => {
Ok(vec![D::BreakBefore(parse_break_between(input)?)])
},
"break-after" | "page-break-after" => {
Ok(vec![D::BreakAfter(parse_break_between(input)?)])
},
"break-inside" | "page-break-inside" => {
Ok(vec![D::BreakInside(parse_break_inside(input)?)])
},
"orphans" => Ok(vec![D::Orphans(parse_positive_integer(input)?)]),
"widows" => Ok(vec![D::Widows(parse_positive_integer(input)?)]),
"float" => Ok(vec![D::Float(parse_float(input)?)]),
"clear" => Ok(vec![D::Clear(parse_clear(input)?)]),
"position" => Ok(vec![D::Position(parse_position(input)?)]),
"top" => Ok(vec![D::Top(parse_length_percentage_or_auto(input)?)]),
"right" => Ok(vec![D::Right(parse_length_percentage_or_auto(input)?)]),
"bottom" => Ok(vec![D::Bottom(parse_length_percentage_or_auto(input)?)]),
"left" => Ok(vec![D::Left(parse_length_percentage_or_auto(input)?)]),
"text-align" => Ok(vec![D::TextAlign(parse_text_align(input)?)]),
"line-height" => Ok(vec![D::LineHeight(parse_line_height(input)?)]),
"text-indent" => Ok(vec![D::TextIndent(parse_length_percentage(input)?)]),
"white-space" => Ok(vec![D::WhiteSpace(parse_white_space(input)?)]),
"letter-spacing" => Ok(vec![D::LetterSpacing(parse_spacing(input)?)]),
"word-spacing" => Ok(vec![D::WordSpacing(parse_spacing(input)?)]),
"text-transform" => Ok(vec![D::TextTransform(parse_text_transform(input)?)]),
"border-collapse" => Ok(vec![D::BorderCollapse(parse_border_collapse(input)?)]),
"border-spacing" => {
let (h, v) = parse_border_spacing(input)?;
Ok(vec![D::BorderSpacing(h, v)])
},
"caption-side" => Ok(vec![D::CaptionSide(parse_caption_side(input)?)]),
"table-layout" => Ok(vec![D::TableLayout(parse_table_layout(input)?)]),
"empty-cells" => Ok(vec![D::EmptyCells(parse_empty_cells(input)?)]),
"vertical-align" => Ok(vec![D::VerticalAlign(parse_vertical_align(input)?)]),
"list-style-type" => Ok(vec![D::ListStyleType(parse_list_style_type(input)?)]),
"list-style-position" => {
Ok(vec![D::ListStylePosition(parse_list_style_position(input)?)])
},
"list-style-image" => Ok(vec![D::ListStyleImage(parse_list_style_image(input)?)]),
"list-style" => parse_list_style_shorthand(input),
"overflow" => Ok(vec![D::Overflow(parse_overflow(input)?)]),
"box-sizing" => Ok(vec![D::BoxSizing(parse_box_sizing(input)?)]),
"z-index" => Ok(vec![D::ZIndex(parse_z_index(input)?)]),
"visibility" => Ok(vec![D::Visibility(parse_visibility(input)?)]),
"outline-width" => Ok(vec![D::OutlineWidth(parse_length(input)?)]),
"outline-style" => Ok(vec![D::OutlineStyle(parse_border_style_keyword(input)?)]),
"outline-color" => Ok(vec![D::OutlineColor(parse_color(input)?)]),
"outline" => parse_outline_shorthand(input),
"counter-reset" => Ok(vec![D::CounterReset(parse_counter_list(input, 0)?)]),
"counter-increment" => Ok(vec![D::CounterIncrement(parse_counter_list(input, 1)?)]),
"quotes" => Ok(vec![D::Quotes(parse_quotes(input)?)]),
"object-fit" => Ok(vec![D::ObjectFit(parse_object_fit(input)?)]),
"object-position" => Ok(vec![D::ObjectPosition(parse_background_position(input)?)]),
"box-shadow" => Ok(vec![D::BoxShadow(parse_box_shadow(input)?)]),
"flex-direction" => Ok(vec![D::FlexDirection(parse_flex_direction(input)?)]),
"flex-wrap" => Ok(vec![D::FlexWrap(parse_flex_wrap(input)?)]),
"justify-content" => Ok(vec![D::JustifyContent(parse_justify_content(input)?)]),
"align-items" => Ok(vec![D::AlignItems(parse_align_items(input)?)]),
"align-content" => Ok(vec![D::AlignContent(parse_align_content(input)?)]),
"align-self" => Ok(vec![D::AlignSelf(parse_align_self(input)?)]),
"flex-grow" => Ok(vec![D::FlexGrow(parse_non_negative_number(input)?)]),
"flex-shrink" => Ok(vec![D::FlexShrink(parse_non_negative_number(input)?)]),
"flex-basis" => Ok(vec![D::FlexBasis(parse_flex_basis(input)?)]),
"flex" => parse_flex_shorthand(input),
"row-gap" => Ok(vec![D::RowGap(parse_length_percentage(input)?)]),
"column-gap" => Ok(vec![D::ColumnGap(parse_length_percentage(input)?)]),
"gap" => parse_gap_shorthand(input),
"transform" => Ok(vec![D::Transform(parse_transform(input)?)]),
"transform-origin" => Ok(vec![D::TransformOrigin(parse_background_position(input)?)]),
"opacity" => Ok(vec![D::Opacity(parse_opacity(input)?)]),
"text-shadow" => Ok(vec![D::TextShadow(parse_text_shadow(input)?)]),
"text-overflow" => Ok(vec![D::TextOverflow(parse_text_overflow(input)?)]),
"word-break" => Ok(vec![D::WordBreak(parse_word_break(input)?)]),
"overflow-wrap" | "word-wrap" => {
Ok(vec![D::OverflowWrap(parse_overflow_wrap(input)?)])
},
"hyphens" => Ok(vec![D::Hyphens(parse_hyphens(input)?)]),
"text-emphasis-style" => {
Ok(vec![D::TextEmphasisStyle(parse_text_emphasis_style(input)?)])
},
"text-emphasis-color" => Ok(vec![D::TextEmphasisColor(parse_color(input)?)]),
"text-emphasis-position" => {
Ok(vec![D::TextEmphasisPosition(parse_text_emphasis_position(input)?)])
},
"text-emphasis" => parse_text_emphasis_shorthand(input),
"grid-template-columns" => {
Ok(vec![D::GridTemplateColumns(parse_track_list(input)?)])
},
"grid-template-rows" => Ok(vec![D::GridTemplateRows(parse_track_list(input)?)]),
"grid-auto-columns" => Ok(vec![D::GridAutoColumns(parse_auto_track_list(input)?)]),
"grid-auto-rows" => Ok(vec![D::GridAutoRows(parse_auto_track_list(input)?)]),
"grid-auto-flow" => Ok(vec![D::GridAutoFlow(parse_grid_auto_flow(input)?)]),
"grid-template-areas" => {
Ok(vec![D::GridTemplateAreas(parse_grid_template_areas(input)?)])
},
"grid-row-start" => Ok(vec![D::GridRowStart(parse_grid_line(input)?)]),
"grid-row-end" => Ok(vec![D::GridRowEnd(parse_grid_line(input)?)]),
"grid-column-start" => Ok(vec![D::GridColumnStart(parse_grid_line(input)?)]),
"grid-column-end" => Ok(vec![D::GridColumnEnd(parse_grid_line(input)?)]),
"grid-row" => parse_grid_row_shorthand(input),
"grid-column" => parse_grid_column_shorthand(input),
"grid-area" => parse_grid_area_shorthand(input),
"justify-items" => Ok(vec![D::JustifyItems(parse_align_items(input)?)]),
"justify-self" => Ok(vec![D::JustifySelf(parse_align_self(input)?)]),
"margin-inline-start" => Ok(vec![D::MarginLeft(parse_length_percentage_or_auto(input)?)]),
"margin-inline-end" => Ok(vec![D::MarginRight(parse_length_percentage_or_auto(input)?)]),
"margin-block-start" => Ok(vec![D::MarginTop(parse_length_percentage_or_auto(input)?)]),
"margin-block-end" => Ok(vec![D::MarginBottom(parse_length_percentage_or_auto(input)?)]),
"margin-inline" => {
parse_start_end(input, parse_length_percentage_or_auto, D::MarginLeft, D::MarginRight)
},
"margin-block" => {
parse_start_end(input, parse_length_percentage_or_auto, D::MarginTop, D::MarginBottom)
},
"padding-inline-start" => Ok(vec![D::PaddingLeft(parse_non_negative_length_percentage(input)?)]),
"padding-inline-end" => Ok(vec![D::PaddingRight(parse_non_negative_length_percentage(input)?)]),
"padding-block-start" => Ok(vec![D::PaddingTop(parse_non_negative_length_percentage(input)?)]),
"padding-block-end" => Ok(vec![D::PaddingBottom(parse_non_negative_length_percentage(input)?)]),
"padding-inline" => {
parse_start_end(
input,
parse_non_negative_length_percentage,
D::PaddingLeft,
D::PaddingRight,
)
},
"padding-block" => {
parse_start_end(
input,
parse_non_negative_length_percentage,
D::PaddingTop,
D::PaddingBottom,
)
},
"inset" => parse_inset_shorthand(input),
"inset-inline-start" => Ok(vec![D::Left(parse_length_percentage_or_auto(input)?)]),
"inset-inline-end" => Ok(vec![D::Right(parse_length_percentage_or_auto(input)?)]),
"inset-block-start" => Ok(vec![D::Top(parse_length_percentage_or_auto(input)?)]),
"inset-block-end" => Ok(vec![D::Bottom(parse_length_percentage_or_auto(input)?)]),
"inset-inline" => {
parse_start_end(input, parse_length_percentage_or_auto, D::Left, D::Right)
},
"inset-block" => {
parse_start_end(input, parse_length_percentage_or_auto, D::Top, D::Bottom)
},
"border-inline-start" => parse_border_left_shorthand(input),
"border-inline-end" => parse_border_right_shorthand(input),
"border-block-start" => parse_border_top_shorthand(input),
"border-block-end" => parse_border_bottom_shorthand(input),
"border-inline" => {
let mut decls = parse_border_left_shorthand(input)?;
decls.extend(mirror_border_side(&decls, Side::Right));
Ok(decls)
},
"border-block" => {
let mut decls = parse_border_top_shorthand(input)?;
decls.extend(mirror_border_side(&decls, Side::Bottom));
Ok(decls)
},
"border-inline-start-width" => Ok(vec![D::BorderLeftWidth(parse_length(input)?)]),
"border-inline-end-width" => Ok(vec![D::BorderRightWidth(parse_length(input)?)]),
"border-block-start-width" => Ok(vec![D::BorderTopWidth(parse_length(input)?)]),
"border-block-end-width" => Ok(vec![D::BorderBottomWidth(parse_length(input)?)]),
"border-inline-width" => {
parse_start_end(input, parse_length, D::BorderLeftWidth, D::BorderRightWidth)
},
"border-block-width" => {
parse_start_end(input, parse_length, D::BorderTopWidth, D::BorderBottomWidth)
},
"border-inline-start-style" => {
Ok(vec![D::BorderLeftStyle(parse_border_style_keyword(input)?)])
},
"border-inline-end-style" => {
Ok(vec![D::BorderRightStyle(parse_border_style_keyword(input)?)])
},
"border-block-start-style" => {
Ok(vec![D::BorderTopStyle(parse_border_style_keyword(input)?)])
},
"border-block-end-style" => {
Ok(vec![D::BorderBottomStyle(parse_border_style_keyword(input)?)])
},
"border-inline-style" => {
parse_start_end(input, parse_border_style_keyword, D::BorderLeftStyle, D::BorderRightStyle)
},
"border-block-style" => {
parse_start_end(input, parse_border_style_keyword, D::BorderTopStyle, D::BorderBottomStyle)
},
"border-inline-start-color" => Ok(vec![D::BorderLeftColor(parse_color(input)?)]),
"border-inline-end-color" => Ok(vec![D::BorderRightColor(parse_color(input)?)]),
"border-block-start-color" => Ok(vec![D::BorderTopColor(parse_color(input)?)]),
"border-block-end-color" => Ok(vec![D::BorderBottomColor(parse_color(input)?)]),
"border-inline-color" => {
parse_start_end(input, parse_color, D::BorderLeftColor, D::BorderRightColor)
},
"border-block-color" => {
parse_start_end(input, parse_color, D::BorderTopColor, D::BorderBottomColor)
},
"border-start-start-radius" => {
Ok(vec![D::BorderTopLeftRadius(parse_corner_radius(input)?)])
},
"border-start-end-radius" => {
Ok(vec![D::BorderTopRightRadius(parse_corner_radius(input)?)])
},
"border-end-start-radius" => {
Ok(vec![D::BorderBottomLeftRadius(parse_corner_radius(input)?)])
},
"border-end-end-radius" => {
Ok(vec![D::BorderBottomRightRadius(parse_corner_radius(input)?)])
},
_ => Err(input.new_custom_error(())),
}
}
fn parse_margin_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (top, right, bottom, left) = parse_four_sides(input, parse_length_percentage_or_auto)?;
Ok(vec![
D::MarginTop(top),
D::MarginRight(right),
D::MarginBottom(bottom),
D::MarginLeft(left),
])
}
fn parse_padding_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (top, right, bottom, left) = parse_four_sides(input, parse_non_negative_length_percentage)?;
Ok(vec![
D::PaddingTop(top),
D::PaddingRight(right),
D::PaddingBottom(bottom),
D::PaddingLeft(left),
])
}
fn parse_inset_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (top, right, bottom, left) = parse_four_sides(input, parse_length_percentage_or_auto)?;
Ok(vec![
D::Top(top),
D::Right(right),
D::Bottom(bottom),
D::Left(left),
])
}
fn parse_start_end<'i, T: Copy>(
input: &mut Parser<'i, '_>,
mut parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i, ()>>,
start: fn(T) -> PropertyDeclaration,
end: fn(T) -> PropertyDeclaration,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
let first = parse_one(input)?;
let second = input.try_parse(&mut parse_one).unwrap_or(first);
Ok(vec![start(first), end(second)])
}
#[derive(Clone, Copy)]
enum Side {
Right,
Bottom,
}
fn mirror_border_side(decls: &[PropertyDeclaration], to: Side) -> Vec<PropertyDeclaration> {
use PropertyDeclaration as D;
decls
.iter()
.filter_map(|d| {
Some(match (d, to) {
(D::BorderLeftWidth(w), Side::Right) => D::BorderRightWidth(*w),
(D::BorderLeftStyle(s), Side::Right) => D::BorderRightStyle(*s),
(D::BorderLeftColor(c), Side::Right) => D::BorderRightColor(*c),
(D::BorderTopWidth(w), Side::Bottom) => D::BorderBottomWidth(*w),
(D::BorderTopStyle(s), Side::Bottom) => D::BorderBottomStyle(*s),
(D::BorderTopColor(c), Side::Bottom) => D::BorderBottomColor(*c),
_ => return None,
})
})
.collect()
}
fn parse_four_sides<'i, T: Copy>(
input: &mut Parser<'i, '_>,
mut parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i, ()>>,
) -> Result<(T, T, T, T), ParseError<'i, ()>> {
let top = parse_one(input)?;
let Ok(right) = input.try_parse(&mut parse_one) else {
return Ok((top, top, top, top));
};
let Ok(bottom) = input.try_parse(&mut parse_one) else {
return Ok((top, right, top, right));
};
let Ok(left) = input.try_parse(&mut parse_one) else {
return Ok((top, right, bottom, right));
};
Ok((top, right, bottom, left))
}
fn parse_border_edge_values<'i>(
input: &mut Parser<'i, '_>,
) -> Result<(SpecifiedLength, BorderStyle, Option<Color>), ParseError<'i, ()>> {
let mut width = SpecifiedLength::Px(0.0);
let mut style = BorderStyle::None;
let mut color = None;
loop {
if let Ok(w) = input.try_parse(parse_length) {
width = w;
continue;
}
if let Ok(s) = input.try_parse(parse_border_style_keyword) {
style = s;
continue;
}
if let Ok(c) = input.try_parse(parse_color) {
color = Some(c);
continue;
}
break;
}
Ok((width, style, color))
}
fn parse_border_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (width, style, color) = parse_border_edge_values(input)?;
let mut decls = vec![
D::BorderTopWidth(width),
D::BorderRightWidth(width),
D::BorderBottomWidth(width),
D::BorderLeftWidth(width),
D::BorderTopStyle(style),
D::BorderRightStyle(style),
D::BorderBottomStyle(style),
D::BorderLeftStyle(style),
];
if let Some(c) = color {
decls.extend([
D::BorderTopColor(c),
D::BorderRightColor(c),
D::BorderBottomColor(c),
D::BorderLeftColor(c),
]);
}
Ok(decls)
}
fn parse_border_top_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (width, style, color) = parse_border_edge_values(input)?;
let mut decls = vec![D::BorderTopWidth(width), D::BorderTopStyle(style)];
if let Some(c) = color {
decls.push(D::BorderTopColor(c));
}
Ok(decls)
}
fn parse_border_right_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (width, style, color) = parse_border_edge_values(input)?;
let mut decls = vec![D::BorderRightWidth(width), D::BorderRightStyle(style)];
if let Some(c) = color {
decls.push(D::BorderRightColor(c));
}
Ok(decls)
}
fn parse_border_bottom_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (width, style, color) = parse_border_edge_values(input)?;
let mut decls = vec![D::BorderBottomWidth(width), D::BorderBottomStyle(style)];
if let Some(c) = color {
decls.push(D::BorderBottomColor(c));
}
Ok(decls)
}
fn parse_border_left_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (width, style, color) = parse_border_edge_values(input)?;
let mut decls = vec![D::BorderLeftWidth(width), D::BorderLeftStyle(style)];
if let Some(c) = color {
decls.push(D::BorderLeftColor(c));
}
Ok(decls)
}
fn parse_border_width_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (top, right, bottom, left) = parse_four_sides(input, parse_length)?;
Ok(vec![
D::BorderTopWidth(top),
D::BorderRightWidth(right),
D::BorderBottomWidth(bottom),
D::BorderLeftWidth(left),
])
}
fn parse_border_color_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (top, right, bottom, left) = parse_four_sides(input, parse_color)?;
Ok(vec![
D::BorderTopColor(top),
D::BorderRightColor(right),
D::BorderBottomColor(bottom),
D::BorderLeftColor(left),
])
}
fn parse_border_style_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (top, right, bottom, left) = parse_four_sides(input, parse_border_style_keyword)?;
Ok(vec![
D::BorderTopStyle(top),
D::BorderRightStyle(right),
D::BorderBottomStyle(bottom),
D::BorderLeftStyle(left),
])
}
fn parse_border_radius_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (h_tl, h_tr, h_br, h_bl) = parse_four_sides(input, parse_length)?;
let vertical = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
Some(parse_four_sides(input, parse_length)?)
} else {
None
};
let (v_tl, v_tr, v_br, v_bl) = vertical.unwrap_or((h_tl, h_tr, h_br, h_bl));
Ok(vec![
D::BorderTopLeftRadius(SpecifiedCornerRadius {
horizontal: h_tl,
vertical: v_tl,
}),
D::BorderTopRightRadius(SpecifiedCornerRadius {
horizontal: h_tr,
vertical: v_tr,
}),
D::BorderBottomRightRadius(SpecifiedCornerRadius {
horizontal: h_br,
vertical: v_br,
}),
D::BorderBottomLeftRadius(SpecifiedCornerRadius {
horizontal: h_bl,
vertical: v_bl,
}),
])
}
fn parse_corner_radius<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedCornerRadius, ParseError<'i, ()>> {
let horizontal = parse_length(input)?;
let vertical = input.try_parse(parse_length).unwrap_or(horizontal);
Ok(SpecifiedCornerRadius {
horizontal,
vertical,
})
}
fn parse_border_style_keyword<'i>(
input: &mut Parser<'i, '_>,
) -> Result<BorderStyle, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"none" | "hidden" => BorderStyle::None,
"solid" => BorderStyle::Solid,
"dashed" => BorderStyle::Dashed,
"dotted" => BorderStyle::Dotted,
"double" => BorderStyle::Double,
"groove" => BorderStyle::Groove,
"ridge" => BorderStyle::Ridge,
"inset" => BorderStyle::Inset,
"outset" => BorderStyle::Outset,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_overflow<'i>(input: &mut Parser<'i, '_>) -> Result<Overflow, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"visible" => Overflow::Visible,
"hidden" => Overflow::Hidden,
"scroll" => Overflow::Scroll,
"auto" => Overflow::Auto,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_box_sizing<'i>(input: &mut Parser<'i, '_>) -> Result<BoxSizing, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"content-box" => BoxSizing::ContentBox,
"border-box" => BoxSizing::BorderBox,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_z_index<'i>(input: &mut Parser<'i, '_>) -> Result<ZIndex, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("auto"))
.is_ok()
{
return Ok(ZIndex::Auto);
}
Ok(ZIndex::Value(input.expect_integer()?))
}
fn parse_visibility<'i>(input: &mut Parser<'i, '_>) -> Result<Visibility, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"visible" => Visibility::Visible,
"hidden" => Visibility::Hidden,
"collapse" => Visibility::Collapse,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_outline_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let mut width = SpecifiedLength::Px(0.0);
let mut style = BorderStyle::None;
let mut color = None;
loop {
if let Ok(w) = input.try_parse(parse_length) {
width = w;
continue;
}
if let Ok(s) = input.try_parse(parse_border_style_keyword) {
style = s;
continue;
}
if let Ok(c) = input.try_parse(parse_color) {
color = Some(c);
continue;
}
break;
}
let mut decls = vec![D::OutlineWidth(width), D::OutlineStyle(style)];
if let Some(c) = color {
decls.push(D::OutlineColor(c));
}
Ok(decls)
}
fn parse_text_align<'i>(input: &mut Parser<'i, '_>) -> Result<TextAlign, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"left" => TextAlign::Left,
"right" => TextAlign::Right,
"center" => TextAlign::Center,
"justify" => TextAlign::Justify,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_white_space<'i>(input: &mut Parser<'i, '_>) -> Result<WhiteSpace, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"normal" => WhiteSpace::Normal,
"nowrap" => WhiteSpace::Nowrap,
"pre" => WhiteSpace::Pre,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_text_transform<'i>(
input: &mut Parser<'i, '_>,
) -> Result<TextTransform, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"none" => TextTransform::None,
"uppercase" => TextTransform::Uppercase,
"lowercase" => TextTransform::Lowercase,
"capitalize" => TextTransform::Capitalize,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_line_height<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedLineHeight, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("normal"))
.is_ok()
{
return Ok(SpecifiedLineHeight::Normal);
}
let token = input.next()?.clone();
match token {
Token::Number { value, .. } => Ok(SpecifiedLineHeight::Number(value)),
Token::Percentage { unit_value, .. } => Ok(SpecifiedLineHeight::Percentage(unit_value)),
Token::Dimension {
value, ref unit, ..
} => Ok(SpecifiedLineHeight::Length(parse_length_unit(
input, value, unit,
)?)),
_ => Err(input.new_custom_error(())),
}
}
fn parse_spacing<'i>(input: &mut Parser<'i, '_>) -> Result<SpecifiedSpacing, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("normal"))
.is_ok()
{
return Ok(SpecifiedSpacing::Normal);
}
Ok(SpecifiedSpacing::Length(parse_length(input)?))
}
fn parse_border_collapse<'i>(
input: &mut Parser<'i, '_>,
) -> Result<BorderCollapse, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"separate" => BorderCollapse::Separate,
"collapse" => BorderCollapse::Collapse,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_border_spacing<'i>(
input: &mut Parser<'i, '_>,
) -> Result<(SpecifiedLength, SpecifiedLength), ParseError<'i, ()>> {
let horizontal = parse_length(input)?;
let vertical = input.try_parse(parse_length).unwrap_or(horizontal);
Ok((horizontal, vertical))
}
fn parse_caption_side<'i>(input: &mut Parser<'i, '_>) -> Result<CaptionSide, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"top" => CaptionSide::Top,
"bottom" => CaptionSide::Bottom,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_table_layout<'i>(input: &mut Parser<'i, '_>) -> Result<TableLayout, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"auto" => TableLayout::Auto,
"fixed" => TableLayout::Fixed,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_empty_cells<'i>(input: &mut Parser<'i, '_>) -> Result<EmptyCells, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"show" => EmptyCells::Show,
"hide" => EmptyCells::Hide,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_vertical_align<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedVerticalAlign, ParseError<'i, ()>> {
if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
return Ok(match_ignore_ascii_case! { &ident,
"top" => SpecifiedVerticalAlign::Top,
"middle" => SpecifiedVerticalAlign::Middle,
"bottom" => SpecifiedVerticalAlign::Bottom,
"baseline" => SpecifiedVerticalAlign::Baseline,
"sub" => SpecifiedVerticalAlign::Sub,
"super" => SpecifiedVerticalAlign::Super,
"text-top" => SpecifiedVerticalAlign::TextTop,
"text-bottom" => SpecifiedVerticalAlign::TextBottom,
_ => return Err(input.new_custom_error(())),
});
}
Ok(SpecifiedVerticalAlign::LengthPercentage(
parse_length_percentage(input)?,
))
}
fn parse_display<'i>(input: &mut Parser<'i, '_>) -> Result<Display, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"block" => Display::Block,
"inline" => Display::Inline,
"inline-block" => Display::InlineBlock,
"table" => Display::Table,
"table-row" => Display::TableRow,
"table-cell" => Display::TableCell,
"table-caption" => Display::TableCaption,
"list-item" => Display::ListItem,
"flex" => Display::Flex,
"inline-flex" => Display::InlineFlex,
"grid" => Display::Grid,
"none" => Display::None,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_list_style_type<'i>(
input: &mut Parser<'i, '_>,
) -> Result<ListStyleType, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"disc" => ListStyleType::Disc,
"circle" => ListStyleType::Circle,
"square" => ListStyleType::Square,
"decimal" => ListStyleType::Decimal,
"decimal-leading-zero" => ListStyleType::DecimalLeadingZero,
"lower-roman" => ListStyleType::LowerRoman,
"upper-roman" => ListStyleType::UpperRoman,
"lower-alpha" | "lower-latin" => ListStyleType::LowerAlpha,
"upper-alpha" | "upper-latin" => ListStyleType::UpperAlpha,
"none" => ListStyleType::None,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_list_style_position<'i>(
input: &mut Parser<'i, '_>,
) -> Result<ListStylePosition, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"outside" => ListStylePosition::Outside,
"inside" => ListStylePosition::Inside,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_list_style_image<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Option<String>, ParseError<'i, ()>> {
parse_background_image(input)
}
fn parse_list_style_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let mut ty = None;
let mut position = None;
let mut image = None;
loop {
if position.is_none() {
if let Ok(p) = input.try_parse(parse_list_style_position) {
position = Some(p);
continue;
}
}
if ty.is_none() {
if let Ok(t) = input.try_parse(parse_list_style_type) {
ty = Some(t);
continue;
}
}
if image.is_none() {
if let Ok(img) = input.try_parse(parse_list_style_image) {
image = Some(img);
continue;
}
}
break;
}
let mut decls = Vec::new();
if let Some(p) = position {
decls.push(D::ListStylePosition(p));
}
decls.push(D::ListStyleType(ty.unwrap_or_default()));
decls.push(D::ListStyleImage(image.unwrap_or(None)));
Ok(decls)
}
fn parse_float<'i>(input: &mut Parser<'i, '_>) -> Result<Float, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"none" => Float::None,
"left" => Float::Left,
"right" => Float::Right,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_clear<'i>(input: &mut Parser<'i, '_>) -> Result<Clear, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"none" => Clear::None,
"left" => Clear::Left,
"right" => Clear::Right,
"both" => Clear::Both,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_position<'i>(input: &mut Parser<'i, '_>) -> Result<Position, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"static" => Position::Static,
"relative" => Position::Relative,
"absolute" => Position::Absolute,
"fixed" => Position::Fixed,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_break_between<'i>(input: &mut Parser<'i, '_>) -> Result<BreakBetween, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"auto" => BreakBetween::Auto,
"avoid" | "avoid-page" | "avoid-column" => BreakBetween::Avoid,
"always" | "page" => BreakBetween::Always,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_break_inside<'i>(input: &mut Parser<'i, '_>) -> Result<BreakInside, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"auto" => BreakInside::Auto,
"avoid" | "avoid-page" | "avoid-column" => BreakInside::Avoid,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_positive_integer<'i>(input: &mut Parser<'i, '_>) -> Result<u32, ParseError<'i, ()>> {
let value = input.expect_integer()?;
if value < 1 {
return Err(input.new_custom_error(()));
}
Ok(value as u32)
}
pub(crate) fn parse_font_weight<'i>(
input: &mut Parser<'i, '_>,
) -> Result<FontWeight, ParseError<'i, ()>> {
if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
return Ok(match_ignore_ascii_case! { &ident,
"normal" => FontWeight::Normal,
"bold" => FontWeight::Bold,
_ => return Err(input.new_custom_error(())),
});
}
let value = input.expect_number()?;
Ok(if value >= 600.0 {
FontWeight::Bold
} else {
FontWeight::Normal
})
}
pub(crate) fn parse_font_style<'i>(
input: &mut Parser<'i, '_>,
) -> Result<FontStyle, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"normal" => FontStyle::Normal,
"italic" | "oblique" => FontStyle::Italic,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_text_decoration_line<'i>(
input: &mut Parser<'i, '_>,
) -> Result<TextDecorationLine, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(TextDecorationLine::default());
}
let mut line = TextDecorationLine::default();
loop {
let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) else {
break;
};
match_ignore_ascii_case! { &ident,
"underline" => line.underline = true,
"line-through" => line.line_through = true,
_ => return Err(input.new_custom_error(())),
}
}
Ok(line)
}
fn parse_non_negative_length_percentage<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedLengthPercentage, ParseError<'i, ()>> {
let value = parse_length_percentage(input)?;
if value.is_negative() {
return Err(input.new_custom_error(()));
}
Ok(value)
}
fn parse_length_percentage<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedLengthPercentage, ParseError<'i, ()>> {
if let Ok(calc) = input.try_parse(parse_calc) {
return Ok(SpecifiedLengthPercentage::Calc(calc));
}
let token = input.next()?.clone();
match token {
Token::Percentage { unit_value, .. } => {
Ok(SpecifiedLengthPercentage::Percentage(unit_value))
}
Token::Number { value: 0.0, .. } => {
Ok(SpecifiedLengthPercentage::Length(SpecifiedLength::Px(0.0)))
}
Token::Dimension {
value, ref unit, ..
} => Ok(SpecifiedLengthPercentage::Length(parse_length_unit(
input, value, unit,
)?)),
_ => Err(input.new_custom_error(())),
}
}
#[derive(Debug, Clone, Copy, Default)]
struct CalcValue {
px: f32,
em: f32,
rem: f32,
percent: f32,
number: f32,
}
impl CalcValue {
fn number(n: f32) -> Self {
Self {
number: n,
..Default::default()
}
}
fn is_pure_number(&self) -> bool {
self.px == 0.0 && self.em == 0.0 && self.rem == 0.0 && self.percent == 0.0
}
fn add(self, other: Self) -> Self {
Self {
px: self.px + other.px,
em: self.em + other.em,
rem: self.rem + other.rem,
percent: self.percent + other.percent,
number: self.number + other.number,
}
}
fn scale(self, factor: f32) -> Self {
Self {
px: self.px * factor,
em: self.em * factor,
rem: self.rem * factor,
percent: self.percent * factor,
number: self.number * factor,
}
}
}
const MAX_CALC_DEPTH: u32 = 32;
fn parse_calc<'i>(input: &mut Parser<'i, '_>) -> Result<SpecifiedCalc, ParseError<'i, ()>> {
input.expect_function_matching("calc")?;
let value = input.parse_nested_block(|input| parse_calc_sum(input, 1))?;
if value.number != 0.0 {
return Err(input.new_custom_error(()));
}
Ok(SpecifiedCalc {
px: value.px,
em: value.em,
rem: value.rem,
percent: value.percent,
})
}
fn parse_calc_sum<'i>(
input: &mut Parser<'i, '_>,
depth: u32,
) -> Result<CalcValue, ParseError<'i, ()>> {
let mut acc = parse_calc_product(input, depth)?;
loop {
let sign = input.try_parse(|input| {
let token = input.next()?.clone();
match token {
Token::Delim('+') => Ok(1.0),
Token::Delim('-') => Ok(-1.0),
_ => Err(input.new_custom_error::<(), ()>(())),
}
});
match sign {
Ok(sign) => {
let rhs = parse_calc_product(input, depth)?;
acc = acc.add(rhs.scale(sign));
}
Err(_) => return Ok(acc),
}
}
}
fn parse_calc_product<'i>(
input: &mut Parser<'i, '_>,
depth: u32,
) -> Result<CalcValue, ParseError<'i, ()>> {
let mut acc = parse_calc_value(input, depth)?;
loop {
enum Op {
Mul,
Div,
}
let op = input.try_parse(|input| {
let token = input.next()?.clone();
match token {
Token::Delim('*') => Ok(Op::Mul),
Token::Delim('/') => Ok(Op::Div),
_ => Err(input.new_custom_error::<(), ()>(())),
}
});
match op {
Ok(Op::Mul) => {
let rhs = parse_calc_value(input, depth)?;
if acc.is_pure_number() {
acc = rhs.scale(acc.number);
} else if rhs.is_pure_number() {
acc = acc.scale(rhs.number);
} else {
return Err(input.new_custom_error(()));
}
}
Ok(Op::Div) => {
let rhs = parse_calc_value(input, depth)?;
if !rhs.is_pure_number() || rhs.number == 0.0 {
return Err(input.new_custom_error(()));
}
acc = acc.scale(1.0 / rhs.number);
}
Err(_) => return Ok(acc),
}
}
}
fn parse_calc_value<'i>(
input: &mut Parser<'i, '_>,
depth: u32,
) -> Result<CalcValue, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_parenthesis_block())
.is_ok()
|| input
.try_parse(|input| input.expect_function_matching("calc"))
.is_ok()
{
if depth >= MAX_CALC_DEPTH {
return Err(input.new_custom_error(()));
}
return input.parse_nested_block(|input| parse_calc_sum(input, depth + 1));
}
let token = input.next()?.clone();
match token {
Token::Number { value, .. } => Ok(CalcValue::number(value)),
Token::Percentage { unit_value, .. } => Ok(CalcValue {
percent: unit_value,
..Default::default()
}),
Token::Dimension {
value, ref unit, ..
} => {
let mut v = CalcValue::default();
if let Some(px_per_unit) = absolute_length_px(unit) {
v.px = value * px_per_unit;
} else if unit.eq_ignore_ascii_case("em") {
v.em = value;
} else if unit.eq_ignore_ascii_case("rem") {
v.rem = value;
} else {
return Err(input.new_custom_error(()));
}
Ok(v)
}
_ => Err(input.new_custom_error(())),
}
}
fn parse_aspect_ratio<'i>(input: &mut Parser<'i, '_>) -> Result<AspectRatio, ParseError<'i, ()>> {
let mut auto = false;
let mut ratio = None;
loop {
if !auto
&& input
.try_parse(|input| input.expect_ident_matching("auto"))
.is_ok()
{
auto = true;
continue;
}
if ratio.is_none() {
if let Ok(r) = input.try_parse(parse_ratio) {
ratio = Some(r);
continue;
}
}
break;
}
if !auto && ratio.is_none() {
return Err(input.new_custom_error(()));
}
Ok(AspectRatio { auto, ratio })
}
fn parse_ratio<'i>(input: &mut Parser<'i, '_>) -> Result<f32, ParseError<'i, ()>> {
let width = input.expect_number()?;
let height = input
.try_parse(|input| {
input.expect_delim('/')?;
input.expect_number()
})
.unwrap_or(1.0);
if width <= 0.0 || height <= 0.0 {
return Err(input.new_custom_error(()));
}
Ok(width / height)
}
fn parse_max_size<'i>(input: &mut Parser<'i, '_>) -> Result<SpecifiedMaxSize, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(SpecifiedMaxSize::None);
}
Ok(SpecifiedMaxSize::LengthPercentage(parse_length_percentage(
input,
)?))
}
fn parse_length_percentage_or_auto<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedLengthPercentageOrAuto, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("auto"))
.is_ok()
{
return Ok(SpecifiedLengthPercentageOrAuto::Auto);
}
parse_length_percentage(input).map(SpecifiedLengthPercentageOrAuto::LengthPercentage)
}
pub(crate) fn parse_length<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedLength, ParseError<'i, ()>> {
let token = input.next()?.clone();
match token {
Token::Number { value: 0.0, .. } => Ok(SpecifiedLength::Px(0.0)),
Token::Dimension {
value, ref unit, ..
} => parse_length_unit(input, value, unit),
_ => Err(input.new_custom_error(())),
}
}
fn absolute_length_px(unit: &str) -> Option<f32> {
const PX_PER_IN: f32 = 96.0;
if unit.eq_ignore_ascii_case("px") {
Some(1.0)
} else if unit.eq_ignore_ascii_case("in") {
Some(PX_PER_IN)
} else if unit.eq_ignore_ascii_case("cm") {
Some(PX_PER_IN / 2.54)
} else if unit.eq_ignore_ascii_case("mm") {
Some(PX_PER_IN / 25.4)
} else if unit.eq_ignore_ascii_case("q") {
Some(PX_PER_IN / 25.4 / 4.0)
} else if unit.eq_ignore_ascii_case("pt") {
Some(PX_PER_IN / 72.0)
} else if unit.eq_ignore_ascii_case("pc") {
Some(PX_PER_IN / 6.0)
} else {
None
}
}
fn parse_length_unit<'i>(
input: &Parser<'i, '_>,
value: f32,
unit: &str,
) -> Result<SpecifiedLength, ParseError<'i, ()>> {
if let Some(px_per_unit) = absolute_length_px(unit) {
Ok(SpecifiedLength::Px(value * px_per_unit))
} else if unit.eq_ignore_ascii_case("em") {
Ok(SpecifiedLength::Em(value))
} else if unit.eq_ignore_ascii_case("rem") {
Ok(SpecifiedLength::Rem(value))
} else {
Err(input.new_custom_error(()))
}
}
const MAX_COLOR_MIX_DEPTH: u32 = 16;
fn parse_color<'i>(input: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i, ()>> {
parse_color_at_depth(input, 0)
}
fn parse_color_at_depth<'i>(
input: &mut Parser<'i, '_>,
depth: u32,
) -> Result<Color, ParseError<'i, ()>> {
if let Ok(mixed) = input.try_parse(|input| parse_color_mix(input, depth)) {
return Ok(mixed);
}
let color = cssparser_color::Color::parse(input).map_err(|_| input.new_custom_error(()))?;
match color {
cssparser_color::Color::CurrentColor => Ok(Color::CurrentColor),
cssparser_color::Color::Rgba(rgba) => Ok(Color::Rgba {
red: rgba.red,
green: rgba.green,
blue: rgba.blue,
alpha: rgba.alpha,
}),
cssparser_color::Color::Hsl(hsl) => {
let (r, g, b) = cssparser_color::hsl_to_rgb(
hsl.hue.unwrap_or(0.0) / 360.0,
hsl.saturation.unwrap_or(0.0),
hsl.lightness.unwrap_or(0.0),
);
Ok(rgba_from_unit_floats(r, g, b, hsl.alpha.unwrap_or(1.0)))
}
cssparser_color::Color::Hwb(hwb) => {
let (r, g, b) = cssparser_color::hwb_to_rgb(
hwb.hue.unwrap_or(0.0) / 360.0,
hwb.whiteness.unwrap_or(0.0),
hwb.blackness.unwrap_or(0.0),
);
Ok(rgba_from_unit_floats(r, g, b, hwb.alpha.unwrap_or(1.0)))
}
cssparser_color::Color::Lab(lab) => {
let srgb = Srgb::from_color(Lab::new(
lab.lightness.unwrap_or(0.0),
lab.a.unwrap_or(0.0),
lab.b.unwrap_or(0.0),
));
Ok(rgba_from_unit_floats(
srgb.red,
srgb.green,
srgb.blue,
lab.alpha.unwrap_or(1.0),
))
}
cssparser_color::Color::Lch(lch) => {
let srgb = Srgb::from_color(Lch::new(
lch.lightness.unwrap_or(0.0),
lch.chroma.unwrap_or(0.0),
lch.hue.unwrap_or(0.0),
));
Ok(rgba_from_unit_floats(
srgb.red,
srgb.green,
srgb.blue,
lch.alpha.unwrap_or(1.0),
))
}
cssparser_color::Color::Oklab(oklab) => {
let srgb = Srgb::from_color(Oklab::new(
oklab.lightness.unwrap_or(0.0),
oklab.a.unwrap_or(0.0),
oklab.b.unwrap_or(0.0),
));
Ok(rgba_from_unit_floats(
srgb.red,
srgb.green,
srgb.blue,
oklab.alpha.unwrap_or(1.0),
))
}
cssparser_color::Color::Oklch(oklch) => {
let srgb = Srgb::from_color(Oklch::new(
oklch.lightness.unwrap_or(0.0),
oklch.chroma.unwrap_or(0.0),
oklch.hue.unwrap_or(0.0),
));
Ok(rgba_from_unit_floats(
srgb.red,
srgb.green,
srgb.blue,
oklch.alpha.unwrap_or(1.0),
))
}
_ => Err(input.new_custom_error(())),
}
}
fn parse_color_mix<'i>(
input: &mut Parser<'i, '_>,
depth: u32,
) -> Result<Color, ParseError<'i, ()>> {
if depth >= MAX_COLOR_MIX_DEPTH {
return Err(input.new_custom_error(()));
}
if input.expect_function_matching("color-mix").is_err() {
return Err(input.new_custom_error(()));
}
input.parse_nested_block(|input| {
if input.expect_ident_matching("in").is_err() {
return Err(input.new_custom_error(()));
}
let space = match input.expect_ident() {
Ok(ident) => ColorSpace::parse(ident).ok_or(()),
Err(_) => Err(()),
}
.map_err(|_| input.new_custom_error(()))?;
let hue_method = input
.try_parse(|input| {
let method = match input.expect_ident() {
Ok(ident) => HueMethod::parse(ident).ok_or(()),
Err(_) => Err(()),
}?;
input.expect_ident_matching("hue").map_err(|_| ())?;
Ok::<_, ()>(method)
})
.unwrap_or_default();
input
.expect_comma()
.map_err(|_| input.new_custom_error(()))?;
let (first, first_percentage) = parse_color_mix_operand(input, depth)?;
input
.expect_comma()
.map_err(|_| input.new_custom_error(()))?;
let (second, second_percentage) = parse_color_mix_operand(input, depth)?;
let (w1, w2, alpha_multiplier) = normalize_mix_weights(first_percentage, second_percentage)
.ok_or_else(|| input.new_custom_error(()))?;
let (red, green, blue, alpha) = color_mix::mix(space, hue_method, first, w1, second, w2);
Ok(rgba_from_unit_floats(
red,
green,
blue,
alpha * alpha_multiplier,
))
})
}
#[allow(clippy::type_complexity)]
fn parse_color_mix_operand<'i>(
input: &mut Parser<'i, '_>,
depth: u32,
) -> Result<(color_mix::UnitRgba, Option<f32>), ParseError<'i, ()>> {
let leading = input.try_parse(parse_mix_percentage).ok();
let color = parse_color_at_depth(input, depth + 1)?;
let percentage = match leading {
Some(p) => Some(p),
None => input.try_parse(parse_mix_percentage).ok(),
};
let Color::Rgba {
red,
green,
blue,
alpha,
} = color
else {
return Err(input.new_custom_error(()));
};
Ok((
(
red as f32 / 255.0,
green as f32 / 255.0,
blue as f32 / 255.0,
alpha,
),
percentage,
))
}
fn parse_mix_percentage<'i>(input: &mut Parser<'i, '_>) -> Result<f32, ParseError<'i, ()>> {
match input.expect_percentage() {
Ok(p) if p >= 0.0 => Ok(p),
_ => Err(input.new_custom_error(())),
}
}
fn normalize_mix_weights(first: Option<f32>, second: Option<f32>) -> Option<(f32, f32, f32)> {
match (first, second) {
(None, None) => Some((0.5, 0.5, 1.0)),
(Some(p), None) => Some((p, 1.0 - p, 1.0)),
(None, Some(p)) => Some((1.0 - p, p, 1.0)),
(Some(a), Some(b)) => {
let sum = a + b;
if sum <= 0.0 {
return None;
}
let alpha_multiplier = if sum < 1.0 { sum } else { 1.0 };
Some((a / sum, b / sum, alpha_multiplier))
}
}
}
fn rgba_from_unit_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Color {
let to_u8 = |c: f32| (c.clamp(0.0, 1.0) * 255.0).round() as u8;
Color::Rgba {
red: to_u8(red),
green: to_u8(green),
blue: to_u8(blue),
alpha: alpha.clamp(0.0, 1.0),
}
}
fn parse_object_fit<'i>(input: &mut Parser<'i, '_>) -> Result<ObjectFit, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"fill" => ObjectFit::Fill,
"contain" => ObjectFit::Contain,
"cover" => ObjectFit::Cover,
"none" => ObjectFit::None,
"scale-down" => ObjectFit::ScaleDown,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_box_shadow<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<SpecifiedBoxShadow>, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(Vec::new());
}
input.parse_comma_separated(parse_single_box_shadow)
}
fn parse_single_box_shadow<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedBoxShadow, ParseError<'i, ()>> {
let mut inset = false;
let mut color = None;
let mut lengths = None;
loop {
if !inset
&& input
.try_parse(|input| input.expect_ident_matching("inset"))
.is_ok()
{
inset = true;
continue;
}
if color.is_none() {
if let Ok(c) = input.try_parse(parse_color) {
color = Some(c);
continue;
}
}
if lengths.is_none() {
if let Ok(l) = input.try_parse(parse_box_shadow_lengths) {
lengths = Some(l);
continue;
}
}
break;
}
let Some((offset_x, offset_y, blur_radius, spread_radius)) = lengths else {
return Err(input.new_custom_error(()));
};
Ok(SpecifiedBoxShadow {
offset_x,
offset_y,
blur_radius,
spread_radius,
color,
inset,
})
}
#[allow(clippy::type_complexity)]
fn parse_box_shadow_lengths<'i>(
input: &mut Parser<'i, '_>,
) -> Result<
(
SpecifiedLength,
SpecifiedLength,
SpecifiedLength,
SpecifiedLength,
),
ParseError<'i, ()>,
> {
let offset_x = parse_length(input)?;
let offset_y = parse_length(input)?;
let blur_radius = input
.try_parse(parse_length)
.unwrap_or(SpecifiedLength::Px(0.0));
let spread_radius = input
.try_parse(parse_length)
.unwrap_or(SpecifiedLength::Px(0.0));
Ok((offset_x, offset_y, blur_radius, spread_radius))
}
fn parse_content<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Option<Vec<ContentPart>>, ParseError<'i, ()>> {
if input
.try_parse(|input| -> Result<(), ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
match_ignore_ascii_case! { &ident,
"none" | "normal" => Ok(()),
_ => Err(input.new_custom_error(())),
}
})
.is_ok()
{
return Ok(None);
}
let mut parts = Vec::new();
loop {
if let Ok(s) = input.try_parse(|input| input.expect_string_cloned()) {
parts.push(ContentPart::String(s.as_ref().to_string()));
continue;
}
if let Ok(part) = input.try_parse(parse_content_quote_keyword) {
parts.push(part);
continue;
}
if let Ok(part) = input.try_parse(parse_content_function) {
parts.push(part);
continue;
}
break;
}
if parts.is_empty() {
return Err(input.new_custom_error(()));
}
Ok(Some(parts))
}
fn parse_content_quote_keyword<'i>(
input: &mut Parser<'i, '_>,
) -> Result<ContentPart, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"open-quote" => ContentPart::OpenQuote,
"close-quote" => ContentPart::CloseQuote,
"no-open-quote" => ContentPart::NoOpenQuote,
"no-close-quote" => ContentPart::NoCloseQuote,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_content_function<'i>(
input: &mut Parser<'i, '_>,
) -> Result<ContentPart, ParseError<'i, ()>> {
let name = input.expect_function()?.clone();
if name.eq_ignore_ascii_case("attr") {
return input.parse_nested_block(|input| {
let ident = input.expect_ident()?.clone();
Ok(ContentPart::Attr(ident.as_ref().to_string()))
});
}
if name.eq_ignore_ascii_case("counter") {
return input.parse_nested_block(|input| {
let counter_name = input.expect_ident()?.as_ref().to_string();
let style = if input.try_parse(|input| input.expect_comma()).is_ok() {
parse_list_style_type(input)?
} else {
ListStyleType::Decimal
};
Ok(ContentPart::Counter(counter_name, style))
});
}
if name.eq_ignore_ascii_case("counters") {
return input.parse_nested_block(|input| {
let counter_name = input.expect_ident()?.as_ref().to_string();
input.expect_comma()?;
let separator = input.expect_string()?.as_ref().to_string();
let style = if input.try_parse(|input| input.expect_comma()).is_ok() {
parse_list_style_type(input)?
} else {
ListStyleType::Decimal
};
Ok(ContentPart::Counters(counter_name, separator, style))
});
}
Err(input.new_custom_error(()))
}
fn parse_counter_list<'i>(
input: &mut Parser<'i, '_>,
default_value: i32,
) -> Result<Vec<(String, i32)>, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(Vec::new());
}
let mut result = Vec::new();
loop {
let Ok(name) = input.try_parse(|input| input.expect_ident_cloned()) else {
break;
};
let value = input
.try_parse(|input| input.expect_integer())
.unwrap_or(default_value);
result.push((name.as_ref().to_string(), value));
}
if result.is_empty() {
return Err(input.new_custom_error(()));
}
Ok(result)
}
fn parse_quotes<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Option<Vec<QuotePair>>, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(None);
}
let mut pairs = Vec::new();
loop {
let Ok(open) = input.try_parse(|input| input.expect_string_cloned()) else {
break;
};
let close = input.expect_string()?.as_ref().to_string();
pairs.push(QuotePair {
open: open.as_ref().to_string(),
close,
});
}
if pairs.is_empty() {
return Err(input.new_custom_error(()));
}
Ok(Some(pairs))
}
fn parse_background_image<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Option<String>, ParseError<'i, ()>> {
if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
return match_ignore_ascii_case! { &ident,
"none" => Ok(None),
_ => Err(input.new_custom_error(())),
};
}
let url = input
.expect_url_or_string()
.map_err(|_| input.new_custom_error(()))?;
Ok(Some(url.as_ref().to_string()))
}
enum BackgroundPositionComponent {
Horizontal(SpecifiedLengthPercentage),
Vertical(SpecifiedLengthPercentage),
Either(SpecifiedLengthPercentage),
}
fn parse_background_position_component<'i>(
input: &mut Parser<'i, '_>,
) -> Result<BackgroundPositionComponent, ParseError<'i, ()>> {
use BackgroundPositionComponent as C;
if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
return match_ignore_ascii_case! { &ident,
"left" => Ok(C::Horizontal(SpecifiedLengthPercentage::Percentage(0.0))),
"right" => Ok(C::Horizontal(SpecifiedLengthPercentage::Percentage(1.0))),
"top" => Ok(C::Vertical(SpecifiedLengthPercentage::Percentage(0.0))),
"bottom" => Ok(C::Vertical(SpecifiedLengthPercentage::Percentage(1.0))),
"center" => Ok(C::Either(SpecifiedLengthPercentage::Percentage(0.5))),
_ => Err(input.new_custom_error(())),
};
}
Ok(C::Either(parse_length_percentage(input)?))
}
fn parse_background_position<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedBackgroundPosition, ParseError<'i, ()>> {
use BackgroundPositionComponent as C;
let half = SpecifiedLengthPercentage::Percentage(0.5);
let first = parse_background_position_component(input)?;
let second = input.try_parse(parse_background_position_component).ok();
let (horizontal, vertical) = match second {
None => match first {
C::Vertical(v) => (half, v),
C::Horizontal(h) | C::Either(h) => (h, half),
},
Some(second) => {
let first_is_vertical = matches!(first, C::Vertical(_));
let second_is_horizontal = matches!(second, C::Horizontal(_));
if matches!((&first, &second), (C::Horizontal(_), C::Horizontal(_)))
|| matches!((&first, &second), (C::Vertical(_), C::Vertical(_)))
{
return Err(input.new_custom_error(()));
}
let value_of = |c: C| match c {
C::Horizontal(v) | C::Vertical(v) | C::Either(v) => v,
};
if first_is_vertical || second_is_horizontal {
(value_of(second), value_of(first))
} else {
(value_of(first), value_of(second))
}
}
};
Ok(SpecifiedBackgroundPosition {
horizontal,
vertical,
})
}
fn parse_background_size<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedBackgroundSize, ParseError<'i, ()>> {
if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
return match_ignore_ascii_case! { &ident,
"cover" => Ok(SpecifiedBackgroundSize::Cover),
"contain" => Ok(SpecifiedBackgroundSize::Contain),
"auto" => Ok(SpecifiedBackgroundSize::WidthHeight(
SpecifiedLengthPercentageOrAuto::Auto,
input
.try_parse(parse_length_percentage_or_auto)
.unwrap_or(SpecifiedLengthPercentageOrAuto::Auto),
)),
_ => Err(input.new_custom_error(())),
};
}
let width = SpecifiedLengthPercentageOrAuto::LengthPercentage(parse_length_percentage(input)?);
let height = input
.try_parse(parse_length_percentage_or_auto)
.unwrap_or(SpecifiedLengthPercentageOrAuto::Auto);
Ok(SpecifiedBackgroundSize::WidthHeight(width, height))
}
fn parse_background_repeat<'i>(
input: &mut Parser<'i, '_>,
) -> Result<BackgroundRepeat, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"repeat" => BackgroundRepeat::Repeat,
"repeat-x" => BackgroundRepeat::RepeatX,
"repeat-y" => BackgroundRepeat::RepeatY,
"no-repeat" => BackgroundRepeat::NoRepeat,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_background_attachment<'i>(
input: &mut Parser<'i, '_>,
) -> Result<BackgroundAttachment, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"scroll" => BackgroundAttachment::Scroll,
"fixed" => BackgroundAttachment::Fixed,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_background_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let mut color = None;
let mut image = None;
let mut repeat = None;
let mut attachment = None;
let mut position = None;
let mut size = None;
loop {
if position.is_none() {
if let Ok(p) = input.try_parse(parse_background_position) {
position = Some(p);
if input.try_parse(|input| input.expect_delim('/')).is_ok() {
size = Some(parse_background_size(input)?);
}
continue;
}
}
if repeat.is_none() {
if let Ok(r) = input.try_parse(parse_background_repeat) {
repeat = Some(r);
continue;
}
}
if attachment.is_none() {
if let Ok(a) = input.try_parse(parse_background_attachment) {
attachment = Some(a);
continue;
}
}
if image.is_none() {
if let Ok(img) = input.try_parse(parse_background_image) {
image = Some(img);
continue;
}
}
if color.is_none() {
if let Ok(c) = input.try_parse(parse_color) {
color = Some(c);
continue;
}
}
break;
}
Ok(vec![
D::BackgroundColor(color.unwrap_or(Color::Rgba {
red: 0,
green: 0,
blue: 0,
alpha: 0.0,
})),
D::BackgroundImage(image.unwrap_or(None)),
D::BackgroundPosition(position.unwrap_or(SpecifiedBackgroundPosition {
horizontal: SpecifiedLengthPercentage::Percentage(0.0),
vertical: SpecifiedLengthPercentage::Percentage(0.0),
})),
D::BackgroundSize(size.unwrap_or(SpecifiedBackgroundSize::WidthHeight(
SpecifiedLengthPercentageOrAuto::Auto,
SpecifiedLengthPercentageOrAuto::Auto,
))),
D::BackgroundRepeat(repeat.unwrap_or(BackgroundRepeat::Repeat)),
D::BackgroundAttachment(attachment.unwrap_or(BackgroundAttachment::Scroll)),
])
}
fn parse_font_family<'i>(input: &mut Parser<'i, '_>) -> Result<Vec<String>, ParseError<'i, ()>> {
input.parse_comma_separated(parse_family_name)
}
pub(crate) fn parse_family_name<'i>(
input: &mut Parser<'i, '_>,
) -> Result<String, ParseError<'i, ()>> {
if let Ok(name) = input.try_parse(|input| input.expect_string_cloned()) {
return Ok(name.as_ref().to_string());
}
let mut name = input.expect_ident()?.as_ref().to_string();
while let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
name.push(' ');
name.push_str(&ident);
}
Ok(name)
}
fn parse_flex_direction<'i>(
input: &mut Parser<'i, '_>,
) -> Result<FlexDirection, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"row" => FlexDirection::Row,
"row-reverse" => FlexDirection::RowReverse,
"column" => FlexDirection::Column,
"column-reverse" => FlexDirection::ColumnReverse,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_flex_wrap<'i>(input: &mut Parser<'i, '_>) -> Result<FlexWrap, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"nowrap" => FlexWrap::NoWrap,
"wrap" => FlexWrap::Wrap,
"wrap-reverse" => FlexWrap::WrapReverse,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_justify_content<'i>(
input: &mut Parser<'i, '_>,
) -> Result<JustifyContent, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"normal" => JustifyContent::Normal,
"flex-start" | "start" => JustifyContent::FlexStart,
"flex-end" | "end" => JustifyContent::FlexEnd,
"center" => JustifyContent::Center,
"space-between" => JustifyContent::SpaceBetween,
"space-around" => JustifyContent::SpaceAround,
"space-evenly" => JustifyContent::SpaceEvenly,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_align_items<'i>(input: &mut Parser<'i, '_>) -> Result<AlignItems, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"flex-start" | "start" => AlignItems::FlexStart,
"flex-end" | "end" => AlignItems::FlexEnd,
"center" => AlignItems::Center,
"baseline" => AlignItems::Baseline,
"stretch" => AlignItems::Stretch,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_align_content<'i>(input: &mut Parser<'i, '_>) -> Result<AlignContent, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"flex-start" | "start" => AlignContent::FlexStart,
"flex-end" | "end" => AlignContent::FlexEnd,
"center" => AlignContent::Center,
"stretch" => AlignContent::Stretch,
"space-between" => AlignContent::SpaceBetween,
"space-around" => AlignContent::SpaceAround,
"space-evenly" => AlignContent::SpaceEvenly,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_align_self<'i>(input: &mut Parser<'i, '_>) -> Result<AlignSelf, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"auto" => AlignSelf::Auto,
"flex-start" | "start" => AlignSelf::FlexStart,
"flex-end" | "end" => AlignSelf::FlexEnd,
"center" => AlignSelf::Center,
"baseline" => AlignSelf::Baseline,
"stretch" => AlignSelf::Stretch,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_non_negative_number<'i>(input: &mut Parser<'i, '_>) -> Result<f32, ParseError<'i, ()>> {
let value = input.expect_number()?;
if value < 0.0 {
return Err(input.new_custom_error(()));
}
Ok(value)
}
fn parse_flex_basis<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedFlexBasis, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("auto"))
.is_ok()
{
return Ok(SpecifiedFlexBasis::Auto);
}
if input
.try_parse(|input| input.expect_ident_matching("content"))
.is_ok()
{
return Ok(SpecifiedFlexBasis::Content);
}
parse_length_percentage(input).map(SpecifiedFlexBasis::LengthPercentage)
}
fn parse_flex_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(vec![
D::FlexGrow(0.0),
D::FlexShrink(0.0),
D::FlexBasis(SpecifiedFlexBasis::Auto),
]);
}
let mut grow = None;
let mut shrink = None;
let mut basis = None;
loop {
if grow.is_none() {
if let Ok(g) = input.try_parse(parse_non_negative_number) {
grow = Some(g);
if let Ok(s) = input.try_parse(parse_non_negative_number) {
shrink = Some(s);
}
continue;
}
}
if basis.is_none() {
if let Ok(b) = input.try_parse(parse_flex_basis) {
basis = Some(b);
continue;
}
}
break;
}
if grow.is_none() && basis.is_none() {
return Err(input.new_custom_error(()));
}
Ok(vec![
D::FlexGrow(grow.unwrap_or(1.0)),
D::FlexShrink(shrink.unwrap_or(1.0)),
D::FlexBasis(basis.unwrap_or(SpecifiedFlexBasis::LengthPercentage(
SpecifiedLengthPercentage::Percentage(0.0),
))),
])
}
fn parse_gap_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let row = parse_length_percentage(input)?;
let column = input.try_parse(parse_length_percentage).unwrap_or(row);
Ok(vec![D::RowGap(row), D::ColumnGap(column)])
}
fn parse_transform<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<SpecifiedTransformFunction>, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(Vec::new());
}
let mut functions = Vec::new();
while let Ok(f) = input.try_parse(parse_transform_function) {
functions.push(f);
}
if functions.is_empty() {
return Err(input.new_custom_error(()));
}
Ok(functions)
}
fn parse_transform_function<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedTransformFunction, ParseError<'i, ()>> {
use SpecifiedTransformFunction as F;
let name = match input.next()?.clone() {
Token::Function(name) => name,
_ => return Err(input.new_custom_error(())),
};
input.parse_nested_block(|input| {
Ok(match_ignore_ascii_case! { &name,
"translate" => {
let x = parse_length_percentage(input)?;
let y = input
.try_parse(|input| {
input.expect_comma()?;
parse_length_percentage(input)
})
.unwrap_or(SpecifiedLengthPercentage::Length(SpecifiedLength::Px(0.0)));
F::Translate(x, y)
},
"translatex" => F::TranslateX(parse_length_percentage(input)?),
"translatey" => F::TranslateY(parse_length_percentage(input)?),
"scale" => {
let x = input.expect_number()?;
let y = input
.try_parse(|input| {
input.expect_comma()?;
input.expect_number()
})
.unwrap_or(x);
F::Scale(x, y)
},
"scalex" => F::ScaleX(input.expect_number()?),
"scaley" => F::ScaleY(input.expect_number()?),
"rotate" => F::Rotate(parse_angle_radians(input)?),
"skew" => {
let x = parse_angle_radians(input)?;
let y = input
.try_parse(|input| {
input.expect_comma()?;
parse_angle_radians(input)
})
.unwrap_or(0.0);
F::Skew(x, y)
},
"skewx" => F::SkewX(parse_angle_radians(input)?),
"skewy" => F::SkewY(parse_angle_radians(input)?),
"matrix" => {
let a = input.expect_number()?;
input.expect_comma()?;
let b = input.expect_number()?;
input.expect_comma()?;
let c = input.expect_number()?;
input.expect_comma()?;
let d = input.expect_number()?;
input.expect_comma()?;
let e = input.expect_number()?;
input.expect_comma()?;
let f = input.expect_number()?;
F::Matrix(a, b, c, d, e, f)
},
_ => return Err(input.new_custom_error(())),
})
})
}
fn parse_angle_radians<'i>(input: &mut Parser<'i, '_>) -> Result<f32, ParseError<'i, ()>> {
let token = input.next()?.clone();
match token {
Token::Number { value: 0.0, .. } => Ok(0.0),
Token::Dimension {
value, ref unit, ..
} => {
if unit.eq_ignore_ascii_case("deg") {
Ok(value.to_radians())
} else if unit.eq_ignore_ascii_case("rad") {
Ok(value)
} else if unit.eq_ignore_ascii_case("grad") {
Ok(value * std::f32::consts::PI / 200.0)
} else if unit.eq_ignore_ascii_case("turn") {
Ok(value * std::f32::consts::TAU)
} else {
Err(input.new_custom_error(()))
}
}
_ => Err(input.new_custom_error(())),
}
}
fn parse_track_list<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedTrackList, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(SpecifiedTrackList::default());
}
let mut components = Vec::new();
let mut line_names: Vec<Vec<String>> = vec![parse_line_names(input)];
loop {
if let Ok(repeat) = input.try_parse(parse_track_repeat) {
components.push(repeat);
} else if let Ok(size) = input.try_parse(parse_track_size) {
components.push(SpecifiedTrackComponent::Single(size));
} else {
break;
}
line_names.push(parse_line_names(input));
}
if components.is_empty() {
return Err(input.new_custom_error(()));
}
Ok(SpecifiedTrackList {
components,
line_names,
})
}
fn parse_line_names(input: &mut Parser<'_, '_>) -> Vec<String> {
input
.try_parse(|input| {
input.expect_square_bracket_block()?;
input.parse_nested_block(|input| -> Result<Vec<String>, ParseError<'_, ()>> {
let mut names = Vec::new();
while let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
names.push(ident.as_ref().to_string());
}
Ok(names)
})
})
.unwrap_or_default()
}
fn parse_track_repeat<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedTrackComponent, ParseError<'i, ()>> {
input.expect_function_matching("repeat")?;
input.parse_nested_block(|input| {
let count = if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
match_ignore_ascii_case! { &ident,
"auto-fill" => RepeatCount::AutoFill,
"auto-fit" => RepeatCount::AutoFit,
_ => return Err(input.new_custom_error(())),
}
} else {
let count = input.expect_integer()?;
if count < 1 {
return Err(input.new_custom_error(()));
}
RepeatCount::Count(count as u16)
};
input.expect_comma()?;
let mut tracks = Vec::new();
let mut line_names: Vec<Vec<String>> = vec![parse_line_names(input)];
while let Ok(size) = input.try_parse(parse_track_size) {
tracks.push(size);
line_names.push(parse_line_names(input));
}
if tracks.is_empty() {
return Err(input.new_custom_error(()));
}
Ok(SpecifiedTrackComponent::Repeat {
count,
tracks,
line_names,
})
})
}
fn parse_track_size<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedTrackSize, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_function_matching("minmax"))
.is_ok()
{
return input.parse_nested_block(|input| {
let min = parse_track_breadth(input)?;
if matches!(min, SpecifiedTrackBreadth::Fr(_)) {
return Err(input.new_custom_error(()));
}
input.expect_comma()?;
let max = parse_track_breadth(input)?;
Ok(SpecifiedTrackSize::MinMax(min, max))
});
}
if input
.try_parse(|input| input.expect_function_matching("fit-content"))
.is_ok()
{
return input.parse_nested_block(|input| {
Ok(SpecifiedTrackSize::FitContent(parse_length_percentage(
input,
)?))
});
}
Ok(SpecifiedTrackSize::Breadth(parse_track_breadth(input)?))
}
fn parse_track_breadth<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedTrackBreadth, ParseError<'i, ()>> {
if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
return match_ignore_ascii_case! { &ident,
"auto" => Ok(SpecifiedTrackBreadth::Auto),
"min-content" => Ok(SpecifiedTrackBreadth::MinContent),
"max-content" => Ok(SpecifiedTrackBreadth::MaxContent),
_ => Err(input.new_custom_error(())),
};
}
if let Ok(fr) = input.try_parse(|input| -> Result<f32, ParseError<'i, ()>> {
let token = input.next()?.clone();
match token {
Token::Dimension {
value, ref unit, ..
} if unit.eq_ignore_ascii_case("fr") => {
if value < 0.0 {
return Err(input.new_custom_error(()));
}
Ok(value)
}
_ => Err(input.new_custom_error(())),
}
}) {
return Ok(SpecifiedTrackBreadth::Fr(fr));
}
match parse_length_percentage(input)? {
SpecifiedLengthPercentage::Length(length) => Ok(SpecifiedTrackBreadth::Length(length)),
SpecifiedLengthPercentage::Percentage(v) => Ok(SpecifiedTrackBreadth::Percentage(v)),
SpecifiedLengthPercentage::Calc(_) => Err(input.new_custom_error(())),
}
}
fn parse_auto_track_list<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<SpecifiedTrackSize>, ParseError<'i, ()>> {
let mut sizes = Vec::new();
while let Ok(size) = input.try_parse(parse_track_size) {
sizes.push(size);
}
if sizes.is_empty() {
return Err(input.new_custom_error(()));
}
Ok(sizes)
}
fn parse_grid_auto_flow<'i>(
input: &mut Parser<'i, '_>,
) -> Result<GridAutoFlow, ParseError<'i, ()>> {
let mut column = None;
let mut dense = false;
loop {
let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) else {
break;
};
match_ignore_ascii_case! { &ident,
"row" if column.is_none() => column = Some(false),
"column" if column.is_none() => column = Some(true),
"dense" if !dense => dense = true,
_ => return Err(input.new_custom_error(())),
}
}
if column.is_none() && !dense {
return Err(input.new_custom_error(()));
}
Ok(match (column.unwrap_or(false), dense) {
(false, false) => GridAutoFlow::Row,
(false, true) => GridAutoFlow::RowDense,
(true, false) => GridAutoFlow::Column,
(true, true) => GridAutoFlow::ColumnDense,
})
}
fn parse_grid_line<'i>(input: &mut Parser<'i, '_>) -> Result<GridLine, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("auto"))
.is_ok()
{
return Ok(GridLine::Auto);
}
if input
.try_parse(|input| input.expect_ident_matching("span"))
.is_ok()
{
if let Ok(count) = input.try_parse(|input| input.expect_integer()) {
if count < 1 {
return Err(input.new_custom_error(()));
}
return Ok(GridLine::Span(count as u16));
}
let name = input.expect_ident()?.as_ref().to_string();
let count = input.try_parse(|input| input.expect_integer()).unwrap_or(1);
if count < 1 {
return Err(input.new_custom_error(()));
}
return Ok(GridLine::NamedSpan(name, count as u16));
}
if let Ok(line) = input.try_parse(|input| input.expect_integer()) {
if line == 0 {
return Err(input.new_custom_error(()));
}
return Ok(GridLine::Line(line as i16));
}
Ok(GridLine::Named(input.expect_ident()?.as_ref().to_string()))
}
fn parse_grid_row_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (start, end) = parse_grid_line_pair(input)?;
Ok(vec![D::GridRowStart(start), D::GridRowEnd(end)])
}
fn parse_grid_column_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let (start, end) = parse_grid_line_pair(input)?;
Ok(vec![D::GridColumnStart(start), D::GridColumnEnd(end)])
}
fn parse_grid_line_pair<'i>(
input: &mut Parser<'i, '_>,
) -> Result<(GridLine, GridLine), ParseError<'i, ()>> {
let start = parse_grid_line(input)?;
let end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
parse_grid_line(input)?
} else {
match &start {
GridLine::Named(name) => GridLine::Named(name.clone()),
_ => GridLine::Auto,
}
};
Ok((start, end))
}
fn parse_grid_area_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let row_start = parse_grid_line(input)?;
let mut slots = Vec::new();
while input.try_parse(|input| input.expect_delim('/')).is_ok() {
slots.push(parse_grid_line(input)?);
}
let fallback = |line: &GridLine| match line {
GridLine::Named(name) => GridLine::Named(name.clone()),
_ => GridLine::Auto,
};
let column_start = slots
.first()
.cloned()
.unwrap_or_else(|| fallback(&row_start));
let row_end = slots
.get(1)
.cloned()
.unwrap_or_else(|| fallback(&row_start));
let column_end = slots
.get(2)
.cloned()
.unwrap_or_else(|| fallback(&column_start));
Ok(vec![
D::GridRowStart(row_start),
D::GridColumnStart(column_start),
D::GridRowEnd(row_end),
D::GridColumnEnd(column_end),
])
}
fn parse_grid_template_areas<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<GridArea>, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(Vec::new());
}
let mut rows: Vec<Vec<String>> = Vec::new();
while let Ok(s) = input.try_parse(|input| input.expect_string_cloned()) {
rows.push(s.split_whitespace().map(|cell| cell.to_string()).collect());
}
if rows.is_empty() {
return Err(input.new_custom_error(()));
}
let column_count = rows[0].len();
if column_count == 0 || rows.iter().any(|row| row.len() != column_count) {
return Err(input.new_custom_error(()));
}
let mut bounds: Vec<(String, usize, usize, usize, usize)> = Vec::new();
for (r, row) in rows.iter().enumerate() {
for (c, cell) in row.iter().enumerate() {
if cell.chars().all(|ch| ch == '.') {
continue;
}
match bounds.iter_mut().find(|(name, ..)| name == cell) {
Some((_, row_start, row_end, column_start, column_end)) => {
*row_start = (*row_start).min(r);
*row_end = (*row_end).max(r);
*column_start = (*column_start).min(c);
*column_end = (*column_end).max(c);
}
None => bounds.push((cell.clone(), r, r, c, c)),
}
}
}
for (name, row_start, row_end, column_start, column_end) in &bounds {
for row in rows.iter().take(row_end + 1).skip(*row_start) {
for cell in row.iter().take(column_end + 1).skip(*column_start) {
if cell != name {
return Err(input.new_custom_error(()));
}
}
}
}
Ok(bounds
.into_iter()
.map(
|(name, row_start, row_end, column_start, column_end)| GridArea {
name,
row_start: row_start as u16 + 1,
row_end: row_end as u16 + 2,
column_start: column_start as u16 + 1,
column_end: column_end as u16 + 2,
},
)
.collect())
}
fn parse_text_shadow<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<SpecifiedTextShadow>, ParseError<'i, ()>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(Vec::new());
}
input.parse_comma_separated(parse_single_text_shadow)
}
fn parse_single_text_shadow<'i>(
input: &mut Parser<'i, '_>,
) -> Result<SpecifiedTextShadow, ParseError<'i, ()>> {
let mut color = None;
let mut lengths = None;
loop {
if color.is_none() {
if let Ok(c) = input.try_parse(parse_color) {
color = Some(c);
continue;
}
}
if lengths.is_none() {
if let Ok(l) = input.try_parse(parse_text_shadow_lengths) {
lengths = Some(l);
continue;
}
}
break;
}
let Some((offset_x, offset_y, blur_radius)) = lengths else {
return Err(input.new_custom_error(()));
};
Ok(SpecifiedTextShadow {
offset_x,
offset_y,
blur_radius,
color,
})
}
fn parse_text_shadow_lengths<'i>(
input: &mut Parser<'i, '_>,
) -> Result<(SpecifiedLength, SpecifiedLength, SpecifiedLength), ParseError<'i, ()>> {
let offset_x = parse_length(input)?;
let offset_y = parse_length(input)?;
let blur_radius = input
.try_parse(parse_length)
.unwrap_or(SpecifiedLength::Px(0.0));
Ok((offset_x, offset_y, blur_radius))
}
fn parse_text_overflow<'i>(input: &mut Parser<'i, '_>) -> Result<TextOverflow, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"clip" => TextOverflow::Clip,
"ellipsis" => TextOverflow::Ellipsis,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_word_break<'i>(input: &mut Parser<'i, '_>) -> Result<WordBreak, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"normal" => WordBreak::Normal,
"break-all" => WordBreak::BreakAll,
"keep-all" => WordBreak::KeepAll,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_overflow_wrap<'i>(input: &mut Parser<'i, '_>) -> Result<OverflowWrap, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"normal" => OverflowWrap::Normal,
"break-word" | "anywhere" => OverflowWrap::BreakWord,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_hyphens<'i>(input: &mut Parser<'i, '_>) -> Result<Hyphens, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"none" => Hyphens::None,
"manual" | "auto" => Hyphens::Manual,
_ => return Err(input.new_custom_error(())),
})
}
fn parse_text_emphasis_style<'i>(
input: &mut Parser<'i, '_>,
) -> Result<EmphasisStyle, ParseError<'i, ()>> {
if let Ok(s) = input.try_parse(|input| input.expect_string_cloned()) {
let Some(ch) = s.chars().next() else {
return Err(input.new_custom_error(()));
};
return Ok(EmphasisStyle::String(ch));
}
enum Keyword {
None,
Filled(bool),
Shape(EmphasisShape),
}
let mut filled = None;
let mut shape = None;
loop {
let Ok(keyword) = input.try_parse(|input| -> Result<Keyword, ParseError<'i, ()>> {
let ident = input.expect_ident()?.clone();
Ok(match_ignore_ascii_case! { &ident,
"none" => Keyword::None,
"filled" => Keyword::Filled(true),
"open" => Keyword::Filled(false),
"dot" => Keyword::Shape(EmphasisShape::Dot),
"circle" => Keyword::Shape(EmphasisShape::Circle),
"double-circle" => Keyword::Shape(EmphasisShape::DoubleCircle),
"triangle" => Keyword::Shape(EmphasisShape::Triangle),
"sesame" => Keyword::Shape(EmphasisShape::Sesame),
_ => return Err(input.new_custom_error(())),
})
}) else {
break;
};
match keyword {
Keyword::None => return Ok(EmphasisStyle::None),
Keyword::Filled(_) if filled.is_some() => return Err(input.new_custom_error(())),
Keyword::Shape(_) if shape.is_some() => return Err(input.new_custom_error(())),
Keyword::Filled(v) => filled = Some(v),
Keyword::Shape(v) => shape = Some(v),
}
}
if filled.is_none() && shape.is_none() {
return Err(input.new_custom_error(()));
}
Ok(EmphasisStyle::Shape {
shape: shape.unwrap_or(EmphasisShape::Dot),
filled: filled.unwrap_or(true),
})
}
fn parse_text_emphasis_position<'i>(
input: &mut Parser<'i, '_>,
) -> Result<EmphasisPosition, ParseError<'i, ()>> {
let mut position = None;
loop {
let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) else {
break;
};
match_ignore_ascii_case! { &ident,
"over" if position.is_none() => position = Some(EmphasisPosition::Over),
"under" if position.is_none() => position = Some(EmphasisPosition::Under),
"right" | "left" => {},
_ => return Err(input.new_custom_error(())),
}
}
position.ok_or_else(|| input.new_custom_error(()))
}
fn parse_text_emphasis_shorthand<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Vec<PropertyDeclaration>, ParseError<'i, ()>> {
use PropertyDeclaration as D;
let mut style = None;
let mut color = None;
loop {
if style.is_none() {
if let Ok(s) = input.try_parse(parse_text_emphasis_style) {
style = Some(s);
continue;
}
}
if color.is_none() {
if let Ok(c) = input.try_parse(parse_color) {
color = Some(c);
continue;
}
}
break;
}
if style.is_none() && color.is_none() {
return Err(input.new_custom_error(()));
}
Ok(vec![
D::TextEmphasisStyle(style.unwrap_or_default()),
D::TextEmphasisColor(color.unwrap_or(Color::CurrentColor)),
])
}
fn parse_opacity<'i>(input: &mut Parser<'i, '_>) -> Result<f32, ParseError<'i, ()>> {
let token = input.next()?.clone();
let value = match token {
Token::Number { value, .. } => value,
Token::Percentage { unit_value, .. } => unit_value,
_ => return Err(input.new_custom_error(())),
};
Ok(value.clamp(0.0, 1.0))
}