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-2013 servo-2020" 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-ligatures' if engine == 'gecko' else ''} 25 ${'font-variant-numeric' if engine == 'gecko' else ''} 26 ${'font-variant-position' if engine == 'gecko' else ''} 27 ${'font-language-override' if engine == 'gecko' else ''} 28 ${'font-feature-settings' if engine == 'gecko' else ''} 29 ${'font-variation-settings' if engine == 'gecko' else ''} 30 " 31 derive_value_info="False" 32 spec="https://drafts.csswg.org/css-fonts-3/#propdef-font" 33 > 34 use crate::parser::Parse; 35 use crate::properties::longhands::{font_family, font_style, font_weight, font_stretch}; 36 use crate::properties::longhands::font_variant_caps; 37 #[cfg(feature = "gecko")] 38 use crate::properties::longhands::system_font::SystemFont; 39 use crate::values::specified::text::LineHeight; 40 use crate::values::specified::FontSize; 41 use crate::values::specified::font::{FontStretch, FontStretchKeyword}; 42 43 <% 44 gecko_sub_properties = "kerning language_override size_adjust \ 45 variant_alternates variant_east_asian \ 46 variant_ligatures variant_numeric \ 47 variant_position feature_settings \ 48 variation_settings optical_sizing".split() 49 %> 50 % if engine == "gecko": 51 % for prop in gecko_sub_properties: 52 use crate::properties::longhands::font_${prop}; 53 % endfor 54 % endif 55 use self::font_family::SpecifiedValue as FontFamily; 56 parse_value<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Longhands, ParseError<'i>>57 pub fn parse_value<'i, 't>( 58 context: &ParserContext, 59 input: &mut Parser<'i, 't>, 60 ) -> Result<Longhands, ParseError<'i>> { 61 let mut nb_normals = 0; 62 let mut style = None; 63 let mut variant_caps = None; 64 let mut weight = None; 65 let mut stretch = None; 66 let size; 67 % if engine == "gecko": 68 if let Ok(sys) = input.try(SystemFont::parse) { 69 return Ok(expanded! { 70 % for name in SYSTEM_FONT_LONGHANDS: 71 % if name == "font_size": 72 ${name}: FontSize::system_font(sys), 73 % else: 74 ${name}: ${name}::SpecifiedValue::system_font(sys), 75 % endif 76 % endfor 77 // line-height is just reset to initial 78 line_height: LineHeight::normal(), 79 }) 80 } 81 % endif 82 loop { 83 // Special-case 'normal' because it is valid in each of 84 // font-style, font-weight, font-variant and font-stretch. 85 // Leaves the values to None, 'normal' is the initial value for each of them. 86 if input.try(|input| input.expect_ident_matching("normal")).is_ok() { 87 nb_normals += 1; 88 continue; 89 } 90 if style.is_none() { 91 if let Ok(value) = input.try(|input| font_style::parse(context, input)) { 92 style = Some(value); 93 continue 94 } 95 } 96 if weight.is_none() { 97 if let Ok(value) = input.try(|input| font_weight::parse(context, input)) { 98 weight = Some(value); 99 continue 100 } 101 } 102 if variant_caps.is_none() { 103 if let Ok(value) = input.try(|input| font_variant_caps::parse(context, input)) { 104 variant_caps = Some(value); 105 continue 106 } 107 } 108 if stretch.is_none() { 109 if let Ok(value) = input.try(FontStretchKeyword::parse) { 110 stretch = Some(FontStretch::Keyword(value)); 111 continue 112 } 113 } 114 size = Some(FontSize::parse(context, input)?); 115 break 116 } 117 118 let size = match size { 119 Some(s) => s, 120 None => { 121 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 122 } 123 }; 124 125 let line_height = if input.try(|input| input.expect_delim('/')).is_ok() { 126 Some(LineHeight::parse(context, input)?) 127 } else { 128 None 129 }; 130 131 #[inline] 132 fn count<T>(opt: &Option<T>) -> u8 { 133 if opt.is_some() { 1 } else { 0 } 134 } 135 136 if (count(&style) + count(&weight) + count(&variant_caps) + count(&stretch) + nb_normals) > 4 { 137 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 138 } 139 140 let family = FontFamily::parse_specified(input)?; 141 Ok(expanded! { 142 % for name in "style weight stretch variant_caps".split(): 143 font_${name}: unwrap_or_initial!(font_${name}, ${name}), 144 % endfor 145 font_size: size, 146 line_height: line_height.unwrap_or(LineHeight::normal()), 147 font_family: family, 148 % if engine == "gecko": 149 % for name in gecko_sub_properties: 150 font_${name}: font_${name}::get_initial_specified_value(), 151 % endfor 152 % endif 153 }) 154 } 155 156 % if engine == "gecko": 157 enum CheckSystemResult { 158 AllSystem(SystemFont), 159 SomeSystem, 160 None 161 } 162 % endif 163 164 impl<'a> ToCss for LonghandsToSerialize<'a> { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write165 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 166 % if engine == "gecko": 167 match self.check_system() { 168 CheckSystemResult::AllSystem(sys) => return sys.to_css(dest), 169 CheckSystemResult::SomeSystem => return Ok(()), 170 CheckSystemResult::None => {} 171 } 172 % endif 173 174 % if engine == "gecko": 175 if let Some(v) = self.font_optical_sizing { 176 if v != &font_optical_sizing::get_initial_specified_value() { 177 return Ok(()); 178 } 179 } 180 if let Some(v) = self.font_variation_settings { 181 if v != &font_variation_settings::get_initial_specified_value() { 182 return Ok(()); 183 } 184 } 185 186 % for name in gecko_sub_properties: 187 % if name != "optical_sizing" and name != "variation_settings": 188 if self.font_${name} != &font_${name}::get_initial_specified_value() { 189 return Ok(()); 190 } 191 % endif 192 % endfor 193 % endif 194 195 // Only font-stretch keywords are allowed as part as the font 196 // shorthand. 197 let font_stretch = match *self.font_stretch { 198 FontStretch::Keyword(kw) => kw, 199 FontStretch::Stretch(percentage) => { 200 match FontStretchKeyword::from_percentage(percentage.get()) { 201 Some(kw) => kw, 202 None => return Ok(()), 203 } 204 } 205 FontStretch::System(..) => return Ok(()), 206 }; 207 208 % for name in "style variant_caps weight".split(): 209 if self.font_${name} != &font_${name}::get_initial_specified_value() { 210 self.font_${name}.to_css(dest)?; 211 dest.write_str(" ")?; 212 } 213 % endfor 214 215 if font_stretch != FontStretchKeyword::Normal { 216 font_stretch.to_css(dest)?; 217 dest.write_str(" ")?; 218 } 219 220 self.font_size.to_css(dest)?; 221 222 if *self.line_height != LineHeight::normal() { 223 dest.write_str(" / ")?; 224 self.line_height.to_css(dest)?; 225 } 226 227 dest.write_str(" ")?; 228 self.font_family.to_css(dest)?; 229 230 Ok(()) 231 } 232 } 233 234 impl<'a> LonghandsToSerialize<'a> { 235 % if engine == "gecko": 236 /// Check if some or all members are system fonts check_system(&self) -> CheckSystemResult237 fn check_system(&self) -> CheckSystemResult { 238 let mut sys = None; 239 let mut all = true; 240 241 % for prop in SYSTEM_FONT_LONGHANDS: 242 % if prop == "font_optical_sizing" or prop == "font_variation_settings": 243 if let Some(value) = self.${prop} { 244 % else: 245 { 246 let value = self.${prop}; 247 % endif 248 match value.get_system() { 249 Some(s) => { 250 debug_assert!(sys.is_none() || s == sys.unwrap()); 251 sys = Some(s); 252 } 253 None => { 254 all = false; 255 } 256 } 257 } 258 % endfor 259 if self.line_height != &LineHeight::normal() { 260 all = false 261 } 262 if all { 263 CheckSystemResult::AllSystem(sys.unwrap()) 264 } else if sys.is_some() { 265 CheckSystemResult::SomeSystem 266 } else { 267 CheckSystemResult::None 268 } 269 } 270 % endif 271 } 272 273 <% 274 subprops_for_value_info = ["font_style", "font_weight", "font_stretch", 275 "font_variant_caps", "font_size", "font_family"] 276 subprops_for_value_info = [ 277 "<longhands::{}::SpecifiedValue as SpecifiedValueInfo>".format(p) 278 for p in subprops_for_value_info 279 ] 280 %> 281 impl SpecifiedValueInfo for Longhands { 282 const SUPPORTED_TYPES: u8 = 0 283 % for p in subprops_for_value_info: 284 | ${p}::SUPPORTED_TYPES 285 % endfor 286 ; 287 collect_completion_keywords(f: KeywordsCollectFn)288 fn collect_completion_keywords(f: KeywordsCollectFn) { 289 % for p in subprops_for_value_info: 290 ${p}::collect_completion_keywords(f); 291 % endfor 292 <longhands::system_font::SystemFont as SpecifiedValueInfo>::collect_completion_keywords(f); 293 } 294 } 295 </%helpers:shorthand> 296 297 <%helpers:shorthand name="font-variant" 298 engines="gecko servo-2013" 299 flags="SHORTHAND_IN_GETCS" 300 sub_properties="font-variant-caps 301 ${'font-variant-alternates' if engine == 'gecko' else ''} 302 ${'font-variant-east-asian' if engine == 'gecko' else ''} 303 ${'font-variant-ligatures' if engine == 'gecko' else ''} 304 ${'font-variant-numeric' if engine == 'gecko' else ''} 305 ${'font-variant-position' if engine == 'gecko' else ''}" 306 spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-variant"> 307 <% gecko_sub_properties = "alternates east_asian ligatures numeric position".split() %> 308 <% 309 sub_properties = ["caps"] 310 if engine == "gecko": 311 sub_properties += gecko_sub_properties 312 %> 313 314 % for prop in sub_properties: 315 use crate::properties::longhands::font_variant_${prop}; 316 % endfor 317 #[allow(unused_imports)] 318 use crate::values::specified::FontVariantLigatures; 319 parse_value<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Longhands, ParseError<'i>>320 pub fn parse_value<'i, 't>( 321 context: &ParserContext, 322 input: &mut Parser<'i, 't>, 323 ) -> Result<Longhands, ParseError<'i>> { 324 % for prop in sub_properties: 325 let mut ${prop} = None; 326 % endfor 327 328 if input.try(|input| input.expect_ident_matching("normal")).is_ok() { 329 // Leave the values to None, 'normal' is the initial value for all the sub properties. 330 } else if input.try(|input| input.expect_ident_matching("none")).is_ok() { 331 // The 'none' value sets 'font-variant-ligatures' to 'none' and resets all other sub properties 332 // to their initial value. 333 % if engine == "gecko": 334 ligatures = Some(FontVariantLigatures::none()); 335 % endif 336 } else { 337 let mut has_custom_value: bool = false; 338 loop { 339 if input.try(|input| input.expect_ident_matching("normal")).is_ok() || 340 input.try(|input| input.expect_ident_matching("none")).is_ok() { 341 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 342 } 343 % for prop in sub_properties: 344 if ${prop}.is_none() { 345 if let Ok(value) = input.try(|i| font_variant_${prop}::parse(context, i)) { 346 has_custom_value = true; 347 ${prop} = Some(value); 348 continue 349 } 350 } 351 % endfor 352 353 break 354 } 355 356 if !has_custom_value { 357 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 358 } 359 } 360 361 Ok(expanded! { 362 % for prop in sub_properties: 363 font_variant_${prop}: unwrap_or_initial!(font_variant_${prop}, ${prop}), 364 % endfor 365 }) 366 } 367 368 impl<'a> ToCss for LonghandsToSerialize<'a> { 369 #[allow(unused_assignments)] to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write370 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { 371 372 let has_none_ligatures = 373 % if engine == "gecko": 374 self.font_variant_ligatures == &FontVariantLigatures::none(); 375 % else: 376 false; 377 % endif 378 379 const TOTAL_SUBPROPS: usize = ${len(sub_properties)}; 380 let mut nb_normals = 0; 381 % for prop in sub_properties: 382 if self.font_variant_${prop} == &font_variant_${prop}::get_initial_specified_value() { 383 nb_normals += 1; 384 } 385 % endfor 386 387 388 if nb_normals > 0 && nb_normals == TOTAL_SUBPROPS { 389 dest.write_str("normal")?; 390 } else if has_none_ligatures { 391 if nb_normals == TOTAL_SUBPROPS - 1 { 392 // Serialize to 'none' if 'font-variant-ligatures' is set to 'none' and all other 393 // font feature properties are reset to their initial value. 394 dest.write_str("none")?; 395 } else { 396 return Ok(()) 397 } 398 } else { 399 let mut has_any = false; 400 % for prop in sub_properties: 401 if self.font_variant_${prop} != &font_variant_${prop}::get_initial_specified_value() { 402 if has_any { 403 dest.write_str(" ")?; 404 } 405 has_any = true; 406 self.font_variant_${prop}.to_css(dest)?; 407 } 408 % endfor 409 } 410 411 Ok(()) 412 } 413 } 414 </%helpers:shorthand> 415