Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  parsing.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#![deny(missing_docs)]

//! Parsing for CSS colors.

use super::{
    color_function::ColorFunction,
    component::{ColorComponent, ColorComponentType},
    AbsoluteColor,
};
use crate::{
    parser::{Parse, ParserContext},
    values::{
        generics::{calc::CalcUnits, Optional},
        specified::{angle::Angle as SpecifiedAngle, calc::Leaf, color::Color as SpecifiedColor},
    },
};
use cssparser::{
    color::{parse_hash_color, PredefinedColorSpace, OPAQUE},
    match_ignore_ascii_case, CowRcStr, Parser, Token,
};
use style_traits::{ParseError, StyleParseErrorKind};

/// Returns true if the relative color syntax pref is enabled.
#[inline]
pub fn rcs_enabled() -> bool {
    static_prefs::pref!("layout.css.relative-color-syntax.enabled")
}

/// Represents a channel keyword inside a color.
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, PartialOrd, ToCss, ToShmem)]
#[repr(u8)]
pub enum ChannelKeyword {
    /// alpha
    Alpha,
    /// a
    A,
    /// b, blackness, blue
    B,
    /// chroma
    C,
    /// green
    G,
    /// hue
    H,
    /// lightness
    L,
    /// red
    R,
    /// saturation
    S,
    /// whiteness
    W,
    /// x
    X,
    /// y
    Y,
    /// z
    Z,
}

/// Return the named color with the given name.
///
/// Matching is case-insensitive in the ASCII range.
/// CSS escaping (if relevant) should be resolved before calling this function.
/// (For example, the value of an `Ident` token is fine.)
#[inline]
pub fn parse_color_keyword(ident: &str) -> Result<SpecifiedColor, ()> {
    Ok(match_ignore_ascii_case! { ident,
        "transparent" => {
            SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(0u8, 0u8, 0u8, 0.0))
        },
        "currentcolor" => SpecifiedColor::CurrentColor,
        _ => {
            let (r, g, b) = cssparser::color::parse_named_color(ident)?;
            SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, OPAQUE))
        },
    })
}

/// Parse a CSS color using the specified [`ColorParser`] and return a new color
/// value on success.
pub fn parse_color_with<'i, 't>(
    context: &ParserContext,
    input: &mut Parser<'i, 't>,
) -> Result<SpecifiedColor, ParseError<'i>> {
    let location = input.current_source_location();
    let token = input.next()?;
    match *token {
        Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes())
            .map(|(r, g, b, a)| {
                SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a))
            }),
        Token::Ident(ref value) => parse_color_keyword(value),
        Token::Function(ref name) => {
            let name = name.clone();
            return input.parse_nested_block(|arguments| {
                let color_function = parse_color_function(context, name, arguments)?;

                if color_function.has_origin_color() {
                    // Preserve the color as it was parsed.
                    Ok(SpecifiedColor::ColorFunction(Box::new(color_function)))
                } else if let Ok(resolved) = color_function.resolve_to_absolute() {
                    Ok(SpecifiedColor::from_absolute_color(resolved))
                } else {
                    // This will only happen when the parsed color contains errors like calc units
                    // that cannot be resolved at parse time, but will fail when trying to resolve
                    // them, etc. This should be rare, but for now just failing the color value
                    // makes sense.
                    Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
                }
            });
        },
        _ => Err(()),
    }
    .map_err(|()| location.new_unexpected_token_error(token.clone()))
}

/// Parse one of the color functions: rgba(), lab(), color(), etc.
#[inline]
fn parse_color_function<'i, 't>(
    context: &ParserContext,
    name: CowRcStr<'i>,
    arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
    let origin_color = parse_origin_color(context, arguments)?;
    let has_origin_color = origin_color.is_some();

    let color = match_ignore_ascii_case! { &name,
        "rgb" | "rgba" => parse_rgb(context, arguments, origin_color),
        "hsl" | "hsla" => parse_hsl(context, arguments, origin_color),
        "hwb" => parse_hwb(context, arguments, origin_color),
        "lab" => parse_lab_like(context, arguments, origin_color, ColorFunction::Lab),
        "lch" => parse_lch_like(context, arguments, origin_color, ColorFunction::Lch),
        "oklab" => parse_lab_like(context, arguments, origin_color, ColorFunction::Oklab),
        "oklch" => parse_lch_like(context, arguments, origin_color, ColorFunction::Oklch),
        "color" => parse_color_with_color_space(context, arguments, origin_color),
        _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
    }?;

    if has_origin_color {
        // Validate the channels and calc expressions by trying to resolve them against
        // transparent.
        // FIXME(emilio, bug 1925572): This could avoid cloning, or be done earlier.
        let abs = color.map_origin_color(|_| Some(AbsoluteColor::TRANSPARENT_BLACK));
        if abs.resolve_to_absolute().is_err() {
            return Err(arguments.new_custom_error(StyleParseErrorKind::UnspecifiedError));
        }
    }

    arguments.expect_exhausted()?;

    Ok(color)
}

