tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>