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, shorthand_property};
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 => paste::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
332shorthand_property! {
333 pub struct ListStyle<'i> {
335 #[cfg_attr(feature = "serde", serde(borrow))]
337 list_style_type: ListStyleType(ListStyleType<'i>),
338 image: ListStyleImage(Image<'i>),
340 position: ListStylePosition(ListStylePosition),
342 }
343}
344
345impl<'i> FallbackValues for ListStyle<'i> {
346 fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
347 self
348 .image
349 .get_fallbacks(targets)
350 .into_iter()
351 .map(|image| ListStyle { image, ..self.clone() })
352 .collect()
353 }
354}
355
356shorthand_handler!(ListStyleHandler -> ListStyle<'i> fallbacks: true {
357 image: ListStyleImage(Image<'i>, fallback: true, image: true),
358 list_style_type: ListStyleType(ListStyleType<'i>),
359 position: ListStylePosition(ListStylePosition),
360});