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