font_feature_values_rule.rs (18228B)
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 [`@font-feature-values`][font-feature-values] at-rule. 6 //! 7 //! [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule 8 9 use crate::derives::*; 10 use crate::error_reporting::ContextualParseError; 11 #[cfg(feature = "gecko")] 12 use crate::gecko_bindings::bindings::Gecko_AppendFeatureValueHashEntry; 13 #[cfg(feature = "gecko")] 14 use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet}; 15 use crate::parser::{Parse, ParserContext}; 16 use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; 17 use crate::stylesheets::CssRuleType; 18 use crate::values::computed::font::FamilyName; 19 use crate::values::serialize_atom_identifier; 20 use crate::Atom; 21 use cssparser::{ 22 match_ignore_ascii_case, AtRuleParser, BasicParseErrorKind, CowRcStr, DeclarationParser, 23 Parser, ParserState, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, 24 Token, 25 }; 26 use std::fmt::{self, Write}; 27 use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss}; 28 #[cfg(feature = "gecko")] 29 use thin_vec::ThinVec; 30 31 /// A @font-feature-values block declaration. 32 /// It is `<ident>: <integer>+`. 33 /// This struct can take 3 value types. 34 /// - `SingleValue` is to keep just one unsigned integer value. 35 /// - `PairValues` is to keep one or two unsigned integer values. 36 /// - `VectorValues` is to keep a list of unsigned integer values. 37 #[derive(Clone, Debug, PartialEq, ToShmem)] 38 pub struct FFVDeclaration<T> { 39 /// An `<ident>` for declaration name. 40 pub name: Atom, 41 /// An `<integer>+` for declaration value. 42 pub value: T, 43 } 44 45 impl<T: ToCss> ToCss for FFVDeclaration<T> { 46 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 47 where 48 W: Write, 49 { 50 serialize_atom_identifier(&self.name, dest)?; 51 dest.write_str(": ")?; 52 self.value.to_css(dest)?; 53 dest.write_char(';') 54 } 55 } 56 57 /// A trait for @font-feature-values rule to gecko values conversion. 58 #[cfg(feature = "gecko")] 59 pub trait ToGeckoFontFeatureValues { 60 /// Sets the equivalent of declaration to gecko `ThinVec<u32>` array. 61 fn to_gecko_font_feature_values(&self) -> ThinVec<u32>; 62 } 63 64 /// A @font-feature-values block declaration value that keeps one value. 65 #[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] 66 pub struct SingleValue(pub u32); 67 68 impl Parse for SingleValue { 69 fn parse<'i, 't>( 70 _context: &ParserContext, 71 input: &mut Parser<'i, 't>, 72 ) -> Result<SingleValue, ParseError<'i>> { 73 let location = input.current_source_location(); 74 match *input.next()? { 75 Token::Number { 76 int_value: Some(v), .. 77 } if v >= 0 => Ok(SingleValue(v as u32)), 78 ref t => Err(location.new_unexpected_token_error(t.clone())), 79 } 80 } 81 } 82 83 #[cfg(feature = "gecko")] 84 impl ToGeckoFontFeatureValues for SingleValue { 85 fn to_gecko_font_feature_values(&self) -> ThinVec<u32> { 86 thin_vec::thin_vec![self.0 as u32] 87 } 88 } 89 90 /// A @font-feature-values block declaration value that keeps one or two values. 91 #[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] 92 pub struct PairValues(pub u32, pub Option<u32>); 93 94 impl Parse for PairValues { 95 fn parse<'i, 't>( 96 _context: &ParserContext, 97 input: &mut Parser<'i, 't>, 98 ) -> Result<PairValues, ParseError<'i>> { 99 let location = input.current_source_location(); 100 let first = match *input.next()? { 101 Token::Number { 102 int_value: Some(a), .. 103 } if a >= 0 => a as u32, 104 ref t => return Err(location.new_unexpected_token_error(t.clone())), 105 }; 106 let location = input.current_source_location(); 107 match input.next() { 108 Ok(&Token::Number { 109 int_value: Some(b), .. 110 }) if b >= 0 => Ok(PairValues(first, Some(b as u32))), 111 // It can't be anything other than number. 112 Ok(t) => Err(location.new_unexpected_token_error(t.clone())), 113 // It can be just one value. 114 Err(_) => Ok(PairValues(first, None)), 115 } 116 } 117 } 118 119 #[cfg(feature = "gecko")] 120 impl ToGeckoFontFeatureValues for PairValues { 121 fn to_gecko_font_feature_values(&self) -> ThinVec<u32> { 122 let mut result = thin_vec::thin_vec![self.0 as u32]; 123 if let Some(second) = self.1 { 124 result.push(second as u32); 125 } 126 result 127 } 128 } 129 130 /// A @font-feature-values block declaration value that keeps a list of values. 131 #[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] 132 pub struct VectorValues(#[css(iterable)] pub Vec<u32>); 133 134 impl Parse for VectorValues { 135 fn parse<'i, 't>( 136 _context: &ParserContext, 137 input: &mut Parser<'i, 't>, 138 ) -> Result<VectorValues, ParseError<'i>> { 139 let mut vec = vec![]; 140 loop { 141 let location = input.current_source_location(); 142 match input.next() { 143 Ok(&Token::Number { 144 int_value: Some(a), .. 145 }) if a >= 0 => { 146 vec.push(a as u32); 147 }, 148 // It can't be anything other than number. 149 Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), 150 Err(_) => break, 151 } 152 } 153 154 if vec.len() == 0 { 155 return Err(input.new_error(BasicParseErrorKind::EndOfInput)); 156 } 157 158 Ok(VectorValues(vec)) 159 } 160 } 161 162 #[cfg(feature = "gecko")] 163 impl ToGeckoFontFeatureValues for VectorValues { 164 fn to_gecko_font_feature_values(&self) -> ThinVec<u32> { 165 self.0.iter().copied().collect() 166 } 167 } 168 169 /// Parses a list of `FamilyName`s. 170 pub fn parse_family_name_list<'i, 't>( 171 context: &ParserContext, 172 input: &mut Parser<'i, 't>, 173 ) -> Result<Vec<FamilyName>, ParseError<'i>> { 174 input 175 .parse_comma_separated(|i| FamilyName::parse(context, i)) 176 .map_err(|e| e.into()) 177 } 178 179 /// @font-feature-values inside block parser. Parses a list of `FFVDeclaration`. 180 /// (`<ident>: <integer>+`) 181 struct FFVDeclarationsParser<'a, 'b: 'a, T: 'a> { 182 context: &'a ParserContext<'b>, 183 declarations: &'a mut Vec<FFVDeclaration<T>>, 184 } 185 186 /// Default methods reject all at rules. 187 impl<'a, 'b, 'i, T> AtRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> { 188 type Prelude = (); 189 type AtRule = (); 190 type Error = StyleParseErrorKind<'i>; 191 } 192 193 impl<'a, 'b, 'i, T> QualifiedRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> { 194 type Prelude = (); 195 type QualifiedRule = (); 196 type Error = StyleParseErrorKind<'i>; 197 } 198 199 impl<'a, 'b, 'i, T> DeclarationParser<'i> for FFVDeclarationsParser<'a, 'b, T> 200 where 201 T: Parse, 202 { 203 type Declaration = (); 204 type Error = StyleParseErrorKind<'i>; 205 206 fn parse_value<'t>( 207 &mut self, 208 name: CowRcStr<'i>, 209 input: &mut Parser<'i, 't>, 210 _declaration_start: &ParserState, 211 ) -> Result<(), ParseError<'i>> { 212 let value = input.parse_entirely(|i| T::parse(self.context, i))?; 213 let new = FFVDeclaration { 214 name: Atom::from(&*name), 215 value, 216 }; 217 update_or_push(&mut self.declarations, new); 218 Ok(()) 219 } 220 } 221 222 impl<'a, 'b, 'i, T> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> 223 for FFVDeclarationsParser<'a, 'b, T> 224 where 225 T: Parse, 226 { 227 fn parse_declarations(&self) -> bool { 228 true 229 } 230 fn parse_qualified(&self) -> bool { 231 false 232 } 233 } 234 235 macro_rules! font_feature_values_blocks { 236 ( 237 blocks = [ 238 $( #[$doc: meta] $name: tt $ident: ident / $ident_camel: ident / $gecko_enum: ident: $ty: ty, )* 239 ] 240 ) => { 241 /// The [`@font-feature-values`][font-feature-values] at-rule. 242 /// 243 /// [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule 244 #[derive(Clone, Debug, PartialEq, ToShmem)] 245 pub struct FontFeatureValuesRule { 246 /// Font family list for @font-feature-values rule. 247 /// Family names cannot contain generic families. FamilyName 248 /// also accepts only non-generic names. 249 pub family_names: Vec<FamilyName>, 250 $( 251 #[$doc] 252 pub $ident: Vec<FFVDeclaration<$ty>>, 253 )* 254 /// The line and column of the rule's source code. 255 pub source_location: SourceLocation, 256 } 257 258 impl FontFeatureValuesRule { 259 /// Creates an empty FontFeatureValuesRule with given location and family name list. 260 fn new(family_names: Vec<FamilyName>, location: SourceLocation) -> Self { 261 FontFeatureValuesRule { 262 family_names: family_names, 263 $( 264 $ident: vec![], 265 )* 266 source_location: location, 267 } 268 } 269 270 /// Parses a `FontFeatureValuesRule`. 271 pub fn parse( 272 context: &ParserContext, 273 input: &mut Parser, 274 family_names: Vec<FamilyName>, 275 location: SourceLocation, 276 ) -> Self { 277 let mut rule = FontFeatureValuesRule::new(family_names, location); 278 let mut parser = FontFeatureValuesRuleParser { 279 context, 280 rule: &mut rule, 281 }; 282 let mut iter = RuleBodyParser::new(input, &mut parser); 283 while let Some(result) = iter.next() { 284 if let Err((error, slice)) = result { 285 let location = error.location; 286 let error = ContextualParseError::UnsupportedRule(slice, error); 287 context.log_css_error(location, error); 288 } 289 } 290 rule 291 } 292 293 /// Prints inside of `@font-feature-values` block. 294 pub fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 295 where 296 W: Write, 297 { 298 $( 299 if self.$ident.len() > 0 { 300 dest.write_str(concat!("@", $name, " {\n"))?; 301 let iter = self.$ident.iter(); 302 for val in iter { 303 val.to_css(dest)?; 304 dest.write_str("\n")? 305 } 306 dest.write_str("}\n")? 307 } 308 )* 309 Ok(()) 310 } 311 312 /// Returns length of all at-rules. 313 pub fn len(&self) -> usize { 314 let mut len = 0; 315 $( 316 len += self.$ident.len(); 317 )* 318 len 319 } 320 321 /// Convert to Gecko gfxFontFeatureValueSet. 322 #[cfg(feature = "gecko")] 323 pub fn set_at_rules(&self, dest: *mut gfxFontFeatureValueSet) { 324 for ref family in self.family_names.iter() { 325 let family = family.name.to_ascii_lowercase(); 326 $( 327 if self.$ident.len() > 0 { 328 for val in self.$ident.iter() { 329 let array = unsafe { 330 Gecko_AppendFeatureValueHashEntry( 331 dest, 332 family.as_ptr(), 333 structs::$gecko_enum, 334 val.name.as_ptr() 335 ) 336 }; 337 unsafe { 338 *array = val.value.to_gecko_font_feature_values(); 339 } 340 } 341 } 342 )* 343 } 344 } 345 } 346 347 impl ToCssWithGuard for FontFeatureValuesRule { 348 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { 349 dest.write_str("@font-feature-values ")?; 350 self.family_names.to_css(&mut CssWriter::new(dest))?; 351 dest.write_str(" {\n")?; 352 self.value_to_css(&mut CssWriter::new(dest))?; 353 dest.write_char('}') 354 } 355 } 356 357 /// Updates with new value if same `ident` exists, otherwise pushes to the vector. 358 fn update_or_push<T>(vec: &mut Vec<FFVDeclaration<T>>, element: FFVDeclaration<T>) { 359 if let Some(item) = vec.iter_mut().find(|item| item.name == element.name) { 360 item.value = element.value; 361 } else { 362 vec.push(element); 363 } 364 } 365 366 /// Keeps the information about block type like @swash, @styleset etc. 367 enum BlockType { 368 $( 369 $ident_camel, 370 )* 371 } 372 373 /// Parser for `FontFeatureValuesRule`. Parses all blocks 374 /// <feature-type> { 375 /// <feature-value-declaration-list> 376 /// } 377 /// <feature-type> = @stylistic | @historical-forms | @styleset | 378 /// @character-variant | @swash | @ornaments | @annotation 379 struct FontFeatureValuesRuleParser<'a> { 380 context: &'a ParserContext<'a>, 381 rule: &'a mut FontFeatureValuesRule, 382 } 383 384 /// Default methods reject all qualified rules. 385 impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a> { 386 type Prelude = (); 387 type QualifiedRule = (); 388 type Error = StyleParseErrorKind<'i>; 389 } 390 391 impl<'a, 'i> AtRuleParser<'i> for FontFeatureValuesRuleParser<'a> { 392 type Prelude = BlockType; 393 type AtRule = (); 394 type Error = StyleParseErrorKind<'i>; 395 396 fn parse_prelude<'t>( 397 &mut self, 398 name: CowRcStr<'i>, 399 input: &mut Parser<'i, 't>, 400 ) -> Result<BlockType, ParseError<'i>> { 401 match_ignore_ascii_case! { &*name, 402 $( 403 $name => Ok(BlockType::$ident_camel), 404 )* 405 _ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), 406 } 407 } 408 409 fn parse_block<'t>( 410 &mut self, 411 prelude: BlockType, 412 _: &ParserState, 413 input: &mut Parser<'i, 't> 414 ) -> Result<Self::AtRule, ParseError<'i>> { 415 debug_assert!(self.context.rule_types().contains(CssRuleType::FontFeatureValues)); 416 match prelude { 417 $( 418 BlockType::$ident_camel => { 419 let mut parser = FFVDeclarationsParser { 420 context: &self.context, 421 declarations: &mut self.rule.$ident, 422 }; 423 424 let mut iter = RuleBodyParser::new(input, &mut parser); 425 while let Some(declaration) = iter.next() { 426 if let Err((error, slice)) = declaration { 427 let location = error.location; 428 // TODO(emilio): Maybe add a more specific error kind for 429 // font-feature-values descriptors. 430 let error = ContextualParseError::UnsupportedPropertyDeclaration(slice, error, &[]); 431 self.context.log_css_error(location, error); 432 } 433 } 434 }, 435 )* 436 } 437 438 Ok(()) 439 } 440 } 441 442 impl<'a, 'i> DeclarationParser<'i> for FontFeatureValuesRuleParser<'a> { 443 type Declaration = (); 444 type Error = StyleParseErrorKind<'i>; 445 } 446 447 impl<'a, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> for FontFeatureValuesRuleParser<'a> { 448 fn parse_declarations(&self) -> bool { false } 449 fn parse_qualified(&self) -> bool { true } 450 } 451 } 452 } 453 454 font_feature_values_blocks! { 455 blocks = [ 456 #[doc = "A @swash blocksck. \ 457 Specifies a feature name that will work with the swash() \ 458 functional notation of font-variant-alternates."] 459 "swash" swash / Swash / NS_FONT_VARIANT_ALTERNATES_SWASH: SingleValue, 460 461 #[doc = "A @stylistic block. \ 462 Specifies a feature name that will work with the annotation() \ 463 functional notation of font-variant-alternates."] 464 "stylistic" stylistic / Stylistic / NS_FONT_VARIANT_ALTERNATES_STYLISTIC: SingleValue, 465 466 #[doc = "A @ornaments block. \ 467 Specifies a feature name that will work with the ornaments() ] \ 468 functional notation of font-variant-alternates."] 469 "ornaments" ornaments / Ornaments / NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: SingleValue, 470 471 #[doc = "A @annotation block. \ 472 Specifies a feature name that will work with the stylistic() \ 473 functional notation of font-variant-alternates."] 474 "annotation" annotation / Annotation / NS_FONT_VARIANT_ALTERNATES_ANNOTATION: SingleValue, 475 476 #[doc = "A @character-variant block. \ 477 Specifies a feature name that will work with the styleset() \ 478 functional notation of font-variant-alternates. The value can be a pair."] 479 "character-variant" character_variant / CharacterVariant / NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT: 480 PairValues, 481 482 #[doc = "A @styleset block. \ 483 Specifies a feature name that will work with the character-variant() \ 484 functional notation of font-variant-alternates. The value can be a list."] 485 "styleset" styleset / Styleset / NS_FONT_VARIANT_ALTERNATES_STYLESET: VectorValues, 486 ] 487 }