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