/// Parse the relative color syntax "from" syntax `from <color>`.
fn parse_origin_color<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
) -> Result<Option<SpecifiedColor>, ParseError<'i>> {
    if !rcs_enabled() {
        return Ok(None);
    }

    // Not finding the from keyword is not an error, it just means we don't
    // have an origin color.
    if arguments
        .try_parse(|p| p.expect_ident_matching("from"))
        .is_err()
    {
        return Ok(None);
    }

    SpecifiedColor::parse(context, arguments).map(Option::Some)
}

#[inline]
fn parse_rgb<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
    origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
    let maybe_red = parse_number_or_percentage(context, arguments, true)?;

    // If the first component is not "none" and is followed by a comma, then we
    // are parsing the legacy syntax.  Legacy syntax also doesn't support an
    // origin color.
    let is_legacy_syntax = origin_color.is_none() &&
        !maybe_red.is_none() &&
        arguments.try_parse(|p| p.expect_comma()).is_ok();

    Ok(if is_legacy_syntax {
        let (green, blue) = if maybe_red.could_be_percentage() {
            let green = parse_percentage(context, arguments, false)?;
            arguments.expect_comma()?;
            let blue = parse_percentage(context, arguments, false)?;
            (green, blue)
        } else {
            let green = parse_number(context, arguments, false)?;
            arguments.expect_comma()?;
            let blue = parse_number(context, arguments, false)?;
            (green, blue)
        };

        let alpha = parse_legacy_alpha(context, arguments)?;

        ColorFunction::Rgb(origin_color.into(), maybe_red, green, blue, alpha)
    } else {
        let green = parse_number_or_percentage(context, arguments, true)?;
        let blue = parse_number_or_percentage(context, arguments, true)?;

        let alpha = parse_modern_alpha(context, arguments)?;

        ColorFunction::Rgb(origin_color.into(), maybe_red, green, blue, alpha)
    })
}

/// Parses hsl syntax.
///
/// <https://drafts.csswg.org/css-color/#the-hsl-notation>
#[inline]
fn parse_hsl<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
    origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
    let hue = parse_number_or_angle(context, arguments, true)?;

    // If the hue is not "none" and is followed by a comma, then we are parsing
    // the legacy syntax. Legacy syntax also doesn't support an origin color.
    let is_legacy_syntax = origin_color.is_none() &&
        !hue.is_none() &&
        arguments.try_parse(|p| p.expect_comma()).is_ok();

    let (saturation, lightness, alpha) = if is_legacy_syntax {
        let saturation = parse_percentage(context, arguments, false)?;
        arguments.expect_comma()?;
        let lightness = parse_percentage(context, arguments, false)?;
        let alpha = parse_legacy_alpha(context, arguments)?;
        (saturation, lightness, alpha)
    } else {
        let saturation = parse_number_or_percentage(context, arguments, true)?;
        let lightness = parse_number_or_percentage(context, arguments, true)?;
        let alpha = parse_modern_alpha(context, arguments)?;
        (saturation, lightness, alpha)
    };

    Ok(ColorFunction::Hsl(
        origin_color.into(),
        hue,
        saturation,
        lightness,
        alpha,
    ))
}

/// Parses hwb syntax.
///
/// <https://drafts.csswg.org/css-color/#the-hbw-notation>
#[inline]
fn parse_hwb<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
    origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
    let hue = parse_number_or_angle(context, arguments, true)?;
    let whiteness = parse_number_or_percentage(context, arguments, true)?;
    let blackness = parse_number_or_percentage(context, arguments, true)?;

    let alpha = parse_modern_alpha(context, arguments)?;

    Ok(ColorFunction::Hwb(
        origin_color.into(),
        hue,
        whiteness,
        blackness,
        alpha,
    ))
}

type IntoLabFn<Output> = fn(
    origin: Optional<SpecifiedColor>,
    l: ColorComponent<NumberOrPercentageComponent>,
    a: ColorComponent<NumberOrPercentageComponent>,
    b: ColorComponent<NumberOrPercentageComponent>,
    alpha: ColorComponent<NumberOrPercentageComponent>,
) -> Output;

