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