1use super::custom::UnparsedProperty;
4use super::{Property, PropertyId};
5use crate::context::PropertyHandlerContext;
6use crate::declaration::DeclarationList;
7use crate::error::{ParserError, PrinterError};
8use crate::macros::enum_property;
9use crate::prefixes::{is_flex_2009, Feature};
10use crate::printer::Printer;
11use crate::traits::{Parse, PropertyHandler, ToCss};
12use crate::vendor_prefix::VendorPrefix;
13#[cfg(feature = "visitor")]
14use crate::visitor::Visit;
15use cssparser::*;
16
17enum_property! {
18 #[allow(missing_docs)]
20 pub enum DisplayOutside {
21 Block,
22 Inline,
23 RunIn,
24 }
25}
26
27#[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", content = "vendorPrefix", rename_all = "kebab-case")
34)]
35#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
36#[allow(missing_docs)]
37pub enum DisplayInside {
38 Flow,
39 FlowRoot,
40 Table,
41 Flex(VendorPrefix),
42 Box(VendorPrefix),
43 Grid,
44 Ruby,
45}
46
47impl<'i> Parse<'i> for DisplayInside {
48 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
49 let location = input.current_source_location();
50 let ident = input.expect_ident()?;
51 match_ignore_ascii_case! { &*ident,
52 "flow" => Ok(DisplayInside::Flow),
53 "flow-root" => Ok(DisplayInside::FlowRoot),
54 "table" => Ok(DisplayInside::Table),
55 "flex" => Ok(DisplayInside::Flex(VendorPrefix::None)),
56 "-webkit-flex" => Ok(DisplayInside::Flex(VendorPrefix::WebKit)),
57 "-ms-flexbox" => Ok(DisplayInside::Flex(VendorPrefix::Ms)),
58 "-webkit-box" => Ok(DisplayInside::Box(VendorPrefix::WebKit)),
59 "-moz-box" => Ok(DisplayInside::Box(VendorPrefix::Moz)),
60 "grid" => Ok(DisplayInside::Grid),
61 "ruby" => Ok(DisplayInside::Ruby),
62 _ => Err(location.new_unexpected_token_error(
63 cssparser::Token::Ident(ident.clone())
64 ))
65 }
66 }
67}
68
69impl ToCss for DisplayInside {
70 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
71 where
72 W: std::fmt::Write,
73 {
74 match self {
75 DisplayInside::Flow => dest.write_str("flow"),
76 DisplayInside::FlowRoot => dest.write_str("flow-root"),
77 DisplayInside::Table => dest.write_str("table"),
78 DisplayInside::Flex(prefix) => {
79 prefix.to_css(dest)?;
80 if *prefix == VendorPrefix::Ms {
81 dest.write_str("flexbox")
82 } else {
83 dest.write_str("flex")
84 }
85 }
86 DisplayInside::Box(prefix) => {
87 prefix.to_css(dest)?;
88 dest.write_str("box")
89 }
90 DisplayInside::Grid => dest.write_str("grid"),
91 DisplayInside::Ruby => dest.write_str("ruby"),
92 }
93 }
94}
95
96impl DisplayInside {
97 fn is_equivalent(&self, other: &DisplayInside) -> bool {
98 match (self, other) {
99 (DisplayInside::Flex(_), DisplayInside::Flex(_)) => true,
100 (DisplayInside::Box(_), DisplayInside::Box(_)) => true,
101 (DisplayInside::Flex(_), DisplayInside::Box(_)) => true,
102 (DisplayInside::Box(_), DisplayInside::Flex(_)) => true,
103 _ => self == other,
104 }
105 }
106}
107
108#[derive(Debug, Clone, PartialEq)]
112#[cfg_attr(feature = "visitor", derive(Visit))]
113#[cfg_attr(
114 feature = "serde",
115 derive(serde::Serialize, serde::Deserialize),
116 serde(rename_all = "camelCase")
117)]
118#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
119pub struct DisplayPair {
120 pub outside: DisplayOutside,
122 pub inside: DisplayInside,
124 pub is_list_item: bool,
126}
127
128impl<'i> Parse<'i> for DisplayPair {
129 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
130 let mut list_item = false;
131 let mut outside = None;
132 let mut inside = None;
133
134 loop {
135 if input.try_parse(|input| input.expect_ident_matching("list-item")).is_ok() {
136 list_item = true;
137 continue;
138 }
139
140 if outside.is_none() {
141 if let Ok(o) = input.try_parse(DisplayOutside::parse) {
142 outside = Some(o);
143 continue;
144 }
145 }
146
147 if inside.is_none() {
148 if let Ok(i) = input.try_parse(DisplayInside::parse) {
149 inside = Some(i);
150 continue;
151 }
152 }
153
154 break;
155 }
156
157 if list_item || inside.is_some() || outside.is_some() {
158 let inside = inside.unwrap_or(DisplayInside::Flow);
159 let outside = outside.unwrap_or(match inside {
160 DisplayInside::Ruby => DisplayOutside::Inline,
164 _ => DisplayOutside::Block,
165 });
166
167 if list_item && !matches!(inside, DisplayInside::Flow | DisplayInside::FlowRoot) {
168 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
169 }
170
171 return Ok(DisplayPair {
172 outside,
173 inside,
174 is_list_item: list_item,
175 });
176 }
177
178 let location = input.current_source_location();
179 let ident = input.expect_ident()?;
180 match_ignore_ascii_case! { &*ident,
181 "inline-block" => Ok(DisplayPair {
182 outside: DisplayOutside::Inline,
183 inside: DisplayInside::FlowRoot,
184 is_list_item: false
185 }),
186 "inline-table" => Ok(DisplayPair {
187 outside: DisplayOutside::Inline,
188 inside: DisplayInside::Table,
189 is_list_item: false
190 }),
191 "inline-flex" => Ok(DisplayPair {
192 outside: DisplayOutside::Inline,
193 inside: DisplayInside::Flex(VendorPrefix::None),
194 is_list_item: false
195 }),
196 "-webkit-inline-flex" => Ok(DisplayPair {
197 outside: DisplayOutside::Inline,
198 inside: DisplayInside::Flex(VendorPrefix::WebKit),
199 is_list_item: false
200 }),
201 "-ms-inline-flexbox" => Ok(DisplayPair {
202 outside: DisplayOutside::Inline,
203 inside: DisplayInside::Flex(VendorPrefix::Ms),
204 is_list_item: false
205 }),
206 "-webkit-inline-box" => Ok(DisplayPair {
207 outside: DisplayOutside::Inline,
208 inside: DisplayInside::Box(VendorPrefix::WebKit),
209 is_list_item: false
210 }),
211 "-moz-inline-box" => Ok(DisplayPair {
212 outside: DisplayOutside::Inline,
213 inside: DisplayInside::Box(VendorPrefix::Moz),
214 is_list_item: false
215 }),
216 "inline-grid" => Ok(DisplayPair {
217 outside: DisplayOutside::Inline,
218 inside: DisplayInside::Grid,
219 is_list_item: false
220 }),
221 _ => Err(location.new_unexpected_token_error(
222 cssparser::Token::Ident(ident.clone())
223 ))
224 }
225 }
226}
227
228impl ToCss for DisplayPair {
229 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
230 where
231 W: std::fmt::Write,
232 {
233 match self {
234 DisplayPair {
235 outside: DisplayOutside::Inline,
236 inside: DisplayInside::FlowRoot,
237 is_list_item: false,
238 } => dest.write_str("inline-block"),
239 DisplayPair {
240 outside: DisplayOutside::Inline,
241 inside: DisplayInside::Table,
242 is_list_item: false,
243 } => dest.write_str("inline-table"),
244 DisplayPair {
245 outside: DisplayOutside::Inline,
246 inside: DisplayInside::Flex(prefix),
247 is_list_item: false,
248 } => {
249 prefix.to_css(dest)?;
250 if *prefix == VendorPrefix::Ms {
251 dest.write_str("inline-flexbox")
252 } else {
253 dest.write_str("inline-flex")
254 }
255 }
256 DisplayPair {
257 outside: DisplayOutside::Inline,
258 inside: DisplayInside::Box(prefix),
259 is_list_item: false,
260 } => {
261 prefix.to_css(dest)?;
262 dest.write_str("inline-box")
263 }
264 DisplayPair {
265 outside: DisplayOutside::Inline,
266 inside: DisplayInside::Grid,
267 is_list_item: false,
268 } => dest.write_str("inline-grid"),
269 DisplayPair {
270 outside,
271 inside,
272 is_list_item,
273 } => {
274 let default_outside = match inside {
275 DisplayInside::Ruby => DisplayOutside::Inline,
276 _ => DisplayOutside::Block,
277 };
278
279 let mut needs_space = false;
280 if *outside != default_outside || (*inside == DisplayInside::Flow && !*is_list_item) {
281 outside.to_css(dest)?;
282 needs_space = true;
283 }
284
285 if *inside != DisplayInside::Flow {
286 if needs_space {
287 dest.write_char(' ')?;
288 }
289 inside.to_css(dest)?;
290 needs_space = true;
291 }
292
293 if *is_list_item {
294 if needs_space {
295 dest.write_char(' ')?;
296 }
297 dest.write_str("list-item")?;
298 }
299
300 Ok(())
301 }
302 }
303 }
304}
305
306enum_property! {
307 #[allow(missing_docs)]
311 pub enum DisplayKeyword {
312 None,
313 Contents,
314 TableRowGroup,
315 TableHeaderGroup,
316 TableFooterGroup,
317 TableRow,
318 TableCell,
319 TableColumnGroup,
320 TableColumn,
321 TableCaption,
322 RubyBase,
323 RubyText,
324 RubyBaseContainer,
325 RubyTextContainer,
326 }
327}
328
329#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
331#[cfg_attr(feature = "visitor", derive(Visit))]
332#[cfg_attr(
333 feature = "serde",
334 derive(serde::Serialize, serde::Deserialize),
335 serde(tag = "type", rename_all = "kebab-case")
336)]
337#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
338#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
339pub enum Display {
340 #[cfg_attr(
342 feature = "serde",
343 serde(with = "crate::serialization::ValueWrapper::<DisplayKeyword>")
344 )]
345 Keyword(DisplayKeyword),
346 Pair(DisplayPair),
348}
349
350enum_property! {
351 pub enum Visibility {
353 Visible,
355 Hidden,
357 Collapse,
359 }
360}
361
362#[derive(Default)]
363pub(crate) struct DisplayHandler<'i> {
364 decls: Vec<Property<'i>>,
365 display: Option<Display>,
366}
367
368impl<'i> PropertyHandler<'i> for DisplayHandler<'i> {
369 fn handle_property(
370 &mut self,
371 property: &Property<'i>,
372 dest: &mut DeclarationList<'i>,
373 context: &mut PropertyHandlerContext<'i, '_>,
374 ) -> bool {
375 if let Property::Display(display) = property {
376 match (&self.display, display) {
377 (Some(Display::Pair(cur)), Display::Pair(new)) => {
378 if cur.outside == new.outside
381 && cur.is_list_item == new.is_list_item
382 && cur.inside != new.inside
383 && cur.inside.is_equivalent(&new.inside)
384 {
385 if context.targets.browsers.is_some() && new.inside == DisplayInside::Flex(VendorPrefix::None) {
389 self.decls.clear();
390 } else if context.targets.browsers.is_none() || cur.inside != DisplayInside::Flex(VendorPrefix::None) {
391 self.decls.push(Property::Display(self.display.clone().unwrap()));
392 }
393 }
394 }
395 _ => {}
396 }
397
398 self.display = Some(display.clone());
399 return true;
400 }
401
402 if matches!(
403 property,
404 Property::Unparsed(UnparsedProperty {
405 property_id: PropertyId::Display,
406 ..
407 })
408 ) {
409 self.finalize(dest, context);
410 dest.push(property.clone());
411 return true;
412 }
413
414 false
415 }
416
417 fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
418 if self.display.is_none() {
419 return;
420 }
421
422 dest.extend(self.decls.drain(..));
423
424 if let Some(display) = std::mem::take(&mut self.display) {
425 if let Display::Pair(DisplayPair {
427 inside: DisplayInside::Flex(VendorPrefix::None),
428 outside,
429 ..
430 }) = display
431 {
432 let prefixes = context.targets.prefixes(VendorPrefix::None, Feature::DisplayFlex);
433
434 if let Some(targets) = context.targets.browsers {
435 if is_flex_2009(targets) {
437 if prefixes.contains(VendorPrefix::WebKit) {
438 dest.push(Property::Display(Display::Pair(DisplayPair {
439 inside: DisplayInside::Box(VendorPrefix::WebKit),
440 outside: outside.clone(),
441 is_list_item: false,
442 })));
443 }
444
445 if prefixes.contains(VendorPrefix::Moz) {
446 dest.push(Property::Display(Display::Pair(DisplayPair {
447 inside: DisplayInside::Box(VendorPrefix::Moz),
448 outside: outside.clone(),
449 is_list_item: false,
450 })));
451 }
452 }
453 }
454
455 if prefixes.contains(VendorPrefix::WebKit) {
456 dest.push(Property::Display(Display::Pair(DisplayPair {
457 inside: DisplayInside::Flex(VendorPrefix::WebKit),
458 outside: outside.clone(),
459 is_list_item: false,
460 })));
461 }
462
463 if prefixes.contains(VendorPrefix::Ms) {
464 dest.push(Property::Display(Display::Pair(DisplayPair {
465 inside: DisplayInside::Flex(VendorPrefix::Ms),
466 outside: outside.clone(),
467 is_list_item: false,
468 })));
469 }
470 }
471
472 dest.push(Property::Display(display))
473 }
474 }
475}