1use crate::derives::*;
10use crate::error_reporting::ContextualParseError;
11use crate::parser::{Parse, ParserContext};
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::values::specified::Integer;
14use crate::values::{AtomString, CustomIdent};
15use crate::Atom;
16use cssparser::{
17 ascii_case_insensitive_phf_map, match_ignore_ascii_case, CowRcStr, Parser, RuleBodyParser,
18 SourceLocation, Token,
19};
20use std::fmt::{self, Write};
21use std::mem;
22use std::num::Wrapping;
23use style_traits::{
24 Comma, CssStringWriter, CssWriter, KeywordsCollectFn, OneOrMoreSeparated, ParseError,
25 SpecifiedValueInfo, StyleParseErrorKind, ToCss,
26};
27
28pub use crate::properties::counter_style::{DescriptorId, DescriptorParser, Descriptors};
29
30#[allow(missing_docs)]
32#[derive(
33 Clone,
34 Copy,
35 Debug,
36 Deserialize,
37 Eq,
38 MallocSizeOf,
39 Parse,
40 PartialEq,
41 Serialize,
42 ToComputedValue,
43 ToCss,
44 ToResolvedValue,
45 ToShmem,
46)]
47#[repr(u8)]
48pub enum SymbolsType {
49 Cyclic,
50 Numeric,
51 Alphabetic,
52 Symbolic,
53 Fixed,
54}
55
56#[derive(
61 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
62)]
63#[repr(u8)]
64pub enum CounterStyle {
65 None,
67 Name(CustomIdent),
69 #[css(function)]
71 Symbols {
72 #[css(skip_if = "is_symbolic")]
74 ty: SymbolsType,
75 symbols: Symbols,
77 },
78 String(AtomString),
80}
81
82#[inline]
83fn is_symbolic(symbols_type: &SymbolsType) -> bool {
84 *symbols_type == SymbolsType::Symbolic
85}
86
87impl CounterStyle {
88 pub fn disc() -> Self {
90 CounterStyle::Name(CustomIdent(atom!("disc")))
91 }
92
93 pub fn decimal() -> Self {
95 CounterStyle::Name(CustomIdent(atom!("decimal")))
96 }
97
98 #[inline]
100 pub fn is_bullet(&self) -> bool {
101 match self {
102 CounterStyle::Name(CustomIdent(name)) => {
103 name == &atom!("disc")
104 || name == &atom!("circle")
105 || name == &atom!("square")
106 || name == &atom!("disclosure-closed")
107 || name == &atom!("disclosure-open")
108 },
109 _ => false,
110 }
111 }
112
113 pub fn is_name(&self, name: &Atom) -> bool {
115 match *self {
116 CounterStyle::Name(CustomIdent(ref n)) => n == name,
117 _ => false,
118 }
119 }
120}
121
122bitflags! {
123 #[derive(Clone, Copy)]
124 pub struct CounterStyleParsingFlags: u8 {
126 const ALLOW_NONE = 1 << 0;
128 const ALLOW_STRING = 1 << 1;
130 }
131}
132
133impl CounterStyle {
134 pub fn parse(
136 context: &ParserContext,
137 input: &mut Parser,
138 flags: CounterStyleParsingFlags,
139 ) -> Result<Self, ParseError> {
140 use self::CounterStyleParsingFlags as Flags;
141 match input.next()? {
142 Token::QuotedString(string) if flags.intersects(Flags::ALLOW_STRING) => {
143 Ok(Self::String(AtomString::from(string.as_ref())))
144 },
145 Token::Ident(ident) => {
146 if flags.intersects(Flags::ALLOW_NONE) && ident.eq_ignore_ascii_case("none") {
147 return Ok(Self::None);
148 }
149 Ok(Self::Name(counter_style_name_from_ident(ident)?))
150 },
151 Token::Function(name) if name.eq_ignore_ascii_case("symbols") => {
152 input.parse_nested_block(|input| {
153 let symbols_type = input
154 .try_parse(SymbolsType::parse)
155 .unwrap_or(SymbolsType::Symbolic);
156 let symbols = Symbols::parse(context, input)?;
157 if (symbols_type == SymbolsType::Alphabetic
160 || symbols_type == SymbolsType::Numeric)
161 && symbols.0.len() < 2
162 {
163 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
164 }
165 if symbols.0.iter().any(|sym| !sym.is_allowed_in_symbols()) {
167 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
168 }
169 Ok(Self::Symbols {
170 ty: symbols_type,
171 symbols,
172 })
173 })
174 },
175 _ => Err(ParseError::unexpected_token()),
176 }
177 }
178}
179
180impl SpecifiedValueInfo for CounterStyle {
181 fn collect_completion_keywords(f: KeywordsCollectFn) {
182 macro_rules! predefined {
188 ($($name:expr,)+) => {
189 f(&["symbols", "none", $($name,)+])
190 }
191 }
192 include!("predefined.rs");
193 }
194}
195
196fn parse_counter_style_name(input: &mut Parser) -> Result<CustomIdent, ParseError> {
197 let ident = input.expect_ident()?;
198 counter_style_name_from_ident(ident)
199}
200
201fn counter_style_name_from_ident<'i>(ident: &CowRcStr<'i>) -> Result<CustomIdent, ParseError> {
203 macro_rules! predefined {
204 ($($name: tt,)+) => {{
205 ascii_case_insensitive_phf_map! {
206 predefined -> Atom = {
207 $(
208 $name => atom!($name),
209 )+
210 }
211 }
212
213 if let Some(lower_case) = predefined::get(&ident) {
215 Ok(CustomIdent(lower_case.clone()))
216 } else {
217 CustomIdent::from_ident(ident, &["none"])
219 }
220 }}
221 }
222 include!("predefined.rs")
223}
224
225fn is_valid_name_definition(ident: &CustomIdent) -> bool {
226 ident.0 != atom!("decimal")
227 && ident.0 != atom!("disc")
228 && ident.0 != atom!("circle")
229 && ident.0 != atom!("square")
230 && ident.0 != atom!("disclosure-closed")
231 && ident.0 != atom!("disclosure-open")
232}
233
234pub fn parse_counter_style_name_definition(input: &mut Parser) -> Result<CustomIdent, ParseError> {
236 parse_counter_style_name(input).and_then(|ident| {
237 if !is_valid_name_definition(&ident) {
238 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
239 } else {
240 Ok(ident)
241 }
242 })
243}
244
245#[derive(Clone, Debug, ToShmem)]
247pub struct CounterStyleRule {
248 name: CustomIdent,
249 generation: Wrapping<u32>,
250 descriptors: Descriptors,
251 pub source_location: SourceLocation,
253}
254
255pub fn parse_counter_style_body(
257 name: CustomIdent,
258 context: &ParserContext,
259 input: &mut Parser,
260 location: SourceLocation,
261) -> Result<CounterStyleRule, ParseError> {
262 let start = input.current_source_location();
263 let mut rule = CounterStyleRule::empty(name, location);
264 {
265 let mut parser = DescriptorParser {
266 context,
267 descriptors: &mut rule.descriptors,
268 };
269 let iter = RuleBodyParser::new(input, &mut parser);
270 for declaration in iter {
271 if let Err((error, slice, location)) = declaration {
272 let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(
273 slice, error,
274 );
275 context.log_css_error(location, error)
276 }
277 }
278 }
279 let error = match *rule.resolved_system() {
280 ref system @ System::Cyclic
281 | ref system @ System::Fixed { .. }
282 | ref system @ System::Symbolic
283 | ref system @ System::Alphabetic
284 | ref system @ System::Numeric
285 if rule.descriptors.symbols.is_none() =>
286 {
287 let system = system.to_css_string();
288 Some(ContextualParseError::InvalidCounterStyleWithoutSymbols(
289 system,
290 ))
291 },
292 ref system @ System::Alphabetic | ref system @ System::Numeric
293 if rule.descriptors.symbols.as_ref().unwrap().0.len() < 2 =>
294 {
295 let system = system.to_css_string();
296 Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols(
297 system,
298 ))
299 },
300 System::Additive if rule.descriptors.additive_symbols.is_none() => {
301 Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols)
302 },
303 System::Extends(_) if rule.descriptors.symbols.is_some() => {
304 Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols)
305 },
306 System::Extends(_) if rule.descriptors.additive_symbols.is_some() => {
307 Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols)
308 },
309 _ => None,
310 };
311 if let Some(error) = error {
312 context.log_css_error(start, error);
313 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
314 } else {
315 Ok(rule)
316 }
317}
318
319impl ToCssWithGuard for CounterStyleRule {
320 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
321 dest.write_str("@counter-style ")?;
322 self.name.to_css(&mut CssWriter::new(dest))?;
323 dest.write_str(" { ")?;
324 self.descriptors.to_css(&mut CssWriter::new(dest))?;
325 dest.write_char('}')
326 }
327}
328
329impl CounterStyleRule {
332 fn empty(name: CustomIdent, source_location: SourceLocation) -> Self {
333 Self {
334 name,
335 generation: Wrapping(0),
336 descriptors: Descriptors::default(),
337 source_location,
338 }
339 }
340
341 pub fn descriptors(&self) -> &Descriptors {
344 &self.descriptors
345 }
346
347 pub fn set_descriptor(
349 &mut self,
350 id: DescriptorId,
351 context: &ParserContext,
352 input: &mut Parser,
353 ) -> Result<bool, ParseError> {
354 if id == DescriptorId::AdditiveSymbols
357 && matches!(*self.resolved_system(), System::Extends(..))
358 {
359 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
361 }
362 let changed = match id {
363 DescriptorId::System => {
364 let system = input.parse_entirely(|i| System::parse(context, i))?;
365 if !self.check_system(&system) {
366 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
367 }
368 let new = Some(system);
369 if self.descriptors.system == new {
370 return Ok(false);
371 }
372 self.descriptors.system = new;
373 true
374 },
375 DescriptorId::Symbols => {
376 let symbols = input.parse_entirely(|i| Symbols::parse(context, i))?;
377 if !self.check_symbols(&symbols) {
378 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
379 }
380 let new = Some(symbols);
381 if self.descriptors.symbols == new {
382 return Ok(false);
383 }
384 self.descriptors.symbols = new;
385 true
386 },
387 _ => self.descriptors.set(id, context, input)?,
388 };
389 if changed {
390 self.generation += Wrapping(1);
391 }
392 Ok(changed)
393 }
394
395 fn check_system(&self, value: &System) -> bool {
398 mem::discriminant(self.resolved_system()) == mem::discriminant(value)
399 }
400
401 fn check_symbols(&self, value: &Symbols) -> bool {
402 match *self.resolved_system() {
403 System::Numeric | System::Alphabetic => value.0.len() >= 2,
405 System::Extends(_) => false,
407 _ => true,
408 }
409 }
410
411 pub fn name(&self) -> &CustomIdent {
413 &self.name
414 }
415
416 pub fn set_name(&mut self, name: CustomIdent) {
419 debug_assert!(is_valid_name_definition(&name));
420 self.name = name;
421 }
422
423 pub fn generation(&self) -> u32 {
425 self.generation.0
426 }
427
428 pub fn resolved_system(&self) -> &System {
431 match self.descriptors.system {
432 Some(ref system) => system,
433 None => &System::Symbolic,
434 }
435 }
436}
437
438#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
440pub enum System {
441 Cyclic,
443 Numeric,
445 Alphabetic,
447 Symbolic,
449 Additive,
451 Fixed {
453 first_symbol_value: Option<Integer>,
455 },
456 Extends(CustomIdent),
458}
459
460impl Parse for System {
461 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
462 try_match_ident_ignore_ascii_case! { input,
463 "cyclic" => Ok(System::Cyclic),
464 "numeric" => Ok(System::Numeric),
465 "alphabetic" => Ok(System::Alphabetic),
466 "symbolic" => Ok(System::Symbolic),
467 "additive" => Ok(System::Additive),
468 "fixed" => {
469 let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok();
470 Ok(System::Fixed { first_symbol_value })
471 },
472 "extends" => {
473 let other = parse_counter_style_name(input)?;
474 Ok(System::Extends(other))
475 },
476 }
477 }
478}
479
480impl ToCss for System {
481 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
482 where
483 W: Write,
484 {
485 match self {
486 System::Cyclic => dest.write_str("cyclic"),
487 System::Numeric => dest.write_str("numeric"),
488 System::Alphabetic => dest.write_str("alphabetic"),
489 System::Symbolic => dest.write_str("symbolic"),
490 System::Additive => dest.write_str("additive"),
491 System::Fixed { first_symbol_value } => {
492 if let Some(value) = first_symbol_value {
493 dest.write_str("fixed ")?;
494 value.to_css(dest)
495 } else {
496 dest.write_str("fixed")
497 }
498 },
499 System::Extends(other) => {
500 dest.write_str("extends ")?;
501 other.to_css(dest)
502 },
503 }
504 }
505}
506
507#[derive(
509 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
510)]
511#[repr(u8)]
512pub enum Symbol {
513 String(crate::OwnedStr),
515 Ident(CustomIdent),
517 }
521
522impl Parse for Symbol {
523 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
524 match *input.next()? {
525 Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())),
526 Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(s, &[])?)),
527 _ => Err(ParseError::unexpected_token()),
528 }
529 }
530}
531
532impl Symbol {
533 pub fn is_allowed_in_symbols(&self) -> bool {
535 match self {
536 &Symbol::Ident(_) => false,
538 _ => true,
539 }
540 }
541}
542
543#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
545pub struct Negative(pub Symbol, pub Option<Symbol>);
546
547impl Parse for Negative {
548 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
549 Ok(Negative(
550 Symbol::parse(context, input)?,
551 input.try_parse(|input| Symbol::parse(context, input)).ok(),
552 ))
553 }
554}
555
556#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
558pub struct CounterRange {
559 pub start: CounterBound,
561 pub end: CounterBound,
563}
564
565#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
569#[css(comma)]
570pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice<CounterRange>);
571
572#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
574pub enum CounterBound {
575 Integer(Integer),
577 Infinite,
579}
580
581impl Parse for CounterRanges {
582 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
583 if input
584 .try_parse(|input| input.expect_ident_matching("auto"))
585 .is_ok()
586 {
587 return Ok(CounterRanges(Default::default()));
588 }
589
590 let ranges = input.parse_comma_separated(|input| {
591 let start = parse_bound(context, input)?;
592 let end = parse_bound(context, input)?;
593 if let (CounterBound::Integer(s), CounterBound::Integer(e)) = (&start, &end) {
594 let s = s.resolve();
597 let e = e.resolve();
598 if s.is_none() || e.is_none() || s.unwrap() > e.unwrap() {
599 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
600 }
601 }
602 Ok(CounterRange { start, end })
603 })?;
604
605 Ok(CounterRanges(ranges.into()))
606 }
607}
608
609fn parse_bound(context: &ParserContext, input: &mut Parser) -> Result<CounterBound, ParseError> {
610 if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) {
611 return Ok(CounterBound::Integer(integer));
612 }
613 input.expect_ident_matching("infinite")?;
614 Ok(CounterBound::Infinite)
615}
616
617#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
619pub struct Pad(pub Integer, pub Symbol);
620
621impl Parse for Pad {
622 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
623 let pad_with = input.try_parse(|input| Symbol::parse(context, input));
624 let min_length = Integer::parse_non_negative(context, input)?;
625 if min_length.resolve().is_none() {
628 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
629 }
630 let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?;
631 Ok(Pad(min_length, pad_with))
632 }
633}
634
635#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
637pub struct Fallback(pub CustomIdent);
638
639impl Parse for Fallback {
640 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
641 Ok(Fallback(parse_counter_style_name(input)?))
642 }
643}
644
645#[derive(
647 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem,
648)]
649#[repr(C)]
650pub struct Symbols(
651 #[css(iterable)]
652 #[ignore_malloc_size_of = "Arc"]
653 pub crate::ArcSlice<Symbol>,
654);
655
656impl Parse for Symbols {
657 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
658 let mut symbols = smallvec::SmallVec::<[_; 5]>::new();
659 while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) {
660 symbols.push(s);
661 }
662 if symbols.is_empty() {
663 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
664 }
665 Ok(Symbols(crate::ArcSlice::from_iter(symbols.drain(..))))
666 }
667}
668
669#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
671#[css(comma)]
672pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice<AdditiveTuple>);
673
674impl Parse for AdditiveSymbols {
675 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
676 let tuples = Vec::<AdditiveTuple>::parse(context, input)?;
677 if tuples.iter().any(|t| t.weight.resolve().is_none()) {
678 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
679 }
680 if tuples
682 .windows(2)
683 .any(|window| window[0].weight.get() <= window[1].weight.get())
684 {
685 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
686 }
687 Ok(AdditiveSymbols(tuples.into()))
688 }
689}
690
691#[derive(Clone, Debug, MallocSizeOf, ToCss, ToShmem, PartialEq)]
693pub struct AdditiveTuple {
694 pub weight: Integer,
696 pub symbol: Symbol,
698}
699
700impl OneOrMoreSeparated for AdditiveTuple {
701 type S = Comma;
702}
703
704impl Parse for AdditiveTuple {
705 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
706 let symbol = input.try_parse(|input| Symbol::parse(context, input));
707 let weight = Integer::parse_non_negative(context, input)?;
708 let symbol = symbol.or_else(|_| Symbol::parse(context, input))?;
709 Ok(Self { weight, symbol })
710 }
711}
712
713#[derive(Clone, Debug, MallocSizeOf, ToCss, PartialEq, ToShmem)]
715pub enum SpeakAs {
716 Auto,
718 Bullets,
720 Numbers,
722 Words,
724 Other(CustomIdent),
728}
729
730impl Parse for SpeakAs {
731 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
732 let mut is_spell_out = false;
733 let result = input.try_parse(|input| {
734 let ident = input.expect_ident().map_err(|_| ())?;
735 match_ignore_ascii_case! { ident,
736 "auto" => Ok(SpeakAs::Auto),
737 "bullets" => Ok(SpeakAs::Bullets),
738 "numbers" => Ok(SpeakAs::Numbers),
739 "words" => Ok(SpeakAs::Words),
740 "spell-out" => {
741 is_spell_out = true;
742 Err(())
743 },
744 _ => Err(()),
745 }
746 });
747 if is_spell_out {
748 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
751 }
752 result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?)))
753 }
754}