1use super::{Property, PropertyId};
4use crate::context::PropertyHandlerContext;
5use crate::declaration::{DeclarationBlock, DeclarationList};
6use crate::error::{ParserError, PrinterError};
7use crate::macros::{define_shorthand, enum_property, shorthand_handler};
8use crate::printer::Printer;
9use crate::targets::{Browsers, Targets};
10use crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
11use crate::values::string::CSSString;
12use crate::values::{ident::CustomIdent, image::Image};
13#[cfg(feature = "visitor")]
14use crate::visitor::Visit;
15use cssparser::*;
16
17#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
19#[cfg_attr(feature = "visitor", derive(Visit))]
20#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
21#[cfg_attr(
22 feature = "serde",
23 derive(serde::Serialize, serde::Deserialize),
24 serde(tag = "type", content = "value", rename_all = "kebab-case")
25)]
26#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
27pub enum ListStyleType<'i> {
28 None,
30 #[cfg_attr(feature = "serde", serde(borrow))]
32 String(CSSString<'i>),
33 CounterStyle(CounterStyle<'i>),
35}
36
37impl Default for ListStyleType<'_> {
38 fn default() -> Self {
39 ListStyleType::CounterStyle(CounterStyle::Predefined(PredefinedCounterStyle::Disc))
40 }
41}
42
43impl IsCompatible for ListStyleType<'_> {
44 fn is_compatible(&self, browsers: Browsers) -> bool {
45 match self {
46 ListStyleType::CounterStyle(c) => c.is_compatible(browsers),
47 ListStyleType::String(..) => crate::compat::Feature::StringListStyleType.is_compatible(browsers),
48 ListStyleType::None => true,
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq)]
55#[cfg_attr(feature = "visitor", derive(Visit))]
56#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
57#[cfg_attr(
58 feature = "serde",
59 derive(serde::Serialize, serde::Deserialize),
60 serde(tag = "type", rename_all = "kebab-case")
61)]
62#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
63pub enum CounterStyle<'i> {
64 #[cfg_attr(
66 feature = "serde",
67 serde(with = "crate::serialization::ValueWrapper::<PredefinedCounterStyle>")
68 )]
69 Predefined(PredefinedCounterStyle),
70 #[cfg_attr(
72 feature = "serde",
73 serde(borrow, with = "crate::serialization::ValueWrapper::<CustomIdent>")
74 )]
75 Name(CustomIdent<'i>),
76 Symbols {
78 #[cfg_attr(feature = "serde", serde(default))]
80 system: SymbolsType,
81 symbols: Vec<Symbol<'i>>,
83 },
84}
85
86macro_rules! counter_styles {
87 (
88 $(#[$outer:meta])*
89 $vis:vis enum $name:ident {
90 $(
91 $(#[$meta: meta])*
92 $id: ident,
93 )+
94 }
95 ) => {
96 enum_property! {
97 #[allow(missing_docs)]
99 pub enum PredefinedCounterStyle {
100 $(
101 $(#[$meta])*
102 $id,
103 )+
104 }
105 }
106
107 impl IsCompatible for PredefinedCounterStyle {
108 fn is_compatible(&self, browsers: Browsers) -> bool {
109 match self {
110 $(
111 PredefinedCounterStyle::$id => pastey::paste! {
112 crate::compat::Feature::[<$id ListStyleType>].is_compatible(browsers)
113 },
114 )+
115 }
116 }
117 }
118 };
119}
120
121counter_styles! {
122 #[allow(missing_docs)]
124 pub enum PredefinedCounterStyle {
125 Decimal,
127 DecimalLeadingZero,
128 ArabicIndic,
129 Armenian,
130 UpperArmenian,
131 LowerArmenian,
132 Bengali,
133 Cambodian,
134 Khmer,
135 CjkDecimal,
136 Devanagari,
137 Georgian,
138 Gujarati,
139 Gurmukhi,
140 Hebrew,
141 Kannada,
142 Lao,
143 Malayalam,
144 Mongolian,
145 Myanmar,
146 Oriya,
147 Persian,
148 LowerRoman,
149 UpperRoman,
150 Tamil,
151 Telugu,
152 Thai,
153 Tibetan,
154
155 LowerAlpha,
157 LowerLatin,
158 UpperAlpha,
159 UpperLatin,
160 LowerGreek,
161 Hiragana,
162 HiraganaIroha,
163 Katakana,
164 KatakanaIroha,
165
166 Disc,
168 Circle,
169 Square,
170 DisclosureOpen,
171 DisclosureClosed,
172
173 CjkEarthlyBranch,
175 CjkHeavenlyStem,
176
177 JapaneseInformal,
179 JapaneseFormal,
180 KoreanHangulFormal,
181 KoreanHanjaInformal,
182 KoreanHanjaFormal,
183 SimpChineseInformal,
184 SimpChineseFormal,
185 TradChineseInformal,
186 TradChineseFormal,
187 EthiopicNumeric,
188 }
189}
190
191impl<'i> Parse<'i> for CounterStyle<'i> {
192 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
193 if let Ok(predefined) = input.try_parse(PredefinedCounterStyle::parse) {
194 return Ok(CounterStyle::Predefined(predefined));
195 }
196
197 if input.try_parse(|input| input.expect_function_matching("symbols")).is_ok() {
198 return input.parse_nested_block(|input| {
199 let t = input.try_parse(SymbolsType::parse).unwrap_or_default();
200
201 let mut symbols = Vec::new();
202 while let Ok(s) = input.try_parse(Symbol::parse) {
203 symbols.push(s);
204 }
205
206 Ok(CounterStyle::Symbols { system: t, symbols })
207 });
208 }
209
210 let name = CustomIdent::parse(input)?;
211 Ok(CounterStyle::Name(name))
212 }
213}
214
215impl ToCss for CounterStyle<'_> {
216 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
217 where
218 W: std::fmt::Write,
219 {
220 match self {
221 CounterStyle::Predefined(style) => style.to_css(dest),
222 CounterStyle::Name(name) => {
223 if let Some(css_module) = &mut dest.css_module {
224 css_module.reference(&name.0, dest.loc.source_index)
225 }
226 name.to_css(dest)
227 }
228 CounterStyle::Symbols { system: t, symbols } => {
229 dest.write_str("symbols(")?;
230 let mut needs_space = false;
231 if *t != SymbolsType::Symbolic {
232 t.to_css(dest)?;
233 needs_space = true;
234 }
235
236 for symbol in symbols {
237 if needs_space {
238 dest.write_char(' ')?;
239 }
240 symbol.to_css(dest)?;
241 needs_space = true;
242 }
243 dest.write_char(')')
244 }
245 }
246 }
247}
248
249impl IsCompatible for CounterStyle<'_> {
250 fn is_compatible(&self, browsers: Browsers) -> bool {
251 match self {
252 CounterStyle::Name(..) => true,
253 CounterStyle::Predefined(p) => p.is_compatible(browsers),
254 CounterStyle::Symbols { .. } => crate::compat::Feature::SymbolsListStyleType.is_compatible(browsers),
255 }
256 }
257}
258
259enum_property! {
260 #[allow(missing_docs)]
265 pub enum SymbolsType {
266 Cyclic,
267 Numeric,
268 Alphabetic,
269 Symbolic,
270 Fixed,
271 }
272}
273
274impl Default for SymbolsType {
275 fn default() -> Self {
276 SymbolsType::Symbolic
277 }
278}
279
280#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
285#[cfg_attr(feature = "visitor", derive(Visit))]
286#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
287#[cfg_attr(
288 feature = "serde",
289 derive(serde::Serialize, serde::Deserialize),
290 serde(tag = "type", content = "value", rename_all = "kebab-case")
291)]
292#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
293pub enum Symbol<'i> {
294 #[cfg_attr(feature = "serde", serde(borrow))]
296 String(CSSString<'i>),
297 Image(Image<'i>),
299}
300
301enum_property! {
302 pub enum ListStylePosition {
304 Inside,
306 Outside,
308 }
309}
310
311impl Default for ListStylePosition {
312 fn default() -> ListStylePosition {
313 ListStylePosition::Outside
314 }
315}
316
317impl IsCompatible for ListStylePosition {
318 fn is_compatible(&self, _browsers: Browsers) -> bool {
319 true
320 }
321}
322
323enum_property! {
324 #[allow(missing_docs)]
326 pub enum MarkerSide {
327 MatchSelf,
328 MatchParent,
329 }
330}
331
332define_shorthand! {
333 pub struct ListStyle<'i> {
335 position: ListStylePosition(ListStylePosition),
337 #[cfg_attr(feature = "serde", serde(borrow))]
339 image: ListStyleImage(Image<'i>),
340 list_style_type: ListStyleType(ListStyleType<'i>),
342 }
343}
344
345impl<'i> Parse<'i> for ListStyle<'i> {
346 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
347 let mut position = None;
348 let mut image = None;
349 let mut list_style_type = None;
350 let mut nones = 0;
351
352 loop {
353 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
355 nones += 1;
356 if nones > 2 {
357 return Err(input.new_custom_error(ParserError::InvalidValue));
358 }
359 continue;
360 }
361
362 if image.is_none() {
363 if let Ok(val) = input.try_parse(Image::parse) {
364 image = Some(val);
365 continue;
366 }
367 }
368
369 if position.is_none() {
370 if let Ok(val) = input.try_parse(ListStylePosition::parse) {
371 position = Some(val);
372 continue;
373 }
374 }
375
376 if list_style_type.is_none() {
377 if let Ok(val) = input.try_parse(ListStyleType::parse) {
378 list_style_type = Some(val);
379 continue;
380 }
381 }
382
383 break;
384 }
385
386 match (nones, image, list_style_type) {
389 (2, None, None) | (1, None, None) => Ok(ListStyle {
390 position: position.unwrap_or_default(),
391 image: Image::None,
392 list_style_type: ListStyleType::None,
393 }),
394 (1, Some(image), None) => Ok(ListStyle {
395 position: position.unwrap_or_default(),
396 image,
397 list_style_type: ListStyleType::None,
398 }),
399 (1, None, Some(list_style_type)) => Ok(ListStyle {
400 position: position.unwrap_or_default(),
401 image: Image::None,
402 list_style_type,
403 }),
404 (0, image, list_style_type) => Ok(ListStyle {
405 position: position.unwrap_or_default(),
406 image: image.unwrap_or_default(),
407 list_style_type: list_style_type.unwrap_or_default(),
408 }),
409 _ => Err(input.new_custom_error(ParserError::InvalidValue)),
410 }
411 }
412}
413
414impl<'i> ToCss for ListStyle<'i> {
415 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
416 where
417 W: std::fmt::Write,
418 {
419 let mut needs_space = false;
420 if self.position != ListStylePosition::default() {
421 self.position.to_css(dest)?;
422 needs_space = true;
423 }
424
425 if self.image != Image::default() {
426 if needs_space {
427 dest.write_char(' ')?;
428 }
429 self.image.to_css(dest)?;
430 needs_space = true;
431 }
432
433 if self.list_style_type != ListStyleType::default() {
434 if needs_space {
435 dest.write_char(' ')?;
436 }
437 self.list_style_type.to_css(dest)?;
438 needs_space = true;
439 }
440
441 if !needs_space {
442 self.position.to_css(dest)?;
443 }
444
445 Ok(())
446 }
447}
448
449impl<'i> FallbackValues for ListStyle<'i> {
450 fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
451 self
452 .image
453 .get_fallbacks(targets)
454 .into_iter()
455 .map(|image| ListStyle { image, ..self.clone() })
456 .collect()
457 }
458}
459
460shorthand_handler!(ListStyleHandler -> ListStyle<'i> fallbacks: true {
461 image: ListStyleImage(Image<'i>, fallback: true, image: true),
462 list_style_type: ListStyleType(ListStyleType<'i>),
463 position: ListStylePosition(ListStylePosition),
464});