1use crate::compat::Feature;
4use crate::context::PropertyHandlerContext;
5use crate::declaration::DeclarationList;
6use crate::error::{ParserError, PrinterError};
7use crate::logical::PropertyCategory;
8use crate::macros::{enum_property, property_bitflags};
9use crate::printer::Printer;
10use crate::properties::{Property, PropertyId};
11use crate::traits::{IsCompatible, Parse, PropertyHandler, ToCss};
12use crate::values::length::LengthPercentage;
13use crate::values::ratio::Ratio;
14use crate::vendor_prefix::VendorPrefix;
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use cssparser::*;
18use std::collections::HashMap;
19
20#[cfg(feature = "serde")]
21use crate::serialization::*;
22
23#[derive(Debug, Clone, PartialEq)]
29#[cfg_attr(feature = "visitor", derive(Visit))]
30#[cfg_attr(
31 feature = "serde",
32 derive(serde::Serialize, serde::Deserialize),
33 serde(tag = "type", rename_all = "kebab-case")
34)]
35#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
36#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
37pub enum Size {
38 Auto,
40 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LengthPercentage>"))]
42 LengthPercentage(LengthPercentage),
43 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
45 MinContent(VendorPrefix),
46 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
48 MaxContent(VendorPrefix),
49 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
51 FitContent(VendorPrefix),
52 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LengthPercentage>"))]
54 FitContentFunction(LengthPercentage),
55 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
57 Stretch(VendorPrefix),
58 Contain,
60}
61
62impl<'i> Parse<'i> for Size {
63 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
64 let res = input.try_parse(|input| {
65 let ident = input.expect_ident()?;
66 Ok(match_ignore_ascii_case! { &*ident,
67 "auto" => Size::Auto,
68 "min-content" => Size::MinContent(VendorPrefix::None),
69 "-webkit-min-content" => Size::MinContent(VendorPrefix::WebKit),
70 "-moz-min-content" => Size::MinContent(VendorPrefix::Moz),
71 "max-content" => Size::MaxContent(VendorPrefix::None),
72 "-webkit-max-content" => Size::MaxContent(VendorPrefix::WebKit),
73 "-moz-max-content" => Size::MaxContent(VendorPrefix::Moz),
74 "stretch" => Size::Stretch(VendorPrefix::None),
75 "-webkit-fill-available" => Size::Stretch(VendorPrefix::WebKit),
76 "-moz-available" => Size::Stretch(VendorPrefix::Moz),
77 "fit-content" => Size::FitContent(VendorPrefix::None),
78 "-webkit-fit-content" => Size::FitContent(VendorPrefix::WebKit),
79 "-moz-fit-content" => Size::FitContent(VendorPrefix::Moz),
80 "contain" => Size::Contain,
81 _ => return Err(input.new_custom_error(ParserError::InvalidValue))
82 })
83 });
84
85 if res.is_ok() {
86 return res;
87 }
88
89 if let Ok(res) = input.try_parse(parse_fit_content) {
90 return Ok(Size::FitContentFunction(res));
91 }
92
93 let lp = input.try_parse(LengthPercentage::parse)?;
94 Ok(Size::LengthPercentage(lp))
95 }
96}
97
98impl ToCss for Size {
99 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
100 where
101 W: std::fmt::Write,
102 {
103 use Size::*;
104 match self {
105 Auto => dest.write_str("auto"),
106 Contain => dest.write_str("contain"),
107 MinContent(vp) => {
108 vp.to_css(dest)?;
109 dest.write_str("min-content")
110 }
111 MaxContent(vp) => {
112 vp.to_css(dest)?;
113 dest.write_str("max-content")
114 }
115 FitContent(vp) => {
116 vp.to_css(dest)?;
117 dest.write_str("fit-content")
118 }
119 Stretch(vp) => match *vp {
120 VendorPrefix::None => dest.write_str("stretch"),
121 VendorPrefix::WebKit => dest.write_str("-webkit-fill-available"),
122 VendorPrefix::Moz => dest.write_str("-moz-available"),
123 _ => unreachable!(),
124 },
125 FitContentFunction(l) => {
126 dest.write_str("fit-content(")?;
127 l.to_css(dest)?;
128 dest.write_str(")")
129 }
130 LengthPercentage(l) => l.to_css(dest),
131 }
132 }
133}
134
135impl IsCompatible for Size {
136 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
137 use Size::*;
138 match self {
139 LengthPercentage(l) => l.is_compatible(browsers),
140 MinContent(..) => Feature::MinContentSize.is_compatible(browsers),
141 MaxContent(..) => Feature::MaxContentSize.is_compatible(browsers),
142 FitContent(..) => Feature::FitContentSize.is_compatible(browsers),
143 FitContentFunction(l) => {
144 Feature::FitContentFunctionSize.is_compatible(browsers) && l.is_compatible(browsers)
145 }
146 Stretch(vp) => match *vp {
147 VendorPrefix::None => Feature::StretchSize,
148 VendorPrefix::WebKit => Feature::WebkitFillAvailableSize,
149 VendorPrefix::Moz => Feature::MozAvailableSize,
150 _ => return false,
151 }
152 .is_compatible(browsers),
153 Contain => false, Auto => true,
155 }
156 }
157}
158
159#[derive(Debug, Clone, PartialEq)]
163#[cfg_attr(feature = "visitor", derive(Visit))]
164#[cfg_attr(
165 feature = "serde",
166 derive(serde::Serialize, serde::Deserialize),
167 serde(tag = "type", rename_all = "kebab-case")
168)]
169#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
170#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
171pub enum MaxSize {
172 None,
174 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LengthPercentage>"))]
176 LengthPercentage(LengthPercentage),
177 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
179 MinContent(VendorPrefix),
180 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
182 MaxContent(VendorPrefix),
183 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
185 FitContent(VendorPrefix),
186 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LengthPercentage>"))]
188 FitContentFunction(LengthPercentage),
189 #[cfg_attr(feature = "serde", serde(with = "PrefixWrapper"))]
191 Stretch(VendorPrefix),
192 Contain,
194}
195
196impl<'i> Parse<'i> for MaxSize {
197 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
198 let res = input.try_parse(|input| {
199 let ident = input.expect_ident()?;
200 Ok(match_ignore_ascii_case! { &*ident,
201 "none" => MaxSize::None,
202 "min-content" => MaxSize::MinContent(VendorPrefix::None),
203 "-webkit-min-content" => MaxSize::MinContent(VendorPrefix::WebKit),
204 "-moz-min-content" => MaxSize::MinContent(VendorPrefix::Moz),
205 "max-content" => MaxSize::MaxContent(VendorPrefix::None),
206 "-webkit-max-content" => MaxSize::MaxContent(VendorPrefix::WebKit),
207 "-moz-max-content" => MaxSize::MaxContent(VendorPrefix::Moz),
208 "stretch" => MaxSize::Stretch(VendorPrefix::None),
209 "-webkit-fill-available" => MaxSize::Stretch(VendorPrefix::WebKit),
210 "-moz-available" => MaxSize::Stretch(VendorPrefix::Moz),
211 "fit-content" => MaxSize::FitContent(VendorPrefix::None),
212 "-webkit-fit-content" => MaxSize::FitContent(VendorPrefix::WebKit),
213 "-moz-fit-content" => MaxSize::FitContent(VendorPrefix::Moz),
214 "contain" => MaxSize::Contain,
215 _ => return Err(input.new_custom_error(ParserError::InvalidValue))
216 })
217 });
218
219 if res.is_ok() {
220 return res;
221 }
222
223 if let Ok(res) = input.try_parse(parse_fit_content) {
224 return Ok(MaxSize::FitContentFunction(res));
225 }
226
227 let lp = input.try_parse(LengthPercentage::parse)?;
228 Ok(MaxSize::LengthPercentage(lp))
229 }
230}
231
232impl ToCss for MaxSize {
233 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
234 where
235 W: std::fmt::Write,
236 {
237 use MaxSize::*;
238 match self {
239 None => dest.write_str("none"),
240 Contain => dest.write_str("contain"),
241 MinContent(vp) => {
242 vp.to_css(dest)?;
243 dest.write_str("min-content")
244 }
245 MaxContent(vp) => {
246 vp.to_css(dest)?;
247 dest.write_str("max-content")
248 }
249 FitContent(vp) => {
250 vp.to_css(dest)?;
251 dest.write_str("fit-content")
252 }
253 Stretch(vp) => match *vp {
254 VendorPrefix::None => dest.write_str("stretch"),
255 VendorPrefix::WebKit => dest.write_str("-webkit-fill-available"),
256 VendorPrefix::Moz => dest.write_str("-moz-available"),
257 _ => unreachable!(),
258 },
259 FitContentFunction(l) => {
260 dest.write_str("fit-content(")?;
261 l.to_css(dest)?;
262 dest.write_str(")")
263 }
264 LengthPercentage(l) => l.to_css(dest),
265 }
266 }
267}
268
269impl IsCompatible for MaxSize {
270 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
271 use MaxSize::*;
272 match self {
273 LengthPercentage(l) => l.is_compatible(browsers),
274 MinContent(..) => Feature::MinContentSize.is_compatible(browsers),
275 MaxContent(..) => Feature::MaxContentSize.is_compatible(browsers),
276 FitContent(..) => Feature::FitContentSize.is_compatible(browsers),
277 FitContentFunction(l) => {
278 Feature::FitContentFunctionSize.is_compatible(browsers) && l.is_compatible(browsers)
279 }
280 Stretch(vp) => match *vp {
281 VendorPrefix::None => Feature::StretchSize,
282 VendorPrefix::WebKit => Feature::WebkitFillAvailableSize,
283 VendorPrefix::Moz => Feature::MozAvailableSize,
284 _ => return false,
285 }
286 .is_compatible(browsers),
287 Contain => false, None => true,
289 }
290 }
291}
292
293fn parse_fit_content<'i, 't>(
294 input: &mut Parser<'i, 't>,
295) -> Result<LengthPercentage, ParseError<'i, ParserError<'i>>> {
296 input.expect_function_matching("fit-content")?;
297 input.parse_nested_block(|input| LengthPercentage::parse(input))
298}
299
300enum_property! {
301 pub enum BoxSizing {
303 ContentBox,
305 BorderBox,
307 }
308}
309
310#[derive(Debug, Clone, PartialEq)]
312#[cfg_attr(feature = "visitor", derive(Visit))]
313#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
314#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
315#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
316pub struct AspectRatio {
317 pub auto: bool,
319 pub ratio: Option<Ratio>,
321}
322
323impl<'i> Parse<'i> for AspectRatio {
324 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
325 let location = input.current_source_location();
326 let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
327 let ratio = input.try_parse(Ratio::parse);
328 if auto.is_err() {
329 auto = input.try_parse(|i| i.expect_ident_matching("auto"));
330 }
331 if auto.is_err() && ratio.is_err() {
332 return Err(location.new_custom_error(ParserError::InvalidValue));
333 }
334
335 Ok(AspectRatio {
336 auto: auto.is_ok(),
337 ratio: ratio.ok(),
338 })
339 }
340}
341
342impl ToCss for AspectRatio {
343 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
344 where
345 W: std::fmt::Write,
346 {
347 if self.auto {
348 dest.write_str("auto")?;
349 }
350
351 if let Some(ratio) = &self.ratio {
352 if self.auto {
353 dest.write_char(' ')?;
354 }
355 ratio.to_css(dest)?;
356 }
357
358 Ok(())
359 }
360}
361
362property_bitflags! {
363 #[derive(Default)]
364 struct SizeProperty: u16 {
365 const Width = 1 << 0;
366 const Height = 1 << 1;
367 const MinWidth = 1 << 2;
368 const MinHeight = 1 << 3;
369 const MaxWidth = 1 << 4;
370 const MaxHeight = 1 << 5;
371 const BlockSize = 1 << 6;
372 const InlineSize = 1 << 7;
373 const MinBlockSize = 1 << 8;
374 const MinInlineSize = 1 << 9;
375 const MaxBlockSize = 1 << 10;
376 const MaxInlineSize = 1 << 11;
377 }
378}
379
380#[derive(Default)]
381pub(crate) struct SizeHandler<'i> {
382 width: Option<Size>,
383 height: Option<Size>,
384 min_width: Option<Size>,
385 min_height: Option<Size>,
386 max_width: Option<MaxSize>,
387 max_height: Option<MaxSize>,
388 block_size: Option<Size>,
389 inline_size: Option<Size>,
390 min_block_size: Option<Size>,
391 min_inline_size: Option<Size>,
392 max_block_size: Option<MaxSize>,
393 max_inline_size: Option<MaxSize>,
394 unparsed: HashMap<PropertyId<'i>, Property<'i>>,
395 has_any: bool,
396 flushed_properties: SizeProperty,
397 category: PropertyCategory,
398}
399
400impl<'i> PropertyHandler<'i> for SizeHandler<'i> {
401 fn handle_property(
402 &mut self,
403 property: &Property<'i>,
404 dest: &mut DeclarationList<'i>,
405 context: &mut PropertyHandlerContext<'i, '_>,
406 ) -> bool {
407 let logical_supported = !context.should_compile_logical(Feature::LogicalSize);
408
409 macro_rules! property {
410 ($prop: ident, $prop_id: ident,$val: ident, $category: ident) => {{
411 if PropertyCategory::$category != self.category || ((self.$prop.is_some() || self.unparsed.contains_key(&PropertyId::$prop_id)) && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets))) {
415 self.flush(dest, context);
416 }
417
418 self.$prop = Some($val.clone());
419 self.category = PropertyCategory::$category;
420 self.has_any = true;
421 }};
422 }
423
424 match property {
425 Property::Width(v) => property!(width, Width, v, Physical),
426 Property::Height(v) => property!(height, Height, v, Physical),
427 Property::MinWidth(v) => property!(min_width, MinWidth, v, Physical),
428 Property::MinHeight(v) => property!(min_height, MinHeight, v, Physical),
429 Property::MaxWidth(v) => property!(max_width, MaxWidth, v, Physical),
430 Property::MaxHeight(v) => property!(max_height, MaxHeight, v, Physical),
431 Property::BlockSize(size) => property!(block_size, BlockSize, size, Logical),
432 Property::MinBlockSize(size) => property!(min_block_size, MinBlockSize, size, Logical),
433 Property::MaxBlockSize(size) => property!(max_block_size, MaxBlockSize, size, Logical),
434 Property::InlineSize(size) => property!(inline_size, InlineSize, size, Logical),
435 Property::MinInlineSize(size) => property!(min_inline_size, MinInlineSize, size, Logical),
436 Property::MaxInlineSize(size) => property!(max_inline_size, MaxInlineSize, size, Logical),
437 Property::Unparsed(unparsed) => {
438 macro_rules! physical_unparsed {
439 ($prop: ident) => {{
440 self.$prop = None;
442
443 self.has_any = true;
444 self.unparsed.insert(unparsed.property_id.clone(), property.clone());
445 }};
446 }
447
448 macro_rules! logical_unparsed {
449 ($prop: ident, $physical: ident) => {{
450 self.$prop = None;
452 self.has_any = true;
453 if logical_supported {
454 self.unparsed.insert(unparsed.property_id.clone(), property.clone());
455 } else {
456 self.unparsed.insert(
457 unparsed.property_id.clone(),
458 Property::Unparsed(unparsed.with_property_id(PropertyId::$physical)),
459 );
460 }
461 }};
462 }
463
464 match &unparsed.property_id {
465 PropertyId::Width => physical_unparsed!(width),
466 PropertyId::Height => physical_unparsed!(height),
467 PropertyId::MinWidth => physical_unparsed!(min_width),
468 PropertyId::MaxWidth => physical_unparsed!(max_width),
469 PropertyId::MinHeight => physical_unparsed!(min_height),
470 PropertyId::MaxHeight => physical_unparsed!(max_height),
471 PropertyId::BlockSize => logical_unparsed!(block_size, Height),
472 PropertyId::MinBlockSize => logical_unparsed!(min_block_size, MinHeight),
473 PropertyId::MaxBlockSize => logical_unparsed!(max_block_size, MaxHeight),
474 PropertyId::InlineSize => logical_unparsed!(inline_size, Width),
475 PropertyId::MinInlineSize => logical_unparsed!(min_inline_size, MinWidth),
476 PropertyId::MaxInlineSize => logical_unparsed!(max_inline_size, MaxWidth),
477 _ => return false,
478 }
479 }
480 _ => return false,
481 }
482
483 true
484 }
485
486 fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
487 self.flush(dest, context);
488 self.flushed_properties = SizeProperty::empty();
489 }
490}
491
492impl<'i> SizeHandler<'i> {
493 fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
494 if !self.has_any {
495 return;
496 }
497
498 self.has_any = false;
499 let logical_supported = !context.should_compile_logical(Feature::LogicalSize);
500
501 macro_rules! prefix {
502 ($prop: ident, $size: ident, $feature: ident) => {
503 if !self.flushed_properties.contains(SizeProperty::$prop) {
504 let prefixes =
505 context.targets.prefixes(VendorPrefix::None, crate::prefixes::Feature::$feature) - VendorPrefix::None;
506 for prefix in prefixes {
507 dest.push(Property::$prop($size::$feature(prefix)));
508 }
509 }
510 };
511 }
512
513 macro_rules! property {
514 ($prop: ident, $prop_id: ident, $val: ident, $size: ident) => {{
515 if let Some(val) = std::mem::take(&mut self.$val) {
516 match val {
517 $size::Stretch(VendorPrefix::None) => prefix!($prop, $size, Stretch),
518 $size::MinContent(VendorPrefix::None) => prefix!($prop, $size, MinContent),
519 $size::MaxContent(VendorPrefix::None) => prefix!($prop, $size, MaxContent),
520 $size::FitContent(VendorPrefix::None) => prefix!($prop, $size, FitContent),
521 _ => {}
522 }
523 dest.push(Property::$prop(val.clone()));
524 self.flushed_properties.insert(SizeProperty::$prop);
525 } else {
526 if let Some(property) = self.unparsed.remove(&PropertyId::$prop_id) {
527 dest.push(property);
528 self.flushed_properties.insert(SizeProperty::$prop);
529 }
530 }
531 }};
532 }
533
534 macro_rules! logical {
535 ($prop: ident, $val: ident, $physical: ident, $size: ident) => {
536 if logical_supported {
537 property!($prop, $prop, $val, $size);
538 } else {
539 property!($physical, $prop, $val, $size);
540 }
541 };
542 }
543
544 property!(Width, Width, width, Size);
545 property!(MinWidth, MinWidth, min_width, Size);
546 property!(MaxWidth, MaxWidth, max_width, MaxSize);
547 property!(Height, Height, height, Size);
548 property!(MinHeight, MinHeight, min_height, Size);
549 property!(MaxHeight, MaxHeight, max_height, MaxSize);
550 logical!(BlockSize, block_size, Height, Size);
551 logical!(MinBlockSize, min_block_size, MinHeight, Size);
552 logical!(MaxBlockSize, max_block_size, MaxHeight, MaxSize);
553 logical!(InlineSize, inline_size, Width, Size);
554 logical!(MinInlineSize, min_inline_size, MinWidth, Size);
555 logical!(MaxInlineSize, max_inline_size, MaxWidth, MaxSize);
556 }
557}