/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/// A struct to hold a simplified `<length>` or `<percentage>` expression. /// /// In some cases, e.g. DOMMatrix, we support calc(), but reject all the /// relative lengths, and to_computed_pixel_length_without_context() handles /// this case. Therefore, if you want to add a new field, please make sure this /// function work properly. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] #[allow(missing_docs)] pubstruct CalcLengthPercentage { #[css(skip)] pub clamping_mode: AllowedNumericType, /// Flag indicating if any anchor function is part of this node. /// This can be used to skip the traversal of calc node tree for /// math functions not using any anchor function. #[css(skip)] pub has_anchor_function: bool, pub node: CalcNode,
}
let a = a.node.as_leaf()?; let b = b.node.as_leaf()?;
if a.sort_key() != b.sort_key() { return None;
}
let a = a.as_length()?.unitless_value(); let b = b.as_length()?.unitless_value(); return Some((a, b));
}
}
impl SpecifiedValueInfo for CalcLengthPercentage {}
/// Should parsing anchor-positioning functions in `calc()` be allowed? #[derive(Clone, Copy, PartialEq)] pubenum AllowAnchorPositioningFunctions { /// Don't allow any anchor positioning function.
No, /// Allow `anchor-size()` to be parsed.
AllowAnchorSize, /// Allow `anchor()` and `anchor-size()` to be parsed.
AllowAnchorAndAnchorSize,
}
bitflags! { /// Additional functions within math functions that are permitted to be parsed depending on /// the context of parsing (e.g. Parsing `inset` allows use of `anchor()` within `calc()`). #[derive(Clone, Copy, PartialEq, Eq)] struct AdditionalFunctions: u8 { /// `anchor()` function. const ANCHOR = 1 << 0; /// `anchor-size()` function. const ANCHOR_SIZE = 1 << 1;
}
}
/// What is allowed to be parsed for math functions within in this context? #[derive(Clone, Copy)] pubstruct AllowParse { /// Units allowed to be parsed.
units: CalcUnits, /// Additional functions allowed to be parsed in this context.
additional_functions: AdditionalFunctions,
}
impl AllowParse { /// Allow only specified units to be parsed, without any additional functions. pubfn new(units: CalcUnits) -> Self { Self {
units,
additional_functions: AdditionalFunctions::empty(),
}
}
/// Add new units to the allowed units to be parsed. fn new_including(mutself, units: CalcUnits) -> Self { self.units |= units; self
}
/// Should given unit be allowed to parse? fn includes(&self, unit: CalcUnits) -> bool { self.units.intersects(unit)
}
}
/// Tries to merge one sum to another, that is, perform `x` + `y`. /// /// Only handles leaf nodes, it's the caller's responsibility to simplify /// them before calling this if needed. fn try_sum_in_place(&mutself, other: &Self) -> Result<(), ()> { useself::Leaf::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) { return Err(());
}
fn try_product_in_place(&mutself, other: &mutSelf) -> bool { ifletSelf::Number(refmut left) = *self { ifletSelf::Number(ref right) = *other { // Both sides are numbers, so we can just modify the left side.
*left *= *right; true
} else { // The right side is not a number, so the result should be in the units of the right // side. if other.map(|v| v * *left).is_ok() {
std::mem::swap(self, other); true
} else { false
}
}
} elseifletSelf::Number(ref right) = *other { // The left side is not a number, but the right side is, so the result is the left // side unit. self.map(|v| v * *right).is_ok()
} else { // Neither side is a number, so a product is not possible. false
}
}
/// Specified `anchor()` function in math functions. pubtype CalcAnchorFunction = generic::GenericCalcAnchorFunction<Leaf>; /// Specified `anchor-size()` function in math functions. pubtype CalcAnchorSizeFunction = generic::GenericCalcAnchorSizeFunction<Leaf>;
/// A calc node representation for specified values. pubtype CalcNode = generic::GenericCalcNode<Leaf>;
/// Result of parsing the calc node. pubstruct ParsedCalcNode { /// The parsed calc node. pub node: CalcNode, /// See documentation for the field of the same name in `CalcLengthPercentage`. pub has_anchor_function: bool,
}
#[inline] fn to_time(&self, clamping_mode: Option<AllowedNumericType>) -> Result<Time, ()> {
debug_assert!(!self.has_anchor_function, "Anchor function used for time?"); self.node.to_time(clamping_mode)
}
#[inline] fn to_resolution(&self) -> Result<Resolution, ()> {
debug_assert!(
!self.has_anchor_function, "Anchor function used for resolution?"
); self.node.to_resolution()
}
#[inline] fn to_angle(&self) -> Result<Angle, ()> {
debug_assert!(!self.has_anchor_function, "Anchor function used for angle?"); self.node.to_angle()
}
#[inline] fn to_number(&self) -> Result<CSSFloat, ()> {
debug_assert!(
!self.has_anchor_function, "Anchor function used for number?"
); self.node.to_number()
}
#[inline] fn to_percentage(&self) -> Result<CSSFloat, ()> {
debug_assert!(
!self.has_anchor_function, "Anchor function used for percentage?"
); self.node.to_percentage()
}
}
impl CalcNode { /// Tries to parse a single element in the expression, that is, a /// `<length>`, `<angle>`, `<time>`, `<percentage>`, `<resolution>`, etc. /// /// May return a "complex" `CalcNode`, in the presence of a parenthesized /// expression, for example. fn parse_one<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allowed: AllowParse,
) -> Result<ParsedCalcNode, ParseError<'i>> { let location = input.current_source_location(); match input.next()? {
&Token::Number { value, .. } => Ok(ParsedCalcNode::new(
CalcNode::Leaf(Leaf::Number(value)), false,
)),
&Token::Dimension {
value, ref unit, ..
} => { if allowed.includes(CalcUnits::LENGTH) { iflet Ok(l) = NoCalcLength::parse_dimension(context, value, unit) { return Ok(ParsedCalcNode::new(CalcNode::Leaf(Leaf::Length(l)), false));
}
} if allowed.includes(CalcUnits::ANGLE) { iflet Ok(a) = Angle::parse_dimension(value, unit, /* from_calc = */ true) { return Ok(ParsedCalcNode::new(CalcNode::Leaf(Leaf::Angle(a)), false));
}
} if allowed.includes(CalcUnits::TIME) { iflet Ok(t) = Time::parse_dimension(value, unit) { return Ok(ParsedCalcNode::new(CalcNode::Leaf(Leaf::Time(t)), false));
}
} if allowed.includes(CalcUnits::RESOLUTION) { iflet Ok(t) = Resolution::parse_dimension(value, unit) { return Ok(ParsedCalcNode::new(
CalcNode::Leaf(Leaf::Resolution(t)), false,
));
}
} return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
},
&Token::Percentage { unit_value, .. } if allowed.includes(CalcUnits::PERCENTAGE) => Ok(
ParsedCalcNode::new(CalcNode::Leaf(Leaf::Percentage(unit_value)), false),
),
&Token::ParenthesisBlock => {
input.parse_nested_block(|input| CalcNode::parse_argument(context, input, allowed))
},
&Token::Function(ref name) if allowed
.additional_functions
.intersects(AdditionalFunctions::ANCHOR) &&
name.eq_ignore_ascii_case("anchor") =>
{ let anchor_function = GenericAnchorFunction::parse_in_calc(context, input)?;
Ok(ParsedCalcNode::new(
CalcNode::Anchor(Box::new(anchor_function)), true,
))
},
&Token::Function(ref name) if allowed
.additional_functions
.intersects(AdditionalFunctions::ANCHOR_SIZE) &&
name.eq_ignore_ascii_case("anchor-size") =>
{ let anchor_size_function =
GenericAnchorSizeFunction::parse_in_calc(context, input)?;
Ok(ParsedCalcNode::new(
CalcNode::AnchorSize(Box::new(anchor_size_function)), true,
))
},
&Token::Function(ref name) => { let function = CalcNode::math_function(context, name, location)?;
CalcNode::parse(context, input, function, allowed)
},
&Token::Ident(ref ident) => { let leaf = match_ignore_ascii_case! { &**ident, "e" => Leaf::Number(std::f32::consts::E), "pi" => Leaf::Number(std::f32::consts::PI), "infinity" => Leaf::Number(f32::INFINITY), "-infinity" => Leaf::Number(f32::NEG_INFINITY), "nan" => Leaf::Number(f32::NAN),
_ => { ifcrate::color::parsing::rcs_enabled() &&
allowed.includes(CalcUnits::COLOR_COMPONENT)
{ iflet Ok(channel_keyword) = ChannelKeyword::from_ident(&ident) {
Leaf::ColorComponent(channel_keyword)
} else { return Err(location
.new_unexpected_token_error(Token::Ident(ident.clone())));
}
} else { return Err(
location.new_unexpected_token_error(Token::Ident(ident.clone()))
);
}
},
};
Ok(ParsedCalcNode::new(CalcNode::Leaf(leaf), false))
},
t => Err(location.new_unexpected_token_error(t.clone())),
}
}
/// Parse a top-level `calc` expression, with all nested sub-expressions. /// /// This is in charge of parsing, for example, `2 + 3 * 100%`. pubfn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
function: MathFunction,
allowed: AllowParse,
) -> Result<ParsedCalcNode, ParseError<'i>> {
input.parse_nested_block(|input| { match function {
MathFunction::Calc => Self::parse_argument(context, input, allowed),
MathFunction::Clamp => { let min = Self::parse_argument(context, input, allowed)?;
input.expect_comma()?; let center = Self::parse_argument(context, input, allowed)?;
input.expect_comma()?; let max = Self::parse_argument(context, input, allowed)?;
Ok(ParsedCalcNode {
node: Self::Clamp {
min: Box::new(min.node),
center: Box::new(center.node),
max: Box::new(max.node),
},
has_anchor_function: min.has_anchor_function ||
center.has_anchor_function ||
max.has_anchor_function,
})
},
MathFunction::Round => { let strategy = input.try_parse(parse_rounding_strategy);
let value = Self::parse_argument(context, input, allowed)?;
// <step> defaults to the number 1 if not provided // https://drafts.csswg.org/css-values-4/#funcdef-round let step = input.try_parse(|input| {
input.expect_comma()?; Self::parse_argument(context, input, allowed)
});
let (step, step_has_anchor_function) = step
.map(|s| (s.node, s.has_anchor_function))
.unwrap_or((Self::Leaf(Leaf::Number(1.0)), false));
let op = match function {
MathFunction::Mod => ModRemOp::Mod,
MathFunction::Rem => ModRemOp::Rem,
_ => unreachable!(),
};
Ok(ParsedCalcNode {
node: Self::ModRem {
dividend: Box::new(dividend.node),
divisor: Box::new(divisor.node),
op,
},
has_anchor_function: dividend.has_anchor_function ||
divisor.has_anchor_function,
})
},
MathFunction::Min | MathFunction::Max => { // TODO(emilio): The common case for parse_comma_separated // is just one element, but for min / max is two, really... // // Consider adding an API to cssparser to specify the // initial vector capacity? letmut has_anchor_function = false; let arguments = input.parse_comma_separated(|input| { let result = Self::parse_argument(context, input, allowed)?;
has_anchor_function |= result.has_anchor_function;
Ok(result.node)
})?;
let op = match function {
MathFunction::Min => MinMaxOp::Min,
MathFunction::Max => MinMaxOp::Max,
_ => unreachable!(),
};
Ok(ParsedCalcNode {
node: Self::MinMax(arguments.into(), op),
has_anchor_function,
})
},
MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => { let a = Self::parse_angle_argument(context, input)?;
let number = match function {
MathFunction::Sin => a.sin(),
MathFunction::Cos => a.cos(),
MathFunction::Tan => a.tan(),
_ => unsafe {
debug_unreachable!("We just checked!");
},
};
Ok(ParsedCalcNode {
node: Self::Leaf(Leaf::Number(number)),
has_anchor_function: false,
})
},
MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => { let a = Self::parse_number_argument(context, input)?;
let radians = match function {
MathFunction::Asin => a.asin(),
MathFunction::Acos => a.acos(),
MathFunction::Atan => a.atan(),
_ => unsafe {
debug_unreachable!("We just checked!");
},
};
Ok(ParsedCalcNode {
node: Self::Leaf(Leaf::Angle(Angle::from_radians(radians))),
has_anchor_function: false,
})
},
MathFunction::Atan2 => { let allow_all = allowed.new_including(CalcUnits::ALL); let a = Self::parse_argument(context, input, allow_all)?; let (a, a_has_anchor_function) = (a.node, a.has_anchor_function);
input.expect_comma()?; let b = Self::parse_argument(context, input, allow_all)?; let (b, b_has_anchor_function) = (b.node, b.has_anchor_function);
let radians = Self::try_resolve(input, || { iflet Ok(a) = a.to_number() { let b = b.to_number()?; return Ok(a.atan2(b));
}
iflet Ok(a) = a.to_percentage() { let b = b.to_percentage()?; return Ok(a.atan2(b));
}
iflet Ok(a) = a.to_time(None) { let b = b.to_time(None)?; return Ok(a.seconds().atan2(b.seconds()));
}
iflet Ok(a) = a.to_angle() { let b = b.to_angle()?; return Ok(a.radians().atan2(b.radians()));
}
iflet Ok(a) = a.to_resolution() { let b = b.to_resolution()?; return Ok(a.dppx().atan2(b.dppx()));
}
let a = a.into_length_or_percentage(
AllowedNumericType::All,
a_has_anchor_function,
)?; let b = b.into_length_or_percentage(
AllowedNumericType::All,
b_has_anchor_function,
)?; let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?;
Ok(a.atan2(b))
})?;
Ok(ParsedCalcNode {
node: Self::Leaf(Leaf::Angle(Angle::from_radians(radians))),
has_anchor_function: a_has_anchor_function || b_has_anchor_function,
})
},
MathFunction::Pow => { let a = Self::parse_number_argument(context, input)?;
input.expect_comma()?; let b = Self::parse_number_argument(context, input)?;
let number = a.powf(b);
Ok(ParsedCalcNode {
node: Self::Leaf(Leaf::Number(number)),
has_anchor_function: false,
})
},
MathFunction::Sqrt => { let a = Self::parse_number_argument(context, input)?;
Ok(ParsedCalcNode {
node: Self::Hypot(arguments.into()),
has_anchor_function,
})
},
MathFunction::Log => { let a = Self::parse_number_argument(context, input)?; let b = input
.try_parse(|input| {
input.expect_comma()?; Self::parse_number_argument(context, input)
})
.ok();
let number = match b {
Some(b) => a.log(b),
None => a.ln(),
};
Ok(ParsedCalcNode {
node: Self::Leaf(Leaf::Number(number)),
has_anchor_function: false,
})
},
MathFunction::Exp => { let a = Self::parse_number_argument(context, input)?; let number = a.exp();
Ok(ParsedCalcNode {
node: Self::Leaf(Leaf::Number(number)),
has_anchor_function: false,
})
},
MathFunction::Abs => { let node = Self::parse_argument(context, input, allowed)?;
Ok(ParsedCalcNode {
node: Self::Abs(Box::new(node.node)),
has_anchor_function: node.has_anchor_function,
})
},
MathFunction::Sign => { // The sign of a percentage is dependent on the percentage basis, so if // percentages aren't allowed (so there's no basis) we shouldn't allow them in // sign(). The rest of the units are safe tho. let node = Self::parse_argument(
context,
input,
allowed.new_including(CalcUnits::ALL - CalcUnits::PERCENTAGE),
)?;
Ok(ParsedCalcNode {
node: Self::Sign(Box::new(node.node)),
has_anchor_function: node.has_anchor_function,
})
},
}
})
}
// We can unwrap here, becuase we start the function by adding a node to // the list. if !product
.last_mut()
.unwrap()
.try_product_in_place(&mut rhs.node)
{ // TODO(dshin): need to combine
product.push(rhs.node);
}
},
Ok(&Token::Delim('/')) => { let rhs = Self::parse_one(context, input, allowed)?;
has_anchor_function |= rhs.has_anchor_function;
enum InPlaceDivisionResult { /// The right was merged into the left.
Merged, /// The right is not a number or could not be resolved, so the left is /// unchanged.
Unchanged, /// The right was resolved, but was not a number, so the calculation is /// invalid.
Invalid,
}
fn try_division_in_place(
left: &mut CalcNode,
right: &CalcNode,
) -> InPlaceDivisionResult { iflet Ok(resolved) = right.resolve() { iflet Some(number) = resolved.as_number() { if number != 1.0 && left.is_product_distributive() { if left.map(|l| l / number).is_err() { return InPlaceDivisionResult::Invalid;
} return InPlaceDivisionResult::Merged;
}
} else { // Color components are valid denominators, but they can't resolve // at parse time. returnif resolved.unit().contains(CalcUnits::COLOR_COMPONENT) {
InPlaceDivisionResult::Unchanged
} else {
InPlaceDivisionResult::Invalid
};
}
}
InPlaceDivisionResult::Unchanged
}
// The right hand side of a division *must* be a number, so if we can // already resolve it, then merge it with the last node on the product list. // We can unwrap here, becuase we start the function by adding a node to // the list. match try_division_in_place(&mut product.last_mut().unwrap(), &rhs.node) {
InPlaceDivisionResult::Merged => {},
InPlaceDivisionResult::Unchanged => {
product.push(Self::Invert(Box::new(rhs.node)))
},
InPlaceDivisionResult::Invalid => { return Err(
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
)
},
}
},
_ => {
input.reset(&start); break;
},
}
}
/// Tries to simplify this expression into a `<length>` or `<percentage>` /// value. pubfn into_length_or_percentage( mutself,
clamping_mode: AllowedNumericType,
has_anchor_function: bool,
) -> Result<CalcLengthPercentage, ()> { self.simplify_and_sort();
// Although we allow numbers inside CalcLengthPercentage, calculations that resolve to a // number result is still not allowed. let unit = self.unit()?; if !CalcUnits::LENGTH_PERCENTAGE.intersects(unit) {
Err(())
} else {
Ok(CalcLengthPercentage {
clamping_mode,
node: self,
has_anchor_function,
})
}
}
/// Tries to simplify this expression into a `<time>` value. fn to_time(&self, clamping_mode: Option<AllowedNumericType>) -> Result<Time, ()> { let seconds = iflet Leaf::Time(time) = self.resolve()? {
time.seconds()
} else { return Err(());
};
/// Tries to simplify the expression into a `<resolution>` value. fn to_resolution(&self) -> Result<Resolution, ()> { let dppx = iflet Leaf::Resolution(resolution) = self.resolve()? {
resolution.dppx()
} else { return Err(());
};
Ok(Resolution::from_dppx_calc(dppx))
}
/// Tries to simplify this expression into an `Angle` value. fn to_angle(&self) -> Result<Angle, ()> { let degrees = iflet Leaf::Angle(angle) = self.resolve()? {
angle.degrees()
} else { return Err(());
};
let result = Angle::from_calc(degrees);
Ok(result)
}
/// Tries to simplify this expression into a `<number>` value. fn to_number(&self) -> Result<CSSFloat, ()> { let number = iflet Leaf::Number(number) = self.resolve()? {
number
} else { return Err(());
};
let result = number;
Ok(result)
}
/// Tries to simplify this expression into a `<percentage>` value. fn to_percentage(&self) -> Result<CSSFloat, ()> { iflet Leaf::Percentage(percentage) = self.resolve()? {
Ok(percentage)
} else {
Err(())
}
}
/// Given a function name, and the location from where the token came from, /// return a mathematical function corresponding to that name or an error. #[inline] pubfn math_function<'i>(
_: &ParserContext,
name: &CowRcStr<'i>,
location: cssparser::SourceLocation,
) -> Result<MathFunction, ParseError<'i>> { let function = match MathFunction::from_ident(&*name) {
Ok(f) => f,
Err(()) => { return Err(location.new_unexpected_token_error(Token::Function(name.clone())))
},
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.