font.mako.rs (21884B)
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 <%namespace name="helpers" file="/helpers.mako.rs" /> 6 <% from data import SYSTEM_FONT_LONGHANDS %> 7 8 <%helpers:shorthand 9 name="font" 10 engines="gecko servo" 11 sub_properties=" 12 font-style 13 font-variant-caps 14 font-weight 15 font-stretch 16 font-size 17 line-height 18 font-family 19 ${'font-size-adjust' if engine == 'gecko' else ''} 20 ${'font-kerning' if engine == 'gecko' else ''} 21 ${'font-optical-sizing' if engine == 'gecko' else ''} 22 ${'font-variant-alternates' if engine == 'gecko' else ''} 23 ${'font-variant-east-asian' if engine == 'gecko' else ''} 24 ${'font-variant-emoji' if engine == 'gecko' else ''} 25 ${'font-variant-ligatures' if engine == 'gecko' else ''} 26 ${'font-variant-numeric' if engine == 'gecko' else ''} 27 ${'font-variant-position' if engine == 'gecko' else ''} 28 ${'font-language-override' if engine == 'gecko' else ''} 29 ${'font-feature-settings' if engine == 'gecko' else ''} 30 ${'font-variation-settings' if engine == 'gecko' else ''} 31 " 32 derive_value_info="False" 33 spec="https://drafts.csswg.org/css-fonts-3/#propdef-font" 34 > 35 use crate::computed_values::font_variant_caps::T::SmallCaps; 36 use crate::parser::Parse; 37 use crate::properties::longhands::{font_family, font_style, font_weight, font_stretch}; 38 #[cfg(feature = "gecko")] 39 use crate::properties::longhands::font_size; 40 use crate::properties::longhands::font_variant_caps; 41 use crate::values::specified::font::LineHeight; 42 use crate::values::specified::{FontSize, FontWeight}; 43 use crate::values::specified::font::{FontStretch, FontStretchKeyword}; 44 #[cfg(feature = "gecko")] 45 use crate::values::specified::font::SystemFont; 46 47 <% 48 gecko_sub_properties = "kerning language_override size_adjust \ 49 variant_alternates variant_east_asian \ 50 variant_emoji variant_ligatures \ 51 variant_numeric variant_position \ 52 feature_settings variation_settings \ 53 optical_sizing".split() 54 %> 55 % if engine == "gecko": 56 % for prop in gecko_sub_properties: 57 use crate::properties::longhands::font_${prop}; 58 % endfor 59 % endif 60 use self::font_family::SpecifiedValue as FontFamily; 61 62 pub fn parse_value<'i, 't>( 63 context: &ParserContext, 64 input: &mut Parser<'i, 't>, 65 ) -> Result<Longhands, ParseError<'i>> { 66 let mut nb_normals = 0; 67 let mut style = None; 68 let mut variant_caps = None; 69 let mut weight = None; 70 let mut stretch = None; 71 let size; 72 % if engine == "gecko": 73 if let Ok(sys) = input.try_parse(|i| SystemFont::parse(context, i)) { 74 return Ok(expanded! { 75 % for name in SYSTEM_FONT_LONGHANDS: 76 ${name}: ${name}::SpecifiedValue::system_font(sys), 77 % endfor 78 line_height: LineHeight::normal(), 79 % for name in gecko_sub_properties + ["variant_caps"]: 80 font_${name}: font_${name}::get_initial_specified_value(), 81 % endfor 82 }) 83 } 84 % endif 85 loop { 86 // Special-case 'normal' because it is valid in each of 87 // font-style, font-weight, font-variant and font-stretch. 88 // Leaves the values to None, 'normal' is the initial value for each of them. 89 if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { 90 nb_normals += 1; 91 continue; 92 } 93 if style.is_none() { 94 if let Ok(value) = input.try_parse(|input| font_style::parse(context, input)) { 95 style = Some(value); 96 continue 97 } 98 } 99 if weight.is_none() { 100 if let Ok(value) = input.try_parse(|input| font_weight::parse(context, input)) { 101 weight = Some(value); 102 continue 103 } 104 } 105 if variant_caps.is_none() { 106 // The only variant-caps value allowed is small-caps (from CSS2); the added values 107 // defined by CSS Fonts 3 and later are not accepted. 108 // https://www.w3.org/TR/css-fonts-4/#font-prop 109 if input.try_parse(|input| input.expect_ident_matching("small-caps")).is_ok() { 110 variant_caps = Some(SmallCaps); 111 continue 112 } 113 } 114 if stretch.is_none() { 115 if let Ok(value) = input.try_parse(FontStretchKeyword::parse) { 116 stretch = Some(FontStretch::Keyword(value)); 117 continue 118 } 119 } 120 size = Some(FontSize::parse(context, input)?); 121 break 122 } 123 124 let size = match size { 125 Some(s) => s, 126 None => { 127 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 128 } 129 }; 130 131 let line_height = if input.try_parse(|input| input.expect_delim('/')).is_ok() { 132 Some(LineHeight::parse(context, input)?) 133 } else { 134 None 135 }; 136 137 #[inline] 138 fn count<T>(opt: &Option<T>) -> u8 { 139 if opt.is_some() { 1 } else { 0 } 140 } 141 142 if (count(&style) + count(&weight) + count(&variant_caps) + count(&stretch) + nb_normals) > 4 { 143 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 144 } 145 146 let family = FontFamily::parse(context, input)?; 147 Ok(expanded! { 148 % for name in "style weight stretch variant_caps".split(): 149 font_${name}: unwrap_or_initial!(font_${name}, ${name}), 150 % endfor 151 font_size: size, 152 line_height: line_height.unwrap_or(LineHeight::normal()), 153 font_family: family, 154 % if engine == "gecko": 155 % for name in gecko_sub_properties: 156 font_${name}: font_${name}::get_initial_specified_value(), 157 % endfor 158 % endif 159 }) 160 } 161 162 % if engine == "gecko": 163 enum CheckSystemResult { 164 AllSystem(SystemFont), 165 SomeSystem, 166 None 167 } 168 % endif 169 170 impl<'a> ToCss for LonghandsToSerialize<'a> { 171 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 172 % if engine == "gecko": 173 match self.check_system() { 174 CheckSystemResult::AllSystem(sys) => return sys.to_css(dest), 175 CheckSystemResult::SomeSystem => return Ok(()), 176 CheckSystemResult::None => {} 177 } 178 % endif 179 180 % if engine == "gecko": 181 if let Some(v) = self.font_optical_sizing { 182 if v != &font_optical_sizing::get_initial_specified_value() { 183 return Ok(()); 184 } 185 } 186 if let Some(v) = self.font_variation_settings { 187 if v != &font_variation_settings::get_initial_specified_value() { 188 return Ok(()); 189 } 190 } 191 if let Some(v) = self.font_variant_emoji { 192 if v != &font_variant_emoji::get_initial_specified_value() { 193 return Ok(()); 194 } 195 } 196 197 % for name in gecko_sub_properties: 198 % if name != "optical_sizing" and name != "variation_settings" and name != "variant_emoji": 199 if self.font_${name} != &font_${name}::get_initial_specified_value() { 200 return Ok(()); 201 } 202 % endif 203 % endfor 204 % endif 205 206 // Only font-stretch keywords are allowed as part as the font 207 // shorthand. 208 let font_stretch = match *self.font_stretch { 209 FontStretch::Keyword(kw) => kw, 210 FontStretch::Stretch(percentage) => { 211 match FontStretchKeyword::from_percentage(percentage.0.get()) { 212 Some(kw) => kw, 213 None => return Ok(()), 214 } 215 } 216 FontStretch::System(..) => return Ok(()), 217 }; 218 219 // The only variant-caps value allowed in the shorthand is small-caps (from CSS2); 220 // the added values defined by CSS Fonts 3 and later are not supported. 221 // https://www.w3.org/TR/css-fonts-4/#font-prop 222 if self.font_variant_caps != &font_variant_caps::get_initial_specified_value() && 223 *self.font_variant_caps != SmallCaps { 224 return Ok(()); 225 } 226 227 % for name in "style variant_caps".split(): 228 if self.font_${name} != &font_${name}::get_initial_specified_value() { 229 self.font_${name}.to_css(dest)?; 230 dest.write_char(' ')?; 231 } 232 % endfor 233 234 // The initial specified font-weight value of 'normal' computes as a number (400), 235 // not to the keyword, so we need to check for that as well in order to properly 236 // serialize the computed style. 237 if self.font_weight != &FontWeight::normal() && 238 self.font_weight != &FontWeight::from_gecko_keyword(400) { 239 self.font_weight.to_css(dest)?; 240 dest.write_char(' ')?; 241 } 242 243 if font_stretch != FontStretchKeyword::Normal { 244 font_stretch.to_css(dest)?; 245 dest.write_char(' ')?; 246 } 247 248 self.font_size.to_css(dest)?; 249 250 if *self.line_height != LineHeight::normal() { 251 dest.write_str(" / ")?; 252 self.line_height.to_css(dest)?; 253 } 254 255 dest.write_char(' ')?; 256 self.font_family.to_css(dest)?; 257 258 Ok(()) 259 } 260 } 261 262 impl<'a> LonghandsToSerialize<'a> { 263 % if engine == "gecko": 264 /// Check if some or all members are system fonts 265 fn check_system(&self) -> CheckSystemResult { 266 let mut sys = None; 267 let mut all = true; 268 269 % for prop in SYSTEM_FONT_LONGHANDS: 270 % if prop == "font_optical_sizing" or prop == "font_variation_settings": 271 if let Some(value) = self.${prop} { 272 % else: 273 { 274 let value = self.${prop}; 275 % endif 276 match value.get_system() { 277 Some(s) => { 278 debug_assert!(sys.is_none() || s == sys.unwrap()); 279 sys = Some(s); 280 } 281 None => { 282 all = false; 283 } 284 } 285 } 286 % endfor 287 if self.line_height != &LineHeight::normal() { 288 all = false 289 } 290 if all { 291 CheckSystemResult::AllSystem(sys.unwrap()) 292 } else if sys.is_some() { 293 CheckSystemResult::SomeSystem 294 } else { 295 CheckSystemResult::None 296 } 297 } 298 % endif 299 } 300 301 <% 302 subprops_for_value_info = ["font_style", "font_weight", "font_stretch", 303 "font_variant_caps", "font_size", "font_family"] 304 subprops_for_value_info = [ 305 "<longhands::{}::SpecifiedValue as SpecifiedValueInfo>".format(p) 306 for p in subprops_for_value_info 307 ] 308 %> 309 impl SpecifiedValueInfo for Longhands { 310 const SUPPORTED_TYPES: u8 = 0 311 % for p in subprops_for_value_info: 312 | ${p}::SUPPORTED_TYPES 313 % endfor 314 ; 315 316 fn collect_completion_keywords(f: KeywordsCollectFn) { 317 % for p in subprops_for_value_info: 318 ${p}::collect_completion_keywords(f); 319 % endfor 320 % if engine == "gecko": 321 <SystemFont as SpecifiedValueInfo>::collect_completion_keywords(f); 322 % endif 323 } 324 } 325 </%helpers:shorthand> 326 327 <%helpers:shorthand name="font-variant" 328 engines="gecko servo" 329 sub_properties="font-variant-caps 330 ${'font-variant-alternates' if engine == 'gecko' else ''} 331 ${'font-variant-east-asian' if engine == 'gecko' else ''} 332 ${'font-variant-emoji' if engine == 'gecko' else ''} 333 ${'font-variant-ligatures' if engine == 'gecko' else ''} 334 ${'font-variant-numeric' if engine == 'gecko' else ''} 335 ${'font-variant-position' if engine == 'gecko' else ''}" 336 spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-variant"> 337 % if engine == 'gecko': 338 <% sub_properties = "ligatures caps alternates numeric east_asian position emoji".split() %> 339 % else: 340 <% sub_properties = ["caps"] %> 341 % endif 342 343 % for prop in sub_properties: 344 use crate::properties::longhands::font_variant_${prop}; 345 % endfor 346 #[allow(unused_imports)] 347 use crate::values::specified::FontVariantLigatures; 348 349 pub fn parse_value<'i, 't>( 350 context: &ParserContext, 351 input: &mut Parser<'i, 't>, 352 ) -> Result<Longhands, ParseError<'i>> { 353 % for prop in sub_properties: 354 let mut ${prop} = None; 355 % endfor 356 357 if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { 358 // Leave the values to None, 'normal' is the initial value for all the sub properties. 359 } else if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { 360 // The 'none' value sets 'font-variant-ligatures' to 'none' and resets all other sub properties 361 // to their initial value. 362 % if engine == "gecko": 363 ligatures = Some(FontVariantLigatures::NONE); 364 % endif 365 } else { 366 let mut has_custom_value: bool = false; 367 loop { 368 if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() || 369 input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { 370 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 371 } 372 % for prop in sub_properties: 373 if ${prop}.is_none() { 374 if let Ok(value) = input.try_parse(|i| font_variant_${prop}::parse(context, i)) { 375 has_custom_value = true; 376 ${prop} = Some(value); 377 continue 378 } 379 } 380 % endfor 381 382 break 383 } 384 385 if !has_custom_value { 386 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 387 } 388 } 389 390 Ok(expanded! { 391 % for prop in sub_properties: 392 font_variant_${prop}: unwrap_or_initial!(font_variant_${prop}, ${prop}), 393 % endfor 394 }) 395 } 396 397 impl<'a> ToCss for LonghandsToSerialize<'a> { 398 #[allow(unused_assignments)] 399 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 400 401 let has_none_ligatures = 402 % if engine == "gecko": 403 self.font_variant_ligatures == &FontVariantLigatures::NONE; 404 % else: 405 false; 406 % endif 407 408 const TOTAL_SUBPROPS: usize = ${len(sub_properties)}; 409 let mut nb_normals = 0; 410 % for prop in sub_properties: 411 % if prop == "emoji": 412 if let Some(value) = self.font_variant_${prop} { 413 % else: 414 { 415 let value = self.font_variant_${prop}; 416 % endif 417 if value == &font_variant_${prop}::get_initial_specified_value() { 418 nb_normals += 1; 419 } 420 } 421 % if prop == "emoji": 422 else { 423 // The property was disabled, so we count it as 'normal' for the purpose 424 // of deciding how the shorthand can be serialized. 425 nb_normals += 1; 426 } 427 % endif 428 % endfor 429 430 431 if nb_normals > 0 && nb_normals == TOTAL_SUBPROPS { 432 dest.write_str("normal")?; 433 } else if has_none_ligatures { 434 if nb_normals == TOTAL_SUBPROPS - 1 { 435 // Serialize to 'none' if 'font-variant-ligatures' is set to 'none' and all other 436 // font feature properties are reset to their initial value. 437 dest.write_str("none")?; 438 } else { 439 return Ok(()) 440 } 441 } else { 442 let mut has_any = false; 443 % for prop in sub_properties: 444 % if prop == "emoji": 445 if let Some(value) = self.font_variant_${prop} { 446 % else: 447 { 448 let value = self.font_variant_${prop}; 449 % endif 450 if value != &font_variant_${prop}::get_initial_specified_value() { 451 if has_any { 452 dest.write_char(' ')?; 453 } 454 has_any = true; 455 value.to_css(dest)?; 456 } 457 } 458 % endfor 459 } 460 461 Ok(()) 462 } 463 } 464 </%helpers:shorthand> 465 466 <%helpers:shorthand name="font-synthesis" 467 engines="gecko" 468 sub_properties="font-synthesis-weight font-synthesis-style font-synthesis-small-caps font-synthesis-position" 469 derive_value_info="False" 470 spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-variant"> 471 <% sub_properties = ["weight", "style", "small_caps", "position"] %> 472 473 use crate::values::specified::{FontSynthesis, FontSynthesisStyle}; 474 475 pub fn parse_value<'i, 't>( 476 _context: &ParserContext, 477 input: &mut Parser<'i, 't>, 478 ) -> Result<Longhands, ParseError<'i>> { 479 % for prop in sub_properties: 480 % if prop == "style": 481 let mut style = FontSynthesisStyle::None; 482 % else: 483 let mut ${prop} = FontSynthesis::None; 484 % endif 485 % endfor 486 487 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { 488 // Leave all the individual values as None 489 } else { 490 let mut has_custom_value = false; 491 while !input.is_exhausted() { 492 try_match_ident_ignore_ascii_case! { input, 493 % for prop in sub_properties: 494 % if prop == "style": 495 "style" if style == FontSynthesisStyle::None => { 496 has_custom_value = true; 497 style = FontSynthesisStyle::Auto; 498 continue; 499 }, 500 % else: 501 "${prop.replace('_', '-')}" if ${prop} == FontSynthesis::None => { 502 has_custom_value = true; 503 ${prop} = FontSynthesis::Auto; 504 continue; 505 }, 506 % endif 507 % endfor 508 "oblique-only" if style == FontSynthesisStyle::None => { 509 has_custom_value = true; 510 style = FontSynthesisStyle::ObliqueOnly; 511 continue; 512 }, 513 } 514 } 515 if !has_custom_value { 516 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 517 } 518 } 519 520 Ok(expanded! { 521 % for prop in sub_properties: 522 font_synthesis_${prop}: ${prop}, 523 % endfor 524 }) 525 } 526 527 impl<'a> ToCss for LonghandsToSerialize<'a> { 528 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 529 let mut has_any = false; 530 531 % for prop in sub_properties: 532 % if prop == "style": 533 if self.font_synthesis_style != &FontSynthesisStyle::None { 534 if has_any { 535 dest.write_char(' ')?; 536 } 537 has_any = true; 538 if self.font_synthesis_style == &FontSynthesisStyle::Auto { 539 dest.write_str("style")?; 540 } else { 541 dest.write_str("oblique-only")?; 542 } 543 } 544 % else: 545 if self.font_synthesis_${prop} == &FontSynthesis::Auto { 546 if has_any { 547 dest.write_char(' ')?; 548 } 549 has_any = true; 550 dest.write_str("${prop.replace('_', '-')}")?; 551 } 552 % endif 553 % endfor 554 555 if !has_any { 556 dest.write_str("none")?; 557 } 558 559 Ok(()) 560 } 561 } 562 563 // The shorthand takes the sub-property names of the longhands, and not the 564 // 'auto' keyword like they do, so we can't automatically derive this. 565 impl SpecifiedValueInfo for Longhands { 566 fn collect_completion_keywords(f: KeywordsCollectFn) { 567 f(&[ 568 "none", 569 "oblique-only", 570 % for prop in sub_properties: 571 "${prop.replace('_', '-')}", 572 % endfor 573 ]); 574 } 575 } 576 </%helpers:shorthand>