#[inline]
fn parse_lab_like<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
    origin_color: Option<SpecifiedColor>,
    into_color: IntoLabFn<ColorFunction<SpecifiedColor>>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
    let lightness = parse_number_or_percentage(context, arguments, true)?;
    let a = parse_number_or_percentage(context, arguments, true)?;
    let b = parse_number_or_percentage(context, arguments, true)?;

    let alpha = parse_modern_alpha(context, arguments)?;

    Ok(into_color(origin_color.into(), lightness, a, b, alpha))
}

type IntoLchFn<Output> = fn(
    origin: Optional<SpecifiedColor>,
    l: ColorComponent<NumberOrPercentageComponent>,
    a: ColorComponent<NumberOrPercentageComponent>,
    b: ColorComponent<NumberOrAngleComponent>,
    alpha: ColorComponent<NumberOrPercentageComponent>,
) -> Output;

#[inline]
fn parse_lch_like<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
    origin_color: Option<SpecifiedColor>,
    into_color: IntoLchFn<ColorFunction<SpecifiedColor>>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
    let lightness = parse_number_or_percentage(context, arguments, true)?;
    let chroma = parse_number_or_percentage(context, arguments, true)?;
    let hue = parse_number_or_angle(context, arguments, true)?;

    let alpha = parse_modern_alpha(context, arguments)?;

    Ok(into_color(
        origin_color.into(),
        lightness,
        chroma,
        hue,
        alpha,
    ))
}

/// Parse the color() function.
#[inline]
fn parse_color_with_color_space<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
    origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
    let color_space = PredefinedColorSpace::parse(arguments)?;

    let c1 = parse_number_or_percentage(context, arguments, true)?;
    let c2 = parse_number_or_percentage(context, arguments, true)?;
    let c3 = parse_number_or_percentage(context, arguments, true)?;

    let alpha = parse_modern_alpha(context, arguments)?;

    Ok(ColorFunction::Color(
        origin_color.into(),
        c1,
        c2,
        c3,
        alpha,
        color_space.into(),
    ))
}

/// Either a percentage or a number.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
#[repr(u8)]
pub enum NumberOrPercentageComponent {
    /// `<number>`.
    Number(f32),
    /// `<percentage>`
    /// The value as a float, divided by 100 so that the nominal range is 0.0 to 1.0.
    Percentage(f32),
}

impl NumberOrPercentageComponent {
    /// Return the value as a number. Percentages will be adjusted to the range
    /// [0..percent_basis].
    pub fn to_number(&self, percentage_basis: f32) -> f32 {
        match *self {
            Self::Number(value) => value,
            Self::Percentage(unit_value) => unit_value * percentage_basis,
        }
    }
}

impl ColorComponentType for NumberOrPercentageComponent {
    fn from_value(value: f32) -> Self {
        Self::Number(value)
    }

    fn units() -> CalcUnits {
        CalcUnits::PERCENTAGE
    }

    fn try_from_token(token: &Token) -> Result<Self, ()> {
        Ok(match *token {
            Token::Number { value, .. } => Self::Number(value),
            Token::Percentage { unit_value, .. } => Self::Percentage(unit_value),
            _ => {
                return Err(());
            },
        })
    }

    fn try_from_leaf(leaf: &Leaf) -> Result<Self, ()> {
        Ok(match *leaf {
            Leaf::Percentage(unit_value) => Self::Percentage(unit_value),
            Leaf::Number(value) => Self::Number(value),
            _ => return Err(()),
        })
    }
}

/// Either an angle or a number.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
#[repr(u8)]
pub enum NumberOrAngleComponent {
    /// `<number>`.
    Number(f32),
    /// `<angle>`
    /// The value as a number of degrees.
    Angle(f32),
}

impl NumberOrAngleComponent {
    /// Return the angle in degrees. `NumberOrAngle::Number` is returned as
    /// degrees, because it is the canonical unit.
    pub fn degrees(&self) -> f32 {
        match *self {
            Self::Number(value) => value,
            Self::Angle(degrees) => degrees,
        }
    }
}

impl ColorComponentType for NumberOrAngleComponent {
    fn from_value(value: f32) -> Self {
        Self::Number(value)
    }

    fn units() -> CalcUnits {
        CalcUnits::ANGLE
    }

    fn try_from_token(token: &Token) -> Result<Self, ()> {
        Ok(match *token {
            Token::Number { value, .. } => Self::Number(value),
            Token::Dimension {
                value, ref unit, ..
            } => {
                let degrees =
                    SpecifiedAngle::parse_dimension(value, unit, /* from_calc = */ false)
                        .map(|angle| angle.degrees())?;

                NumberOrAngleComponent::Angle(degrees)
            },
            _ => {
                return Err(());
            },
        })
    }

