mod.rs (28597B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! The [`@counter-style`][counter-style] at-rule. 6 //! 7 //! [counter-style]: https://drafts.csswg.org/css-counter-styles/ 8 9 use crate::derives::*; 10 use crate::error_reporting::ContextualParseError; 11 use crate::parser::{Parse, ParserContext}; 12 use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; 13 use crate::values::specified::Integer; 14 use crate::values::{AtomString, CustomIdent}; 15 use crate::Atom; 16 use cssparser::{ 17 ascii_case_insensitive_phf_map, match_ignore_ascii_case, CowRcStr, Parser, ParserState, 18 SourceLocation, Token, 19 }; 20 use cssparser::{ 21 AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, 22 }; 23 use selectors::parser::SelectorParseErrorKind; 24 use std::fmt::{self, Write}; 25 use std::mem; 26 use std::num::Wrapping; 27 use style_traits::{ 28 Comma, CssStringWriter, CssWriter, KeywordsCollectFn, OneOrMoreSeparated, ParseError, 29 SpecifiedValueInfo, StyleParseErrorKind, ToCss, 30 }; 31 32 /// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type 33 #[allow(missing_docs)] 34 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 35 #[derive( 36 Clone, 37 Copy, 38 Debug, 39 Eq, 40 MallocSizeOf, 41 Parse, 42 PartialEq, 43 ToComputedValue, 44 ToCss, 45 ToResolvedValue, 46 ToShmem, 47 )] 48 #[repr(u8)] 49 pub enum SymbolsType { 50 Cyclic, 51 Numeric, 52 Alphabetic, 53 Symbolic, 54 Fixed, 55 } 56 57 /// <https://drafts.csswg.org/css-counter-styles/#typedef-counter-style> 58 /// 59 /// Note that 'none' is not a valid name, but we include this (along with String) for space 60 /// efficiency when storing list-style-type. 61 #[derive( 62 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 63 )] 64 #[repr(u8)] 65 pub enum CounterStyle { 66 /// The 'none' value. 67 None, 68 /// `<counter-style-name>` 69 Name(CustomIdent), 70 /// `symbols()` 71 #[css(function)] 72 Symbols { 73 /// The <symbols-type>, or symbolic if not specified. 74 #[css(skip_if = "is_symbolic")] 75 ty: SymbolsType, 76 /// The actual symbols. 77 symbols: Symbols, 78 }, 79 /// A single string value, useful for `<list-style-type>`. 80 String(AtomString), 81 } 82 83 #[inline] 84 fn is_symbolic(symbols_type: &SymbolsType) -> bool { 85 *symbols_type == SymbolsType::Symbolic 86 } 87 88 impl CounterStyle { 89 /// disc value 90 pub fn disc() -> Self { 91 CounterStyle::Name(CustomIdent(atom!("disc"))) 92 } 93 94 /// decimal value 95 pub fn decimal() -> Self { 96 CounterStyle::Name(CustomIdent(atom!("decimal"))) 97 } 98 99 /// Is this a bullet? (i.e. `list-style-type: disc|circle|square|disclosure-closed|disclosure-open`) 100 #[inline] 101 pub fn is_bullet(&self) -> bool { 102 match self { 103 CounterStyle::Name(CustomIdent(ref name)) => { 104 name == &atom!("disc") 105 || name == &atom!("circle") 106 || name == &atom!("square") 107 || name == &atom!("disclosure-closed") 108 || name == &atom!("disclosure-open") 109 }, 110 _ => false, 111 } 112 } 113 } 114 115 bitflags! { 116 #[derive(Clone, Copy)] 117 /// Flags to control parsing of counter styles. 118 pub struct CounterStyleParsingFlags: u8 { 119 /// Whether `none` is allowed. 120 const ALLOW_NONE = 1 << 0; 121 /// Whether a bare string is allowed. 122 const ALLOW_STRING = 1 << 1; 123 } 124 } 125 126 impl CounterStyle { 127 /// Parse a counter style, and optionally none|string (for list-style-type). 128 pub fn parse<'i, 't>( 129 context: &ParserContext, 130 input: &mut Parser<'i, 't>, 131 flags: CounterStyleParsingFlags, 132 ) -> Result<Self, ParseError<'i>> { 133 use self::CounterStyleParsingFlags as Flags; 134 let location = input.current_source_location(); 135 match input.next()? { 136 Token::QuotedString(ref string) if flags.intersects(Flags::ALLOW_STRING) => { 137 Ok(Self::String(AtomString::from(string.as_ref()))) 138 }, 139 Token::Ident(ref ident) => { 140 if flags.intersects(Flags::ALLOW_NONE) && ident.eq_ignore_ascii_case("none") { 141 return Ok(Self::None); 142 } 143 Ok(Self::Name(counter_style_name_from_ident(ident, location)?)) 144 }, 145 Token::Function(ref name) if name.eq_ignore_ascii_case("symbols") => { 146 input.parse_nested_block(|input| { 147 let symbols_type = input 148 .try_parse(SymbolsType::parse) 149 .unwrap_or(SymbolsType::Symbolic); 150 let symbols = Symbols::parse(context, input)?; 151 // There must be at least two symbols for alphabetic or 152 // numeric system. 153 if (symbols_type == SymbolsType::Alphabetic 154 || symbols_type == SymbolsType::Numeric) 155 && symbols.0.len() < 2 156 { 157 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 158 } 159 // Identifier is not allowed in symbols() function. 160 if symbols.0.iter().any(|sym| !sym.is_allowed_in_symbols()) { 161 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 162 } 163 Ok(Self::Symbols { 164 ty: symbols_type, 165 symbols, 166 }) 167 }) 168 }, 169 t => Err(location.new_unexpected_token_error(t.clone())), 170 } 171 } 172 } 173 174 impl SpecifiedValueInfo for CounterStyle { 175 fn collect_completion_keywords(f: KeywordsCollectFn) { 176 // XXX The best approach for implementing this is probably 177 // having a CounterStyleName type wrapping CustomIdent, and 178 // put the predefined list for that type in counter_style mod. 179 // But that's a non-trivial change itself, so we use a simpler 180 // approach here. 181 macro_rules! predefined { 182 ($($name:expr,)+) => { 183 f(&["symbols", "none", $($name,)+]) 184 } 185 } 186 include!("predefined.rs"); 187 } 188 } 189 190 fn parse_counter_style_name<'i>(input: &mut Parser<'i, '_>) -> Result<CustomIdent, ParseError<'i>> { 191 let location = input.current_source_location(); 192 let ident = input.expect_ident()?; 193 counter_style_name_from_ident(ident, location) 194 } 195 196 /// This allows the reserved counter style names "decimal" and "disc". 197 fn counter_style_name_from_ident<'i>( 198 ident: &CowRcStr<'i>, 199 location: SourceLocation, 200 ) -> Result<CustomIdent, ParseError<'i>> { 201 macro_rules! predefined { 202 ($($name: tt,)+) => {{ 203 ascii_case_insensitive_phf_map! { 204 predefined -> Atom = { 205 $( 206 $name => atom!($name), 207 )+ 208 } 209 } 210 211 // This effectively performs case normalization only on predefined names. 212 if let Some(lower_case) = predefined::get(&ident) { 213 Ok(CustomIdent(lower_case.clone())) 214 } else { 215 // none is always an invalid <counter-style> value. 216 CustomIdent::from_ident(location, ident, &["none"]) 217 } 218 }} 219 } 220 include!("predefined.rs") 221 } 222 223 fn is_valid_name_definition(ident: &CustomIdent) -> bool { 224 ident.0 != atom!("decimal") 225 && ident.0 != atom!("disc") 226 && ident.0 != atom!("circle") 227 && ident.0 != atom!("square") 228 && ident.0 != atom!("disclosure-closed") 229 && ident.0 != atom!("disclosure-open") 230 } 231 232 /// Parse the prelude of an @counter-style rule 233 pub fn parse_counter_style_name_definition<'i, 't>( 234 input: &mut Parser<'i, 't>, 235 ) -> Result<CustomIdent, ParseError<'i>> { 236 parse_counter_style_name(input).and_then(|ident| { 237 if !is_valid_name_definition(&ident) { 238 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 239 } else { 240 Ok(ident) 241 } 242 }) 243 } 244 245 /// Parse the body (inside `{}`) of an @counter-style rule 246 pub fn parse_counter_style_body<'i, 't>( 247 name: CustomIdent, 248 context: &ParserContext, 249 input: &mut Parser<'i, 't>, 250 location: SourceLocation, 251 ) -> Result<CounterStyleRuleData, ParseError<'i>> { 252 let start = input.current_source_location(); 253 let mut rule = CounterStyleRuleData::empty(name, location); 254 { 255 let mut parser = CounterStyleRuleParser { 256 context, 257 rule: &mut rule, 258 }; 259 let mut iter = RuleBodyParser::new(input, &mut parser); 260 while let Some(declaration) = iter.next() { 261 if let Err((error, slice)) = declaration { 262 let location = error.location; 263 let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration( 264 slice, error, 265 ); 266 context.log_css_error(location, error) 267 } 268 } 269 } 270 let error = match *rule.resolved_system() { 271 ref system @ System::Cyclic 272 | ref system @ System::Fixed { .. } 273 | ref system @ System::Symbolic 274 | ref system @ System::Alphabetic 275 | ref system @ System::Numeric 276 if rule.symbols.is_none() => 277 { 278 let system = system.to_css_string(); 279 Some(ContextualParseError::InvalidCounterStyleWithoutSymbols( 280 system, 281 )) 282 }, 283 ref system @ System::Alphabetic | ref system @ System::Numeric 284 if rule.symbols().unwrap().0.len() < 2 => 285 { 286 let system = system.to_css_string(); 287 Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols( 288 system, 289 )) 290 }, 291 System::Additive if rule.additive_symbols.is_none() => { 292 Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols) 293 }, 294 System::Extends(_) if rule.symbols.is_some() => { 295 Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols) 296 }, 297 System::Extends(_) if rule.additive_symbols.is_some() => { 298 Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols) 299 }, 300 _ => None, 301 }; 302 if let Some(error) = error { 303 context.log_css_error(start, error); 304 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 305 } else { 306 Ok(rule) 307 } 308 } 309 310 struct CounterStyleRuleParser<'a, 'b: 'a> { 311 context: &'a ParserContext<'b>, 312 rule: &'a mut CounterStyleRuleData, 313 } 314 315 /// Default methods reject all at rules. 316 impl<'a, 'b, 'i> AtRuleParser<'i> for CounterStyleRuleParser<'a, 'b> { 317 type Prelude = (); 318 type AtRule = (); 319 type Error = StyleParseErrorKind<'i>; 320 } 321 322 impl<'a, 'b, 'i> QualifiedRuleParser<'i> for CounterStyleRuleParser<'a, 'b> { 323 type Prelude = (); 324 type QualifiedRule = (); 325 type Error = StyleParseErrorKind<'i>; 326 } 327 328 impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> 329 for CounterStyleRuleParser<'a, 'b> 330 { 331 fn parse_qualified(&self) -> bool { 332 false 333 } 334 fn parse_declarations(&self) -> bool { 335 true 336 } 337 } 338 339 macro_rules! checker { 340 ($self:ident._($value:ident)) => {}; 341 ($self:ident. $checker:ident($value:ident)) => { 342 if !$self.$checker(&$value) { 343 return false; 344 } 345 }; 346 } 347 348 macro_rules! counter_style_descriptors { 349 ( 350 $( #[$doc: meta] $name: tt $ident: ident / $setter: ident [$checker: tt]: $ty: ty, )+ 351 ) => { 352 /// An @counter-style rule 353 #[derive(Clone, Debug, ToShmem)] 354 pub struct CounterStyleRuleData { 355 name: CustomIdent, 356 generation: Wrapping<u32>, 357 $( 358 #[$doc] 359 $ident: Option<$ty>, 360 )+ 361 /// Line and column of the @counter-style rule source code. 362 pub source_location: SourceLocation, 363 } 364 365 impl CounterStyleRuleData { 366 fn empty(name: CustomIdent, source_location: SourceLocation) -> Self { 367 CounterStyleRuleData { 368 name: name, 369 generation: Wrapping(0), 370 $( 371 $ident: None, 372 )+ 373 source_location, 374 } 375 } 376 377 $( 378 #[$doc] 379 pub fn $ident(&self) -> Option<&$ty> { 380 self.$ident.as_ref() 381 } 382 )+ 383 384 $( 385 #[$doc] 386 pub fn $setter(&mut self, value: $ty) -> bool { 387 checker!(self.$checker(value)); 388 self.$ident = Some(value); 389 self.generation += Wrapping(1); 390 true 391 } 392 )+ 393 } 394 395 impl<'a, 'b, 'i> DeclarationParser<'i> for CounterStyleRuleParser<'a, 'b> { 396 type Declaration = (); 397 type Error = StyleParseErrorKind<'i>; 398 399 fn parse_value<'t>( 400 &mut self, 401 name: CowRcStr<'i>, 402 input: &mut Parser<'i, 't>, 403 _declaration_start: &ParserState, 404 ) -> Result<(), ParseError<'i>> { 405 match_ignore_ascii_case! { &*name, 406 $( 407 $name => { 408 // DeclarationParser also calls parse_entirely so we’d normally not 409 // need to, but in this case we do because we set the value as a side 410 // effect rather than returning it. 411 let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; 412 self.rule.$ident = Some(value) 413 }, 414 )* 415 _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), 416 } 417 Ok(()) 418 } 419 } 420 421 impl ToCssWithGuard for CounterStyleRuleData { 422 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { 423 dest.write_str("@counter-style ")?; 424 self.name.to_css(&mut CssWriter::new(dest))?; 425 dest.write_str(" { ")?; 426 $( 427 if let Some(ref value) = self.$ident { 428 dest.write_str(concat!($name, ": "))?; 429 ToCss::to_css(value, &mut CssWriter::new(dest))?; 430 dest.write_str("; ")?; 431 } 432 )+ 433 dest.write_char('}') 434 } 435 } 436 } 437 } 438 439 counter_style_descriptors! { 440 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-system> 441 "system" system / set_system [check_system]: System, 442 443 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative> 444 "negative" negative / set_negative [_]: Negative, 445 446 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-prefix> 447 "prefix" prefix / set_prefix [_]: Symbol, 448 449 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-suffix> 450 "suffix" suffix / set_suffix [_]: Symbol, 451 452 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range> 453 "range" range / set_range [_]: CounterRanges, 454 455 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad> 456 "pad" pad / set_pad [_]: Pad, 457 458 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback> 459 "fallback" fallback / set_fallback [_]: Fallback, 460 461 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols> 462 "symbols" symbols / set_symbols [check_symbols]: Symbols, 463 464 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols> 465 "additive-symbols" additive_symbols / 466 set_additive_symbols [check_additive_symbols]: AdditiveSymbols, 467 468 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as> 469 "speak-as" speak_as / set_speak_as [_]: SpeakAs, 470 } 471 472 // Implements the special checkers for some setters. 473 // See <https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface> 474 impl CounterStyleRuleData { 475 /// Check that the system is effectively not changed. Only params 476 /// of system descriptor is changeable. 477 fn check_system(&self, value: &System) -> bool { 478 mem::discriminant(self.resolved_system()) == mem::discriminant(value) 479 } 480 481 fn check_symbols(&self, value: &Symbols) -> bool { 482 match *self.resolved_system() { 483 // These two systems require at least two symbols. 484 System::Numeric | System::Alphabetic => value.0.len() >= 2, 485 // No symbols should be set for extends system. 486 System::Extends(_) => false, 487 _ => true, 488 } 489 } 490 491 fn check_additive_symbols(&self, _value: &AdditiveSymbols) -> bool { 492 match *self.resolved_system() { 493 // No additive symbols should be set for extends system. 494 System::Extends(_) => false, 495 _ => true, 496 } 497 } 498 } 499 500 impl CounterStyleRuleData { 501 /// Get the name of the counter style rule. 502 pub fn name(&self) -> &CustomIdent { 503 &self.name 504 } 505 506 /// Set the name of the counter style rule. Caller must ensure that 507 /// the name is valid. 508 pub fn set_name(&mut self, name: CustomIdent) { 509 debug_assert!(is_valid_name_definition(&name)); 510 self.name = name; 511 } 512 513 /// Get the current generation of the counter style rule. 514 pub fn generation(&self) -> u32 { 515 self.generation.0 516 } 517 518 /// Get the system of this counter style rule, default to 519 /// `symbolic` if not specified. 520 pub fn resolved_system(&self) -> &System { 521 match self.system { 522 Some(ref system) => system, 523 None => &System::Symbolic, 524 } 525 } 526 } 527 528 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-system> 529 #[derive(Clone, Debug, ToShmem)] 530 pub enum System { 531 /// 'cyclic' 532 Cyclic, 533 /// 'numeric' 534 Numeric, 535 /// 'alphabetic' 536 Alphabetic, 537 /// 'symbolic' 538 Symbolic, 539 /// 'additive' 540 Additive, 541 /// 'fixed <integer>?' 542 Fixed { 543 /// '<integer>?' 544 first_symbol_value: Option<Integer>, 545 }, 546 /// 'extends <counter-style-name>' 547 Extends(CustomIdent), 548 } 549 550 impl Parse for System { 551 fn parse<'i, 't>( 552 context: &ParserContext, 553 input: &mut Parser<'i, 't>, 554 ) -> Result<Self, ParseError<'i>> { 555 try_match_ident_ignore_ascii_case! { input, 556 "cyclic" => Ok(System::Cyclic), 557 "numeric" => Ok(System::Numeric), 558 "alphabetic" => Ok(System::Alphabetic), 559 "symbolic" => Ok(System::Symbolic), 560 "additive" => Ok(System::Additive), 561 "fixed" => { 562 let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok(); 563 Ok(System::Fixed { first_symbol_value }) 564 }, 565 "extends" => { 566 let other = parse_counter_style_name(input)?; 567 Ok(System::Extends(other)) 568 }, 569 } 570 } 571 } 572 573 impl ToCss for System { 574 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 575 where 576 W: Write, 577 { 578 match *self { 579 System::Cyclic => dest.write_str("cyclic"), 580 System::Numeric => dest.write_str("numeric"), 581 System::Alphabetic => dest.write_str("alphabetic"), 582 System::Symbolic => dest.write_str("symbolic"), 583 System::Additive => dest.write_str("additive"), 584 System::Fixed { first_symbol_value } => { 585 if let Some(value) = first_symbol_value { 586 dest.write_str("fixed ")?; 587 value.to_css(dest) 588 } else { 589 dest.write_str("fixed") 590 } 591 }, 592 System::Extends(ref other) => { 593 dest.write_str("extends ")?; 594 other.to_css(dest) 595 }, 596 } 597 } 598 } 599 600 /// <https://drafts.csswg.org/css-counter-styles/#typedef-symbol> 601 #[derive( 602 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem, 603 )] 604 #[repr(u8)] 605 pub enum Symbol { 606 /// <string> 607 String(crate::OwnedStr), 608 /// <custom-ident> 609 Ident(CustomIdent), 610 // Not implemented: 611 // /// <image> 612 // Image(Image), 613 } 614 615 impl Parse for Symbol { 616 fn parse<'i, 't>( 617 _context: &ParserContext, 618 input: &mut Parser<'i, 't>, 619 ) -> Result<Self, ParseError<'i>> { 620 let location = input.current_source_location(); 621 match *input.next()? { 622 Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())), 623 Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(location, s, &[])?)), 624 ref t => Err(location.new_unexpected_token_error(t.clone())), 625 } 626 } 627 } 628 629 impl Symbol { 630 /// Returns whether this symbol is allowed in symbols() function. 631 pub fn is_allowed_in_symbols(&self) -> bool { 632 match self { 633 // Identifier is not allowed. 634 &Symbol::Ident(_) => false, 635 _ => true, 636 } 637 } 638 } 639 640 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative> 641 #[derive(Clone, Debug, ToCss, ToShmem)] 642 pub struct Negative(pub Symbol, pub Option<Symbol>); 643 644 impl Parse for Negative { 645 fn parse<'i, 't>( 646 context: &ParserContext, 647 input: &mut Parser<'i, 't>, 648 ) -> Result<Self, ParseError<'i>> { 649 Ok(Negative( 650 Symbol::parse(context, input)?, 651 input.try_parse(|input| Symbol::parse(context, input)).ok(), 652 )) 653 } 654 } 655 656 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range> 657 #[derive(Clone, Debug, ToCss, ToShmem)] 658 pub struct CounterRange { 659 /// The start of the range. 660 pub start: CounterBound, 661 /// The end of the range. 662 pub end: CounterBound, 663 } 664 665 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range> 666 /// 667 /// Empty represents 'auto' 668 #[derive(Clone, Debug, ToCss, ToShmem)] 669 #[css(comma)] 670 pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice<CounterRange>); 671 672 /// A bound found in `CounterRanges`. 673 #[derive(Clone, Copy, Debug, ToCss, ToShmem)] 674 pub enum CounterBound { 675 /// An integer bound. 676 Integer(Integer), 677 /// The infinite bound. 678 Infinite, 679 } 680 681 impl Parse for CounterRanges { 682 fn parse<'i, 't>( 683 context: &ParserContext, 684 input: &mut Parser<'i, 't>, 685 ) -> Result<Self, ParseError<'i>> { 686 if input 687 .try_parse(|input| input.expect_ident_matching("auto")) 688 .is_ok() 689 { 690 return Ok(CounterRanges(Default::default())); 691 } 692 693 let ranges = input.parse_comma_separated(|input| { 694 let start = parse_bound(context, input)?; 695 let end = parse_bound(context, input)?; 696 if let (CounterBound::Integer(start), CounterBound::Integer(end)) = (start, end) { 697 if start > end { 698 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 699 } 700 } 701 Ok(CounterRange { start, end }) 702 })?; 703 704 Ok(CounterRanges(ranges.into())) 705 } 706 } 707 708 fn parse_bound<'i, 't>( 709 context: &ParserContext, 710 input: &mut Parser<'i, 't>, 711 ) -> Result<CounterBound, ParseError<'i>> { 712 if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) { 713 return Ok(CounterBound::Integer(integer)); 714 } 715 input.expect_ident_matching("infinite")?; 716 Ok(CounterBound::Infinite) 717 } 718 719 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad> 720 #[derive(Clone, Debug, ToCss, ToShmem)] 721 pub struct Pad(pub Integer, pub Symbol); 722 723 impl Parse for Pad { 724 fn parse<'i, 't>( 725 context: &ParserContext, 726 input: &mut Parser<'i, 't>, 727 ) -> Result<Self, ParseError<'i>> { 728 let pad_with = input.try_parse(|input| Symbol::parse(context, input)); 729 let min_length = Integer::parse_non_negative(context, input)?; 730 let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?; 731 Ok(Pad(min_length, pad_with)) 732 } 733 } 734 735 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback> 736 #[derive(Clone, Debug, ToCss, ToShmem)] 737 pub struct Fallback(pub CustomIdent); 738 739 impl Parse for Fallback { 740 fn parse<'i, 't>( 741 _context: &ParserContext, 742 input: &mut Parser<'i, 't>, 743 ) -> Result<Self, ParseError<'i>> { 744 Ok(Fallback(parse_counter_style_name(input)?)) 745 } 746 } 747 748 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols> 749 #[derive( 750 Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem, 751 )] 752 #[repr(C)] 753 pub struct Symbols( 754 #[css(iterable)] 755 #[ignore_malloc_size_of = "Arc"] 756 pub crate::ArcSlice<Symbol>, 757 ); 758 759 impl Parse for Symbols { 760 fn parse<'i, 't>( 761 context: &ParserContext, 762 input: &mut Parser<'i, 't>, 763 ) -> Result<Self, ParseError<'i>> { 764 let mut symbols = smallvec::SmallVec::<[_; 5]>::new(); 765 while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) { 766 symbols.push(s); 767 } 768 if symbols.is_empty() { 769 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 770 } 771 Ok(Symbols(crate::ArcSlice::from_iter(symbols.drain(..)))) 772 } 773 } 774 775 /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols> 776 #[derive(Clone, Debug, ToCss, ToShmem)] 777 #[css(comma)] 778 pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice<AdditiveTuple>); 779 780 impl Parse for AdditiveSymbols { 781 fn parse<'i, 't>( 782 context: &ParserContext, 783 input: &mut Parser<'i, 't>, 784 ) -> Result<Self, ParseError<'i>> { 785 let tuples = Vec::<AdditiveTuple>::parse(context, input)?; 786 // FIXME maybe? https://github.com/w3c/csswg-drafts/issues/1220 787 if tuples 788 .windows(2) 789 .any(|window| window[0].weight <= window[1].weight) 790 { 791 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 792 } 793 Ok(AdditiveSymbols(tuples.into())) 794 } 795 } 796 797 /// <integer> && <symbol> 798 #[derive(Clone, Debug, ToCss, ToShmem)] 799 pub struct AdditiveTuple { 800 /// <integer> 801 pub weight: Integer, 802 /// <symbol> 803 pub symbol: Symbol, 804 } 805 806 impl OneOrMoreSeparated for AdditiveTuple { 807 type S = Comma; 808 } 809 810 impl Parse for AdditiveTuple { 811 fn parse<'i, 't>( 812 context: &ParserContext, 813 input: &mut Parser<'i, 't>, 814 ) -> Result<Self, ParseError<'i>> { 815 let symbol = input.try_parse(|input| Symbol::parse(context, input)); 816 let weight = Integer::parse_non_negative(context, input)?; 817 let symbol = symbol.or_else(|_| Symbol::parse(context, input))?; 818 Ok(Self { weight, symbol }) 819 } 820 } 821 822 /// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as> 823 #[derive(Clone, Debug, ToCss, ToShmem)] 824 pub enum SpeakAs { 825 /// auto 826 Auto, 827 /// bullets 828 Bullets, 829 /// numbers 830 Numbers, 831 /// words 832 Words, 833 // /// spell-out, not supported, see bug 1024178 834 // SpellOut, 835 /// <counter-style-name> 836 Other(CustomIdent), 837 } 838 839 impl Parse for SpeakAs { 840 fn parse<'i, 't>( 841 _context: &ParserContext, 842 input: &mut Parser<'i, 't>, 843 ) -> Result<Self, ParseError<'i>> { 844 let mut is_spell_out = false; 845 let result = input.try_parse(|input| { 846 let ident = input.expect_ident().map_err(|_| ())?; 847 match_ignore_ascii_case! { &*ident, 848 "auto" => Ok(SpeakAs::Auto), 849 "bullets" => Ok(SpeakAs::Bullets), 850 "numbers" => Ok(SpeakAs::Numbers), 851 "words" => Ok(SpeakAs::Words), 852 "spell-out" => { 853 is_spell_out = true; 854 Err(()) 855 }, 856 _ => Err(()), 857 } 858 }); 859 if is_spell_out { 860 // spell-out is not supported, but don’t parse it as a <counter-style-name>. 861 // See bug 1024178. 862 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 863 } 864 result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?))) 865 } 866 }