    fn try_from_leaf(leaf: &Leaf) -> Result<Self, ()> {
        Ok(match *leaf {
            Leaf::Angle(angle) => Self::Angle(angle.degrees()),
            Leaf::Number(value) => Self::Number(value),
            _ => return Err(()),
        })
    }
}

/// The raw f32 here is for <number>.
impl ColorComponentType for f32 {
    fn from_value(value: f32) -> Self {
        value
    }

    fn units() -> CalcUnits {
        CalcUnits::empty()
    }

    fn try_from_token(token: &Token) -> Result<Self, ()> {
        if let Token::Number { value, .. } = *token {
            Ok(value)
        } else {
            Err(())
        }
    }

    fn try_from_leaf(leaf: &Leaf) -> Result<Self, ()> {
        if let Leaf::Number(value) = *leaf {
            Ok(value)
        } else {
            Err(())
        }
    }
}

/// Parse an `<number>` or `<angle>` value.
fn parse_number_or_angle<'i, 't>(
    context: &ParserContext,
    input: &mut Parser<'i, 't>,
    allow_none: bool,
) -> Result<ColorComponent<NumberOrAngleComponent>, ParseError<'i>> {
    ColorComponent::parse(context, input, allow_none)
}

/// Parse a `<percentage>` value.
fn parse_percentage<'i, 't>(
    context: &ParserContext,
    input: &mut Parser<'i, 't>,
    allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentageComponent>, ParseError<'i>> {
    let location = input.current_source_location();

    let value = ColorComponent::<NumberOrPercentageComponent>::parse(context, input, allow_none)?;
    if !value.could_be_percentage() {
        return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    }

    Ok(value)
}

/// Parse a `<number>` value.
fn parse_number<'i, 't>(
    context: &ParserContext,
    input: &mut Parser<'i, 't>,
    allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentageComponent>, ParseError<'i>> {
    let location = input.current_source_location();

    let value = ColorComponent::<NumberOrPercentageComponent>::parse(context, input, allow_none)?;

    if !value.could_be_number() {
        return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    }

    Ok(value)
}

/// Parse a `<number>` or `<percentage>` value.
fn parse_number_or_percentage<'i, 't>(
    context: &ParserContext,
    input: &mut Parser<'i, 't>,
    allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentageComponent>, ParseError<'i>> {
    ColorComponent::parse(context, input, allow_none)
}

fn parse_legacy_alpha<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
) -> Result<ColorComponent<NumberOrPercentageComponent>, ParseError<'i>> {
    if !arguments.is_exhausted() {
        arguments.expect_comma()?;
        parse_number_or_percentage(context, arguments, false)
    } else {
        Ok(ColorComponent::AlphaOmitted)
    }
}

fn parse_modern_alpha<'i, 't>(
    context: &ParserContext,
    arguments: &mut Parser<'i, 't>,
) -> Result<ColorComponent<NumberOrPercentageComponent>, ParseError<'i>> {
    if !arguments.is_exhausted() {
        arguments.expect_delim('/')?;
        parse_number_or_percentage(context, arguments, true)
    } else {
        Ok(ColorComponent::AlphaOmitted)
    }
}

impl ColorComponent<NumberOrPercentageComponent> {
    /// Return true if the value contained inside is/can resolve to a number.
    /// Also returns false if the node is invalid somehow.
    fn could_be_number(&self) -> bool {
        match self {
            Self::None | Self::AlphaOmitted => true,
            Self::Value(value) => matches!(value, NumberOrPercentageComponent::Number { .. }),
            Self::ChannelKeyword(_) => {
                // Channel keywords always resolve to numbers.
                true
            },
            Self::Calc(node) => {
                if let Ok(unit) = node.unit() {
                    unit.is_empty()
                } else {
                    false
                }
            },
        }
    }

    /// Return true if the value contained inside is/can resolve to a percentage.
    /// Also returns false if the node is invalid somehow.
    fn could_be_percentage(&self) -> bool {
        match self {
            Self::None | Self::AlphaOmitted => true,
            Self::Value(value) => matches!(value, NumberOrPercentageComponent::Percentage { .. }),
            Self::ChannelKeyword(_) => {
                // Channel keywords always resolve to numbers.
                false
            },
            Self::Calc(node) => {
                if let Ok(unit) = node.unit() {
                    unit == CalcUnits::PERCENTAGE
                } else {
                    false
                }
            },
        }
    }
}

[ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge