Source code

Revision control

Copy as Markdown

Other Tools

/* 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 https://mozilla.org/MPL/2.0/. */
//! [Length values][length].
//!
use super::{AllowQuirks, Number, Percentage, ToComputedValue};
use crate::computed_value_flags::ComputedValueFlags;
use crate::font_metrics::{FontMetrics, FontMetricsOrientation};
#[cfg(feature = "gecko")]
use crate::gecko_bindings::structs::GeckoFontMetrics;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{self, CSSPixelLength, Context};
use crate::values::generics::length as generics;
use crate::values::generics::length::{
GenericAnchorSizeFunction, GenericLengthOrNumber, GenericLengthPercentageOrNormal,
GenericMargin, GenericMaxSize, GenericSize,
};
use crate::values::generics::NonNegative;
use crate::values::specified::calc::{self, AllowAnchorPositioningFunctions, CalcNode};
use crate::values::specified::NonNegativeNumber;
use crate::values::CSSFloat;
use crate::{Zero, ZeroNoPercent};
use app_units::AU_PER_PX;
use cssparser::{Parser, Token};
use std::cmp;
use std::fmt::{self, Write};
use style_traits::values::specified::AllowedNumericType;
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
pub use super::image::Image;
pub use super::image::{EndingShape as GradientEndingShape, Gradient};
pub use crate::values::specified::calc::CalcLengthPercentage;
/// Number of pixels per inch
pub const PX_PER_IN: CSSFloat = 96.;
/// Number of pixels per centimeter
pub const PX_PER_CM: CSSFloat = PX_PER_IN / 2.54;
/// Number of pixels per millimeter
pub const PX_PER_MM: CSSFloat = PX_PER_IN / 25.4;
/// Number of pixels per quarter
pub const PX_PER_Q: CSSFloat = PX_PER_MM / 4.;
/// Number of pixels per point
pub const PX_PER_PT: CSSFloat = PX_PER_IN / 72.;
/// Number of pixels per pica
pub const PX_PER_PC: CSSFloat = PX_PER_PT * 12.;
/// A font relative length. Note that if any new value is
/// added here, `custom_properties::NonCustomReferences::from_unit`
/// must also be updated. Consult the comment in that function as to why.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
pub enum FontRelativeLength {
#[css(dimension)]
Em(CSSFloat),
#[css(dimension)]
Ex(CSSFloat),
#[css(dimension)]
Ch(CSSFloat),
#[css(dimension)]
Cap(CSSFloat),
#[css(dimension)]
Ic(CSSFloat),
#[css(dimension)]
Rem(CSSFloat),
#[css(dimension)]
Lh(CSSFloat),
#[css(dimension)]
Rlh(CSSFloat),
}
/// A source to resolve font-relative units against
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FontBaseSize {
/// Use the font-size of the current element.
CurrentStyle,
/// Use the inherited font-size.
InheritedStyle,
}
/// A source to resolve font-relative line-height units against.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LineHeightBase {
/// Use the line-height of the current element.
CurrentStyle,
/// Use the inherited line-height.
InheritedStyle,
}
impl FontBaseSize {
/// Calculate the actual size for a given context
pub fn resolve(&self, context: &Context) -> computed::FontSize {
let style = context.style();
match *self {
Self::CurrentStyle => style.get_font().clone_font_size(),
Self::InheritedStyle => {
// If we're using the size from our inherited style, we still need to apply our
// own zoom.
let zoom = style.resolved_specified_zoom();
style.get_parent_font().clone_font_size().zoom(zoom)
},
}
}
}
impl FontRelativeLength {
/// Unit identifier for `em`.
pub const EM: &'static str = "em";
/// Unit identifier for `ex`.
pub const EX: &'static str = "ex";
/// Unit identifier for `ch`.
pub const CH: &'static str = "ch";
/// Unit identifier for `cap`.
pub const CAP: &'static str = "cap";
/// Unit identifier for `ic`.
pub const IC: &'static str = "ic";
/// Unit identifier for `rem`.
pub const REM: &'static str = "rem";
/// Unit identifier for `lh`.
pub const LH: &'static str = "lh";
/// Unit identifier for `rlh`.
pub const RLH: &'static str = "rlh";
/// Return the unitless, raw value.
fn unitless_value(&self) -> CSSFloat {
match *self {
Self::Em(v) |
Self::Ex(v) |
Self::Ch(v) |
Self::Cap(v) |
Self::Ic(v) |
Self::Rem(v) |
Self::Lh(v) |
Self::Rlh(v) => v,
}
}
// Return the unit, as a string.
fn unit(&self) -> &'static str {
match *self {
Self::Em(_) => Self::EM,
Self::Ex(_) => Self::EX,
Self::Ch(_) => Self::CH,
Self::Cap(_) => Self::CAP,
Self::Ic(_) => Self::IC,
Self::Rem(_) => Self::REM,
Self::Lh(_) => Self::LH,
Self::Rlh(_) => Self::RLH,
}
}
fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
where
O: Fn(f32, f32) -> f32,
{
use self::FontRelativeLength::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return Err(());
}
Ok(match (self, other) {
(&Em(one), &Em(other)) => Em(op(one, other)),
(&Ex(one), &Ex(other)) => Ex(op(one, other)),
(&Ch(one), &Ch(other)) => Ch(op(one, other)),
(&Cap(one), &Cap(other)) => Cap(op(one, other)),
(&Ic(one), &Ic(other)) => Ic(op(one, other)),
(&Rem(one), &Rem(other)) => Rem(op(one, other)),
(&Lh(one), &Lh(other)) => Lh(op(one, other)),
(&Rlh(one), &Rlh(other)) => Rlh(op(one, other)),
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) | Lh(..) | Rlh(..) => {},
}
debug_unreachable!("Forgot to handle unit in try_op()")
},
})
}
fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self {
match self {
Self::Em(x) => Self::Em(op(*x)),
Self::Ex(x) => Self::Ex(op(*x)),
Self::Ch(x) => Self::Ch(op(*x)),
Self::Cap(x) => Self::Cap(op(*x)),
Self::Ic(x) => Self::Ic(op(*x)),
Self::Rem(x) => Self::Rem(op(*x)),
Self::Lh(x) => Self::Lh(op(*x)),
Self::Rlh(x) => Self::Rlh(op(*x)),
}
}
/// Computes the font-relative length.
pub fn to_computed_value(
&self,
context: &Context,
base_size: FontBaseSize,
line_height_base: LineHeightBase,
) -> computed::Length {
let (reference_size, length) =
self.reference_font_size_and_length(context, base_size, line_height_base);
(reference_size * length).finite()
}
/// Computes the length, given a GeckoFontMetrics getter to resolve font-relative units.
#[cfg(feature = "gecko")]
pub fn to_computed_pixel_length_with_font_metrics(
&self,
get_font_metrics: impl Fn() -> GeckoFontMetrics,
) -> Result<CSSFloat, ()> {
let metrics = get_font_metrics();
Ok(match *self {
Self::Em(v) => v * metrics.mComputedEmSize.px(),
Self::Ex(v) => v * metrics.mXSize.px(),
Self::Ch(v) => v * metrics.mChSize.px(),
Self::Cap(v) => v * metrics.mCapHeight.px(),
Self::Ic(v) => v * metrics.mIcWidth.px(),
// `lh`, `rlh` & `rem` are unsupported as we have no context for it.
Self::Rem(_) | Self::Lh(_) | Self::Rlh(_) => return Err(()),
})
}
/// Return reference font size.
///
/// We use the base_size flag to pass a different size for computing
/// font-size and unconstrained font-size.
///
/// This returns a pair, the first one is the reference font size, and the
/// second one is the unpacked relative length.
fn reference_font_size_and_length(
&self,
context: &Context,
base_size: FontBaseSize,
line_height_base: LineHeightBase,
) -> (computed::Length, CSSFloat) {
fn query_font_metrics(
context: &Context,
base_size: FontBaseSize,
orientation: FontMetricsOrientation,
) -> FontMetrics {
let retrieve_math_scales = false;
context.query_font_metrics(base_size, orientation, retrieve_math_scales)
}
let reference_font_size = base_size.resolve(context);
match *self {
Self::Em(length) => {
if context.for_non_inherited_property && base_size == FontBaseSize::CurrentStyle {
context
.rule_cache_conditions
.borrow_mut()
.set_font_size_dependency(reference_font_size.computed_size);
}
(reference_font_size.computed_size(), length)
},
Self::Ex(length) => {
// The x-height is an intrinsically horizontal metric.
let metrics =
query_font_metrics(context, base_size, FontMetricsOrientation::Horizontal);
let reference_size = metrics.x_height.unwrap_or_else(|| {
//
// In the cases where it is impossible or impractical to
// determine the x-height, a value of 0.5em must be
// assumed.
//
// (But note we use 0.5em of the used, not computed
// font-size)
reference_font_size.used_size() * 0.5
});
(reference_size, length)
},
Self::Ch(length) => {
//
// Equal to the used advance measure of the “0” (ZERO,
// U+0030) glyph in the font used to render it. (The advance
// measure of a glyph is its advance width or height,
// whichever is in the inline axis of the element.)
//
let metrics = query_font_metrics(
context,
base_size,
FontMetricsOrientation::MatchContextPreferHorizontal,
);
let reference_size = metrics.zero_advance_measure.unwrap_or_else(|| {
//
// In the cases where it is impossible or impractical to
// determine the measure of the “0” glyph, it must be
// assumed to be 0.5em wide by 1em tall. Thus, the ch
// unit falls back to 0.5em in the general case, and to
// 1em when it would be typeset upright (i.e.
// writing-mode is vertical-rl or vertical-lr and
// text-orientation is upright).
//
// Same caveat about computed vs. used font-size applies
// above.
let wm = context.style().writing_mode;
if wm.is_vertical() && wm.is_upright() {
reference_font_size.used_size()
} else {
reference_font_size.used_size() * 0.5
}
});
(reference_size, length)
},
Self::Cap(length) => {
let metrics =
query_font_metrics(context, base_size, FontMetricsOrientation::Horizontal);
let reference_size = metrics.cap_height.unwrap_or_else(|| {
//
// In the cases where it is impossible or impractical to
// determine the cap-height, the font’s ascent must be
// used.
//
metrics.ascent
});
(reference_size, length)
},
Self::Ic(length) => {
let metrics = query_font_metrics(
context,
base_size,
FontMetricsOrientation::MatchContextPreferVertical,
);
let reference_size = metrics.ic_width.unwrap_or_else(|| {
//
// In the cases where it is impossible or impractical to
// determine the ideographic advance measure, it must be
// assumed to be 1em.
//
// Same caveat about computed vs. used as for other
// metric-dependent units.
reference_font_size.used_size()
});
(reference_size, length)
},
Self::Rem(length) => {
//
// When specified on the font-size property of the root
// element, the rem units refer to the property's initial
// value.
//
let reference_size = if context.builder.is_root_element || context.in_media_query {
reference_font_size.computed_size()
} else {
context
.device()
.root_font_size()
.zoom(context.builder.effective_zoom)
};
(reference_size, length)
},
Self::Lh(length) => {
//
// When specified in media-query, the lh units refer to the
// initial values of font and line-height properties.
//
let reference_size = if context.in_media_query {
context
.device()
.calc_line_height(
&context.default_style().get_font(),
context.style().writing_mode,
None,
)
.0
} else {
let line_height = context.builder.calc_line_height(
context.device(),
line_height_base,
context.style().writing_mode,
);
if context.for_non_inherited_property &&
line_height_base == LineHeightBase::CurrentStyle
{
context
.rule_cache_conditions
.borrow_mut()
.set_line_height_dependency(line_height)
}
line_height.0
};
(reference_size, length)
},
Self::Rlh(length) => {
//
// When specified on the root element, the rlh units refer
// to the initial values of font and line-height properties.
//
let reference_size = if context.builder.is_root_element || context.in_media_query {
context
.device()
.calc_line_height(
&context.default_style().get_font(),
context.style().writing_mode,
None,
)
.0
} else {
context.device().root_line_height()
};
let reference_size = reference_size.zoom(context.builder.effective_zoom);
(reference_size, length)
},
}
}
}
}
#[derive(PartialEq)]
enum ViewportUnit {
/// *vw units.
Vw,
/// *vh units.
Vh,
/// *vmin units.
Vmin,
/// *vmax units.
Vmax,
/// *vb units.
Vb,
/// *vi units.
Vi,
}
/// A viewport-relative length.
///
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
pub enum ViewportPercentageLength {
#[css(dimension)]
Vw(CSSFloat),
#[css(dimension)]
Svw(CSSFloat),
#[css(dimension)]
Lvw(CSSFloat),
#[css(dimension)]
Dvw(CSSFloat),
#[css(dimension)]
Vh(CSSFloat),
#[css(dimension)]
Svh(CSSFloat),
#[css(dimension)]
Lvh(CSSFloat),
#[css(dimension)]
Dvh(CSSFloat),
#[css(dimension)]
Vmin(CSSFloat),
#[css(dimension)]
Svmin(CSSFloat),
#[css(dimension)]
Lvmin(CSSFloat),
#[css(dimension)]
Dvmin(CSSFloat),
#[css(dimension)]
Vmax(CSSFloat),
#[css(dimension)]
Svmax(CSSFloat),
#[css(dimension)]
Lvmax(CSSFloat),
#[css(dimension)]
Dvmax(CSSFloat),
#[css(dimension)]
Vb(CSSFloat),
#[css(dimension)]
Svb(CSSFloat),
#[css(dimension)]
Lvb(CSSFloat),
#[css(dimension)]
Dvb(CSSFloat),
#[css(dimension)]
Vi(CSSFloat),
#[css(dimension)]
Svi(CSSFloat),
#[css(dimension)]
Lvi(CSSFloat),
#[css(dimension)]
Dvi(CSSFloat),
}
impl ViewportPercentageLength {
/// Return the unitless, raw value.
fn unitless_value(&self) -> CSSFloat {
self.unpack().2
}
// Return the unit, as a string.
fn unit(&self) -> &'static str {
match *self {
Self::Vw(_) => "vw",
Self::Lvw(_) => "lvw",
Self::Svw(_) => "svw",
Self::Dvw(_) => "dvw",
Self::Vh(_) => "vh",
Self::Svh(_) => "svh",
Self::Lvh(_) => "lvh",
Self::Dvh(_) => "dvh",
Self::Vmin(_) => "vmin",
Self::Svmin(_) => "svmin",
Self::Lvmin(_) => "lvmin",
Self::Dvmin(_) => "dvmin",
Self::Vmax(_) => "vmax",
Self::Svmax(_) => "svmax",
Self::Lvmax(_) => "lvmax",
Self::Dvmax(_) => "dvmax",
Self::Vb(_) => "vb",
Self::Svb(_) => "svb",
Self::Lvb(_) => "lvb",
Self::Dvb(_) => "dvb",
Self::Vi(_) => "vi",
Self::Svi(_) => "svi",
Self::Lvi(_) => "lvi",
Self::Dvi(_) => "dvi",
}
}
fn unpack(&self) -> (ViewportVariant, ViewportUnit, CSSFloat) {
match *self {
Self::Vw(v) => (ViewportVariant::UADefault, ViewportUnit::Vw, v),
Self::Svw(v) => (ViewportVariant::Small, ViewportUnit::Vw, v),
Self::Lvw(v) => (ViewportVariant::Large, ViewportUnit::Vw, v),
Self::Dvw(v) => (ViewportVariant::Dynamic, ViewportUnit::Vw, v),
Self::Vh(v) => (ViewportVariant::UADefault, ViewportUnit::Vh, v),
Self::Svh(v) => (ViewportVariant::Small, ViewportUnit::Vh, v),
Self::Lvh(v) => (ViewportVariant::Large, ViewportUnit::Vh, v),
Self::Dvh(v) => (ViewportVariant::Dynamic, ViewportUnit::Vh, v),
Self::Vmin(v) => (ViewportVariant::UADefault, ViewportUnit::Vmin, v),
Self::Svmin(v) => (ViewportVariant::Small, ViewportUnit::Vmin, v),
Self::Lvmin(v) => (ViewportVariant::Large, ViewportUnit::Vmin, v),
Self::Dvmin(v) => (ViewportVariant::Dynamic, ViewportUnit::Vmin, v),
Self::Vmax(v) => (ViewportVariant::UADefault, ViewportUnit::Vmax, v),
Self::Svmax(v) => (ViewportVariant::Small, ViewportUnit::Vmax, v),
Self::Lvmax(v) => (ViewportVariant::Large, ViewportUnit::Vmax, v),
Self::Dvmax(v) => (ViewportVariant::Dynamic, ViewportUnit::Vmax, v),
Self::Vb(v) => (ViewportVariant::UADefault, ViewportUnit::Vb, v),
Self::Svb(v) => (ViewportVariant::Small, ViewportUnit::Vb, v),
Self::Lvb(v) => (ViewportVariant::Large, ViewportUnit::Vb, v),
Self::Dvb(v) => (ViewportVariant::Dynamic, ViewportUnit::Vb, v),
Self::Vi(v) => (ViewportVariant::UADefault, ViewportUnit::Vi, v),
Self::Svi(v) => (ViewportVariant::Small, ViewportUnit::Vi, v),
Self::Lvi(v) => (ViewportVariant::Large, ViewportUnit::Vi, v),
Self::Dvi(v) => (ViewportVariant::Dynamic, ViewportUnit::Vi, v),
}
}
fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
where
O: Fn(f32, f32) -> f32,
{
use self::ViewportPercentageLength::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return Err(());
}
Ok(match (self, other) {
(&Vw(one), &Vw(other)) => Vw(op(one, other)),
(&Svw(one), &Svw(other)) => Svw(op(one, other)),
(&Lvw(one), &Lvw(other)) => Lvw(op(one, other)),
(&Dvw(one), &Dvw(other)) => Dvw(op(one, other)),
(&Vh(one), &Vh(other)) => Vh(op(one, other)),
(&Svh(one), &Svh(other)) => Svh(op(one, other)),
(&Lvh(one), &Lvh(other)) => Lvh(op(one, other)),
(&Dvh(one), &Dvh(other)) => Dvh(op(one, other)),
(&Vmin(one), &Vmin(other)) => Vmin(op(one, other)),
(&Svmin(one), &Svmin(other)) => Svmin(op(one, other)),
(&Lvmin(one), &Lvmin(other)) => Lvmin(op(one, other)),
(&Dvmin(one), &Dvmin(other)) => Dvmin(op(one, other)),
(&Vmax(one), &Vmax(other)) => Vmax(op(one, other)),
(&Svmax(one), &Svmax(other)) => Svmax(op(one, other)),
(&Lvmax(one), &Lvmax(other)) => Lvmax(op(one, other)),
(&Dvmax(one), &Dvmax(other)) => Dvmax(op(one, other)),
(&Vb(one), &Vb(other)) => Vb(op(one, other)),
(&Svb(one), &Svb(other)) => Svb(op(one, other)),
(&Lvb(one), &Lvb(other)) => Lvb(op(one, other)),
(&Dvb(one), &Dvb(other)) => Dvb(op(one, other)),
(&Vi(one), &Vi(other)) => Vi(op(one, other)),
(&Svi(one), &Svi(other)) => Svi(op(one, other)),
(&Lvi(one), &Lvi(other)) => Lvi(op(one, other)),
(&Dvi(one), &Dvi(other)) => Dvi(op(one, other)),
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | Vh(..) | Svh(..) | Lvh(..) |
Dvh(..) | Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | Vmax(..) |
Svmax(..) | Lvmax(..) | Dvmax(..) | Vb(..) | Svb(..) | Lvb(..) | Dvb(..) |
Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {},
}
debug_unreachable!("Forgot to handle unit in try_op()")
},
})
}
fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self {
match self {
Self::Vw(x) => Self::Vw(op(*x)),
Self::Svw(x) => Self::Svw(op(*x)),
Self::Lvw(x) => Self::Lvw(op(*x)),
Self::Dvw(x) => Self::Dvw(op(*x)),
Self::Vh(x) => Self::Vh(op(*x)),
Self::Svh(x) => Self::Svh(op(*x)),
Self::Lvh(x) => Self::Lvh(op(*x)),
Self::Dvh(x) => Self::Dvh(op(*x)),
Self::Vmin(x) => Self::Vmin(op(*x)),
Self::Svmin(x) => Self::Svmin(op(*x)),
Self::Lvmin(x) => Self::Lvmin(op(*x)),
Self::Dvmin(x) => Self::Dvmin(op(*x)),
Self::Vmax(x) => Self::Vmax(op(*x)),
Self::Svmax(x) => Self::Svmax(op(*x)),
Self::Lvmax(x) => Self::Lvmax(op(*x)),
Self::Dvmax(x) => Self::Dvmax(op(*x)),
Self::Vb(x) => Self::Vb(op(*x)),
Self::Svb(x) => Self::Svb(op(*x)),
Self::Lvb(x) => Self::Lvb(op(*x)),
Self::Dvb(x) => Self::Dvb(op(*x)),
Self::Vi(x) => Self::Vi(op(*x)),
Self::Svi(x) => Self::Svi(op(*x)),
Self::Lvi(x) => Self::Lvi(op(*x)),
Self::Dvi(x) => Self::Dvi(op(*x)),
}
}
/// Computes the given viewport-relative length for the given viewport size.
pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength {
let (variant, unit, factor) = self.unpack();
let size = context.viewport_size_for_viewport_unit_resolution(variant);
let length: app_units::Au = match unit {
ViewportUnit::Vw => size.width,
ViewportUnit::Vh => size.height,
ViewportUnit::Vmin => cmp::min(size.width, size.height),
ViewportUnit::Vmax => cmp::max(size.width, size.height),
ViewportUnit::Vi | ViewportUnit::Vb => {
context
.rule_cache_conditions
.borrow_mut()
.set_writing_mode_dependency(context.builder.writing_mode);
if (unit == ViewportUnit::Vb) == context.style().writing_mode.is_vertical() {
size.width
} else {
size.height
}
},
};
// NOTE: This is in app units!
let length = context.builder.effective_zoom.zoom(length.0 as f32);
// FIXME: Bug 1396535, we need to fix the extremely small viewport length for transform.
// See bug 989802. We truncate so that adding multiple viewport units that add up to 100
// does not overflow due to rounding differences. We convert appUnits to CSS px manually
// here to avoid premature clamping by going through the Au type.
let trunc_scaled =
((length as f64 * factor as f64 / 100.).trunc() / AU_PER_PX as f64) as f32;
CSSPixelLength::new(crate::values::normalize(trunc_scaled))
}
}
/// HTML5 "character width", as defined in HTML5 § 14.5.4.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
#[repr(C)]
pub struct CharacterWidth(pub i32);
impl CharacterWidth {
/// Computes the given character width.
pub fn to_computed_value(&self, reference_font_size: computed::Length) -> computed::Length {
// This applies the *converting a character width to pixels* algorithm
// as specified in HTML5 § 14.5.4.
//
// TODO(pcwalton): Find these from the font.
let average_advance = reference_font_size * 0.5;
let max_advance = reference_font_size;
(average_advance * (self.0 as CSSFloat - 1.0) + max_advance).finite()
}
}
/// Represents an absolute length with its unit
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
pub enum AbsoluteLength {
/// An absolute length in pixels (px)
#[css(dimension)]
Px(CSSFloat),
/// An absolute length in inches (in)
#[css(dimension)]
In(CSSFloat),
/// An absolute length in centimeters (cm)
#[css(dimension)]
Cm(CSSFloat),
/// An absolute length in millimeters (mm)
#[css(dimension)]
Mm(CSSFloat),
/// An absolute length in quarter-millimeters (q)
#[css(dimension)]
Q(CSSFloat),
/// An absolute length in points (pt)
#[css(dimension)]
Pt(CSSFloat),
/// An absolute length in pica (pc)
#[css(dimension)]
Pc(CSSFloat),
}
impl AbsoluteLength {
/// Return the unitless, raw value.
fn unitless_value(&self) -> CSSFloat {
match *self {
Self::Px(v) |
Self::In(v) |
Self::Cm(v) |
Self::Mm(v) |
Self::Q(v) |
Self::Pt(v) |
Self::Pc(v) => v,
}
}
// Return the unit, as a string.
fn unit(&self) -> &'static str {
match *self {
Self::Px(_) => "px",
Self::In(_) => "in",
Self::Cm(_) => "cm",
Self::Mm(_) => "mm",
Self::Q(_) => "q",
Self::Pt(_) => "pt",
Self::Pc(_) => "pc",
}
}
/// Convert this into a pixel value.
#[inline]
pub fn to_px(&self) -> CSSFloat {
match *self {
Self::Px(value) => value,
Self::In(value) => value * PX_PER_IN,
Self::Cm(value) => value * PX_PER_CM,
Self::Mm(value) => value * PX_PER_MM,
Self::Q(value) => value * PX_PER_Q,
Self::Pt(value) => value * PX_PER_PT,
Self::Pc(value) => value * PX_PER_PC,
}
}
fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
where
O: Fn(f32, f32) -> f32,
{
Ok(Self::Px(op(self.to_px(), other.to_px())))
}
fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self {
Self::Px(op(self.to_px()))
}
}
impl ToComputedValue for AbsoluteLength {
type ComputedValue = CSSPixelLength;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
CSSPixelLength::new(self.to_px())
.zoom(context.builder.effective_zoom)
.finite()
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
Self::Px(computed.px())
}
}
impl PartialOrd for AbsoluteLength {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.to_px().partial_cmp(&other.to_px())
}
}
/// A container query length.
///
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
pub enum ContainerRelativeLength {
/// 1% of query container's width
#[css(dimension)]
Cqw(CSSFloat),
/// 1% of query container's height
#[css(dimension)]
Cqh(CSSFloat),
/// 1% of query container's inline size
#[css(dimension)]
Cqi(CSSFloat),
/// 1% of query container's block size
#[css(dimension)]
Cqb(CSSFloat),
/// The smaller value of `cqi` or `cqb`
#[css(dimension)]
Cqmin(CSSFloat),
/// The larger value of `cqi` or `cqb`
#[css(dimension)]
Cqmax(CSSFloat),
}
impl ContainerRelativeLength {
fn unitless_value(&self) -> CSSFloat {
match *self {
Self::Cqw(v) |
Self::Cqh(v) |
Self::Cqi(v) |
Self::Cqb(v) |
Self::Cqmin(v) |
Self::Cqmax(v) => v,
}
}
// Return the unit, as a string.
fn unit(&self) -> &'static str {
match *self {
Self::Cqw(_) => "cqw",
Self::Cqh(_) => "cqh",
Self::Cqi(_) => "cqi",
Self::Cqb(_) => "cqb",
Self::Cqmin(_) => "cqmin",
Self::Cqmax(_) => "cqmax",
}
}
pub(crate) fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
where
O: Fn(f32, f32) -> f32,
{
use self::ContainerRelativeLength::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return Err(());
}
Ok(match (self, other) {
(&Cqw(one), &Cqw(other)) => Cqw(op(one, other)),
(&Cqh(one), &Cqh(other)) => Cqh(op(one, other)),
(&Cqi(one), &Cqi(other)) => Cqi(op(one, other)),
(&Cqb(one), &Cqb(other)) => Cqb(op(one, other)),
(&Cqmin(one), &Cqmin(other)) => Cqmin(op(one, other)),
(&Cqmax(one), &Cqmax(other)) => Cqmax(op(one, other)),
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Cqw(..) | Cqh(..) | Cqi(..) | Cqb(..) | Cqmin(..) | Cqmax(..) => {},
}
debug_unreachable!("Forgot to handle unit in try_op()")
},
})
}
pub(crate) fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self {
match self {
Self::Cqw(x) => Self::Cqw(op(*x)),
Self::Cqh(x) => Self::Cqh(op(*x)),
Self::Cqi(x) => Self::Cqi(op(*x)),
Self::Cqb(x) => Self::Cqb(op(*x)),
Self::Cqmin(x) => Self::Cqmin(op(*x)),
Self::Cqmax(x) => Self::Cqmax(op(*x)),
}
}
/// Computes the given container-relative length.
pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength {
if context.for_non_inherited_property {
context.rule_cache_conditions.borrow_mut().set_uncacheable();
}
context
.builder
.add_flags(ComputedValueFlags::USES_CONTAINER_UNITS);
// TODO(emilio, bug 1894104): Need to handle zoom here, probably something like
// container_zoom - effective_zoom or so. See
let size = context.get_container_size_query();
let (factor, container_length) = match *self {
Self::Cqw(v) => (v, size.get_container_width(context)),
Self::Cqh(v) => (v, size.get_container_height(context)),
Self::Cqi(v) => (v, size.get_container_inline_size(context)),
Self::Cqb(v) => (v, size.get_container_block_size(context)),
Self::Cqmin(v) => (
v,
cmp::min(
size.get_container_inline_size(context),
size.get_container_block_size(context),
),
),
Self::Cqmax(v) => (
v,
cmp::max(
size.get_container_inline_size(context),
size.get_container_block_size(context),
),
),
};
CSSPixelLength::new((container_length.to_f64_px() * factor as f64 / 100.0) as f32).finite()
}
}
/// A `<length>` without taking `calc` expressions into account
///
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[repr(u8)]
pub enum NoCalcLength {
/// An absolute length
///
Absolute(AbsoluteLength),
/// A font-relative length:
///
FontRelative(FontRelativeLength),
/// A viewport-relative length.
///
ViewportPercentage(ViewportPercentageLength),
/// A container query length.
///
ContainerRelative(ContainerRelativeLength),
/// HTML5 "character width", as defined in HTML5 § 14.5.4.
///
/// This cannot be specified by the user directly and is only generated by
/// `Stylist::synthesize_rules_for_legacy_attributes()`.
ServoCharacterWidth(CharacterWidth),
}
impl NoCalcLength {
/// Return the unitless, raw value.
pub fn unitless_value(&self) -> CSSFloat {
match *self {
Self::Absolute(v) => v.unitless_value(),
Self::FontRelative(v) => v.unitless_value(),
Self::ViewportPercentage(v) => v.unitless_value(),
Self::ContainerRelative(v) => v.unitless_value(),
Self::ServoCharacterWidth(c) => c.0 as f32,
}
}
// Return the unit, as a string.
fn unit(&self) -> &'static str {
match *self {
Self::Absolute(v) => v.unit(),
Self::FontRelative(v) => v.unit(),
Self::ViewportPercentage(v) => v.unit(),
Self::ContainerRelative(v) => v.unit(),
Self::ServoCharacterWidth(_) => "",
}
}
/// Returns whether the value of this length without unit is less than zero.
pub fn is_negative(&self) -> bool {
self.unitless_value().is_sign_negative()
}
/// Returns whether the value of this length without unit is equal to zero.
pub fn is_zero(&self) -> bool {
self.unitless_value() == 0.0
}
/// Returns whether the value of this length without unit is infinite.
pub fn is_infinite(&self) -> bool {
self.unitless_value().is_infinite()
}
/// Returns whether the value of this length without unit is NaN.
pub fn is_nan(&self) -> bool {
self.unitless_value().is_nan()
}
/// Whether text-only zoom should be applied to this length.
///
/// Generally, font-dependent/relative units don't get text-only-zoomed,
/// because the font they're relative to should be zoomed already.
pub fn should_zoom_text(&self) -> bool {
match *self {
Self::Absolute(..) | Self::ViewportPercentage(..) | Self::ContainerRelative(..) => true,
Self::ServoCharacterWidth(..) | Self::FontRelative(..) => false,
}
}
/// Parse a given absolute or relative dimension.
pub fn parse_dimension(
context: &ParserContext,
value: CSSFloat,
unit: &str,
) -> Result<Self, ()> {
Ok(match_ignore_ascii_case! { unit,
"px" => Self::Absolute(AbsoluteLength::Px(value)),
"in" => Self::Absolute(AbsoluteLength::In(value)),
"cm" => Self::Absolute(AbsoluteLength::Cm(value)),
"mm" => Self::Absolute(AbsoluteLength::Mm(value)),
"q" => Self::Absolute(AbsoluteLength::Q(value)),
"pt" => Self::Absolute(AbsoluteLength::Pt(value)),
"pc" => Self::Absolute(AbsoluteLength::Pc(value)),
// font-relative
"em" if context.allows_computational_dependence() => Self::FontRelative(FontRelativeLength::Em(value)),
"ex" if context.allows_computational_dependence() => Self::FontRelative(FontRelativeLength::Ex(value)),
"ch" if context.allows_computational_dependence() => Self::FontRelative(FontRelativeLength::Ch(value)),
"cap" if context.allows_computational_dependence() => Self::FontRelative(FontRelativeLength::Cap(value)),
"ic" if context.allows_computational_dependence() => Self::FontRelative(FontRelativeLength::Ic(value)),
"rem" if context.allows_computational_dependence() => Self::FontRelative(FontRelativeLength::Rem(value)),
"lh" if context.allows_computational_dependence() => Self::FontRelative(FontRelativeLength::Lh(value)),
"rlh" if context.allows_computational_dependence() => Self::FontRelative(FontRelativeLength::Rlh(value)),
// viewport percentages
"vw" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Vw(value))
},
"svw" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Svw(value))
},
"lvw" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Lvw(value))
},
"dvw" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Dvw(value))
},
"vh" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Vh(value))
},
"svh" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Svh(value))
},
"lvh" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Lvh(value))
},
"dvh" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Dvh(value))
},
"vmin" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Vmin(value))
},
"svmin" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Svmin(value))
},
"lvmin" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Lvmin(value))
},
"dvmin" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Dvmin(value))
},
"vmax" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Vmax(value))
},
"svmax" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Svmax(value))
},
"lvmax" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Lvmax(value))
},
"dvmax" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Dvmax(value))
},
"vb" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Vb(value))
},
"svb" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Svb(value))
},
"lvb" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Lvb(value))
},
"dvb" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Dvb(value))
},
"vi" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Vi(value))
},
"svi" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Svi(value))
},
"lvi" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Lvi(value))
},
"dvi" if !context.in_page_rule() => {
Self::ViewportPercentage(ViewportPercentageLength::Dvi(value))
},
// Container query lengths. Inherit the limitation from viewport units since
// we may fall back to them.
"cqw" if !context.in_page_rule() && cfg!(feature = "gecko") => {
Self::ContainerRelative(ContainerRelativeLength::Cqw(value))
},
"cqh" if !context.in_page_rule() && cfg!(feature = "gecko") => {
Self::ContainerRelative(ContainerRelativeLength::Cqh(value))
},
"cqi" if !context.in_page_rule() && cfg!(feature = "gecko") => {
Self::ContainerRelative(ContainerRelativeLength::Cqi(value))
},
"cqb" if !context.in_page_rule() && cfg!(feature = "gecko") => {
Self::ContainerRelative(ContainerRelativeLength::Cqb(value))
},
"cqmin" if !context.in_page_rule() && cfg!(feature = "gecko") => {
Self::ContainerRelative(ContainerRelativeLength::Cqmin(value))
},
"cqmax" if !context.in_page_rule() && cfg!(feature = "gecko") => {
Self::ContainerRelative(ContainerRelativeLength::Cqmax(value))
},
_ => return Err(()),
})
}
pub(crate) fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
where
O: Fn(f32, f32) -> f32,
{
use self::NoCalcLength::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return Err(());
}
Ok(match (self, other) {
(&Absolute(ref one), &Absolute(ref other)) => Absolute(one.try_op(other, op)?),
(&FontRelative(ref one), &FontRelative(ref other)) => {
FontRelative(one.try_op(other, op)?)
},
(&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => {
ViewportPercentage(one.try_op(other, op)?)
},
(&ContainerRelative(ref one), &ContainerRelative(ref other)) => {
ContainerRelative(one.try_op(other, op)?)
},
(&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => {
ServoCharacterWidth(CharacterWidth(op(one.0 as f32, other.0 as f32) as i32))
},
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Absolute(..) |
FontRelative(..) |
ViewportPercentage(..) |
ContainerRelative(..) |
ServoCharacterWidth(..) => {},
}
debug_unreachable!("Forgot to handle unit in try_op()")
},
})
}
pub(crate) fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self {
use self::NoCalcLength::*;
match self {
Absolute(ref one) => Absolute(one.map(op)),
FontRelative(ref one) => FontRelative(one.map(op)),
ViewportPercentage(ref one) => ViewportPercentage(one.map(op)),
ContainerRelative(ref one) => ContainerRelative(one.map(op)),
ServoCharacterWidth(ref one) => {
ServoCharacterWidth(CharacterWidth(op(one.0 as f32) as i32))
},
}
}
/// Get a px value without context (so only absolute units can be handled).
#[inline]
pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
match *self {
Self::Absolute(len) => Ok(CSSPixelLength::new(len.to_px()).finite().px()),
_ => Err(()),
}
}
/// Get a px value without a full style context; this can handle either
/// absolute or (if a font metrics getter is provided) font-relative units.
#[cfg(feature = "gecko")]
#[inline]
pub fn to_computed_pixel_length_with_font_metrics(
&self,
get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
) -> Result<CSSFloat, ()> {
match *self {
Self::Absolute(len) => Ok(CSSPixelLength::new(len.to_px()).finite().px()),
Self::FontRelative(fr) => {
if let Some(getter) = get_font_metrics {
fr.to_computed_pixel_length_with_font_metrics(getter)
} else {
Err(())
}
},
_ => Err(()),
}
}
/// Get an absolute length from a px value.
#[inline]
pub fn from_px(px_value: CSSFloat) -> NoCalcLength {
NoCalcLength::Absolute(AbsoluteLength::Px(px_value))
}
}
impl ToCss for NoCalcLength {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
crate::values::serialize_specified_dimension(
self.unitless_value(),
self.unit(),
false,
dest,
)
}
}
impl SpecifiedValueInfo for NoCalcLength {}
impl PartialOrd for NoCalcLength {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
use self::NoCalcLength::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return None;
}
match (self, other) {
(&Absolute(ref one), &Absolute(ref other)) => one.to_px().partial_cmp(&other.to_px()),
(&FontRelative(ref one), &FontRelative(ref other)) => one.partial_cmp(other),
(&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => {
one.partial_cmp(other)
},
(&ContainerRelative(ref one), &ContainerRelative(ref other)) => one.partial_cmp(other),
(&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => {
one.0.partial_cmp(&other.0)
},
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Absolute(..) |
FontRelative(..) |
ViewportPercentage(..) |
ContainerRelative(..) |
ServoCharacterWidth(..) => {},
}
debug_unreachable!("Forgot an arm in partial_cmp?")
},
}
}
}
impl Zero for NoCalcLength {
fn zero() -> Self {
NoCalcLength::Absolute(AbsoluteLength::Px(0.))
}
fn is_zero(&self) -> bool {
NoCalcLength::is_zero(self)
}
}
/// An extension to `NoCalcLength` to parse `calc` expressions.
/// This is commonly used for the `<length>` values.
///
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub enum Length {
/// The internal length type that cannot parse `calc`
NoCalc(NoCalcLength),
/// A calc expression.
///
Calc(Box<CalcLengthPercentage>),
}
impl From<NoCalcLength> for Length {
#[inline]
fn from(len: NoCalcLength) -> Self {
Length::NoCalc(len)
}
}
impl PartialOrd for FontRelativeLength {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
use self::FontRelativeLength::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return None;
}
match (self, other) {
(&Em(ref one), &Em(ref other)) => one.partial_cmp(other),
(&Ex(ref one), &Ex(ref other)) => one.partial_cmp(other),
(&Ch(ref one), &Ch(ref other)) => one.partial_cmp(other),
(&Cap(ref one), &Cap(ref other)) => one.partial_cmp(other),
(&Ic(ref one), &Ic(ref other)) => one.partial_cmp(other),
(&Rem(ref one), &Rem(ref other)) => one.partial_cmp(other),
(&Lh(ref one), &Lh(ref other)) => one.partial_cmp(other),
(&Rlh(ref one), &Rlh(ref other)) => one.partial_cmp(other),
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) | Lh(..) | Rlh(..) => {},
}
debug_unreachable!("Forgot an arm in partial_cmp?")
},
}
}
}
impl PartialOrd for ContainerRelativeLength {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
use self::ContainerRelativeLength::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return None;
}
match (self, other) {
(&Cqw(ref one), &Cqw(ref other)) => one.partial_cmp(other),
(&Cqh(ref one), &Cqh(ref other)) => one.partial_cmp(other),
(&Cqi(ref one), &Cqi(ref other)) => one.partial_cmp(other),
(&Cqb(ref one), &Cqb(ref other)) => one.partial_cmp(other),
(&Cqmin(ref one), &Cqmin(ref other)) => one.partial_cmp(other),
(&Cqmax(ref one), &Cqmax(ref other)) => one.partial_cmp(other),
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Cqw(..) | Cqh(..) | Cqi(..) | Cqb(..) | Cqmin(..) | Cqmax(..) => {},
}
debug_unreachable!("Forgot to handle unit in partial_cmp()")
},
}
}
}
impl PartialOrd for ViewportPercentageLength {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
use self::ViewportPercentageLength::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return None;
}
match (self, other) {
(&Vw(ref one), &Vw(ref other)) => one.partial_cmp(other),
(&Svw(ref one), &Svw(ref other)) => one.partial_cmp(other),
(&Lvw(ref one), &Lvw(ref other)) => one.partial_cmp(other),
(&Dvw(ref one), &Dvw(ref other)) => one.partial_cmp(other),
(&Vh(ref one), &Vh(ref other)) => one.partial_cmp(other),
(&Svh(ref one), &Svh(ref other)) => one.partial_cmp(other),
(&Lvh(ref one), &Lvh(ref other)) => one.partial_cmp(other),
(&Dvh(ref one), &Dvh(ref other)) => one.partial_cmp(other),
(&Vmin(ref one), &Vmin(ref other)) => one.partial_cmp(other),
(&Svmin(ref one), &Svmin(ref other)) => one.partial_cmp(other),
(&Lvmin(ref one), &Lvmin(ref other)) => one.partial_cmp(other),
(&Dvmin(ref one), &Dvmin(ref other)) => one.partial_cmp(other),
(&Vmax(ref one), &Vmax(ref other)) => one.partial_cmp(other),
(&Svmax(ref one), &Svmax(ref other)) => one.partial_cmp(other),
(&Lvmax(ref one), &Lvmax(ref other)) => one.partial_cmp(other),
(&Dvmax(ref one), &Dvmax(ref other)) => one.partial_cmp(other),
(&Vb(ref one), &Vb(ref other)) => one.partial_cmp(other),
(&Svb(ref one), &Svb(ref other)) => one.partial_cmp(other),
(&Lvb(ref one), &Lvb(ref other)) => one.partial_cmp(other),
(&Dvb(ref one), &Dvb(ref other)) => one.partial_cmp(other),
(&Vi(ref one), &Vi(ref other)) => one.partial_cmp(other),
(&Svi(ref one), &Svi(ref other)) => one.partial_cmp(other),
(&Lvi(ref one), &Lvi(ref other)) => one.partial_cmp(other),
(&Dvi(ref one), &Dvi(ref other)) => one.partial_cmp(other),
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | Vh(..) | Svh(..) | Lvh(..) |
Dvh(..) | Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | Vmax(..) |
Svmax(..) | Lvmax(..) | Dvmax(..) | Vb(..) | Svb(..) | Lvb(..) | Dvb(..) |
Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {},
}
debug_unreachable!("Forgot an arm in partial_cmp?")
},
}
}
}
impl Length {
#[inline]
fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
num_context: AllowedNumericType,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
let token = input.next()?;
match *token {
Token::Dimension {
value, ref unit, ..
} if num_context.is_ok(context.parsing_mode, value) => {
NoCalcLength::parse_dimension(context, value, unit)
.map(Length::NoCalc)
.map_err(|()| location.new_unexpected_token_error(token.clone()))
},
Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
if value != 0. &&
!context.parsing_mode.allows_unitless_lengths() &&
!allow_quirks.allowed(context.quirks_mode)
{
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(Length::NoCalc(NoCalcLength::Absolute(AbsoluteLength::Px(
value,
))))
},
Token::Function(ref name) => {
let function = CalcNode::math_function(context, name, location)?;
let calc = CalcNode::parse_length(context, input, num_context, function)?;
Ok(Length::Calc(Box::new(calc)))
},
ref token => return Err(location.new_unexpected_token_error(token.clone())),
}
}
/// Parse a non-negative length
#[inline]
pub fn parse_non_negative<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_non_negative_quirky(context, input, AllowQuirks::No)
}
/// Parse a non-negative length, allowing quirks.
#[inline]
pub fn parse_non_negative_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(
context,
input,
AllowedNumericType::NonNegative,
allow_quirks,
)
}
/// Get an absolute length from a px value.
#[inline]
pub fn from_px(px_value: CSSFloat) -> Length {
Length::NoCalc(NoCalcLength::from_px(px_value))
}
/// Get a px value without context.
pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
match *self {
Self::NoCalc(ref l) => l.to_computed_pixel_length_without_context(),
Self::Calc(ref l) => l.to_computed_pixel_length_without_context(),
}
}
/// Get a px value, with an optional GeckoFontMetrics getter to resolve font-relative units.
#[cfg(feature = "gecko")]
pub fn to_computed_pixel_length_with_font_metrics(
&self,
get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
) -> Result<CSSFloat, ()> {
match *self {
Self::NoCalc(ref l) => l.to_computed_pixel_length_with_font_metrics(get_font_metrics),
Self::Calc(ref l) => l.to_computed_pixel_length_with_font_metrics(get_font_metrics),
}
}
}
impl Parse for Length {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_quirky(context, input, AllowQuirks::No)
}
}
impl Zero for Length {
fn zero() -> Self {
Length::NoCalc(NoCalcLength::zero())
}
fn is_zero(&self) -> bool {
// FIXME(emilio): Seems a bit weird to treat calc() unconditionally as
// non-zero here?
match *self {
Length::NoCalc(ref l) => l.is_zero(),
Length::Calc(..) => false,
}
}
}
impl Length {
/// Parses a length, with quirks.
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(context, input, AllowedNumericType::All, allow_quirks)
}
}
/// A wrapper of Length, whose value must be >= 0.
pub type NonNegativeLength = NonNegative<Length>;
impl Parse for NonNegativeLength {
#[inline]
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Ok(NonNegative(Length::parse_non_negative(context, input)?))
}
}
impl From<NoCalcLength> for NonNegativeLength {
#[inline]
fn from(len: NoCalcLength) -> Self {
NonNegative(Length::NoCalc(len))
}
}
impl From<Length> for NonNegativeLength {
#[inline]
fn from(len: Length) -> Self {
NonNegative(len)
}
}
impl NonNegativeLength {
/// Get an absolute length from a px value.
#[inline]
pub fn from_px(px_value: CSSFloat) -> Self {
Length::from_px(px_value.max(0.)).into()
}
/// Parses a non-negative length, optionally with quirks.
#[inline]
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Ok(NonNegative(Length::parse_non_negative_quirky(
context,
input,
allow_quirks,
)?))
}
}
/// A `<length-percentage>` value. This can be either a `<length>`, a
/// `<percentage>`, or a combination of both via `calc()`.
///
#[allow(missing_docs)]
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub enum LengthPercentage {
Length(NoCalcLength),
Percentage(computed::Percentage),
Calc(Box<CalcLengthPercentage>),
}
impl From<Length> for LengthPercentage {
fn from(len: Length) -> LengthPercentage {
match len {
Length::NoCalc(l) => LengthPercentage::Length(l),
Length::Calc(l) => LengthPercentage::Calc(l),
}
}
}
impl From<NoCalcLength> for LengthPercentage {
#[inline]
fn from(len: NoCalcLength) -> Self {
LengthPercentage::Length(len)
}
}
impl From<Percentage> for LengthPercentage {
#[inline]
fn from(pc: Percentage) -> Self {
if let Some(clamping_mode) = pc.calc_clamping_mode() {
LengthPercentage::Calc(Box::new(CalcLengthPercentage {
clamping_mode,
node: CalcNode::Leaf(calc::Leaf::Percentage(pc.get())),
}))
} else {
LengthPercentage::Percentage(computed::Percentage(pc.get()))
}
}
}
impl From<computed::Percentage> for LengthPercentage {
#[inline]
fn from(pc: computed::Percentage) -> Self {
LengthPercentage::Percentage(pc)
}
}
impl Parse for LengthPercentage {
#[inline]
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_quirky(context, input, AllowQuirks::No)
}
}
impl LengthPercentage {
#[inline]
/// Returns a `0%` value.
pub fn zero_percent() -> LengthPercentage {
LengthPercentage::Percentage(computed::Percentage::zero())
}
#[inline]
/// Returns a `100%` value.
pub fn hundred_percent() -> LengthPercentage {
LengthPercentage::Percentage(computed::Percentage::hundred())
}
fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
num_context: AllowedNumericType,
allow_quirks: AllowQuirks,
allow_anchor: AllowAnchorPositioningFunctions,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
let token = input.next()?;
match *token {
Token::Dimension {
value, ref unit, ..
} if num_context.is_ok(context.parsing_mode, value) => {
return NoCalcLength::parse_dimension(context, value, unit)
.map(LengthPercentage::Length)
.map_err(|()| location.new_unexpected_token_error(token.clone()));
},
Token::Percentage { unit_value, .. }
if num_context.is_ok(context.parsing_mode, unit_value) =>
{
return Ok(LengthPercentage::Percentage(computed::Percentage(
unit_value,
)));
},
Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
if value != 0. &&
!context.parsing_mode.allows_unitless_lengths() &&
!allow_quirks.allowed(context.quirks_mode)
{
return Err(location.new_unexpected_token_error(token.clone()));
} else {
return Ok(LengthPercentage::Length(NoCalcLength::from_px(value)));
}
},
Token::Function(ref name) => {
let function = CalcNode::math_function(context, name, location)?;
let calc = CalcNode::parse_length_or_percentage(context, input, num_context, function, allow_anchor)?;
Ok(LengthPercentage::Calc(Box::new(calc)))
},
_ => return Err(location.new_unexpected_token_error(token.clone())),
}
}
/// Parses allowing the unitless length quirk.
#[inline]
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(
context,
input,
AllowedNumericType::All,
allow_quirks,
AllowAnchorPositioningFunctions::No,
)
}
/// Parses allowing the unitless length quirk, as well as allowing
/// anchor-positioning related functions, `anchor()` and `anchor-size()`.
#[inline]
pub fn parse_quirky_with_anchor_functions<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(
context,
input,
AllowedNumericType::All,
allow_quirks,
AllowAnchorPositioningFunctions::AllowAnchorAndAnchorSize,
)
}
/// Parses non-negative length, allowing the unitless length quirk,
/// as well as allowing `anchor-size()`.
pub fn parse_non_negative_with_anchor_size<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(
context,
input,
AllowedNumericType::NonNegative,
allow_quirks,
AllowAnchorPositioningFunctions::AllowAnchorSize,
)
}
/// Parse a non-negative length.
///
/// FIXME(emilio): This should be not public and we should use
/// NonNegativeLengthPercentage instead.
#[inline]
pub fn parse_non_negative<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_non_negative_quirky(context, input, AllowQuirks::No)
}
/// Parse a non-negative length, with quirks.
#[inline]
pub fn parse_non_negative_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(
context,
input,
AllowedNumericType::NonNegative,
allow_quirks,
AllowAnchorPositioningFunctions::No,
)
}
/// Returns self as specified::calc::CalcNode.
/// Note that this expect the clamping_mode is AllowedNumericType::All for Calc. The caller
/// should take care about it when using this function.
fn to_calc_node(self) -> CalcNode {
match self {
LengthPercentage::Length(l) => CalcNode::Leaf(calc::Leaf::Length(l)),
LengthPercentage::Percentage(p) => CalcNode::Leaf(calc::Leaf::Percentage(p.0)),
LengthPercentage::Calc(p) => p.node,
}
}
/// Construct the value representing `calc(100% - self)`.
pub fn hundred_percent_minus(self, clamping_mode: AllowedNumericType) -> Self {
let mut sum = smallvec::SmallVec::<[CalcNode; 2]>::new();
sum.push(CalcNode::Leaf(calc::Leaf::Percentage(1.0)));
let mut node = self.to_calc_node();
node.negate();
sum.push(node);
let calc = CalcNode::Sum(sum.into_boxed_slice().into());
LengthPercentage::Calc(Box::new(
calc.into_length_or_percentage(clamping_mode).unwrap(),
))
}
}
impl Zero for LengthPercentage {
fn zero() -> Self {
LengthPercentage::Length(NoCalcLength::zero())
}
fn is_zero(&self) -> bool {
match *self {
LengthPercentage::Length(l) => l.is_zero(),
LengthPercentage::Percentage(p) => p.0 == 0.0,
LengthPercentage::Calc(_) => false,
}
}
}
impl ZeroNoPercent for LengthPercentage {
fn is_zero_no_percent(&self) -> bool {
match *self {
LengthPercentage::Percentage(_) => false,
_ => self.is_zero(),
}
}
}
/// A specified type for `<length-percentage> | auto`.
pub type LengthPercentageOrAuto = generics::LengthPercentageOrAuto<LengthPercentage>;
impl LengthPercentageOrAuto {
/// Returns a value representing `0%`.
#[inline]
pub fn zero_percent() -> Self {
generics::LengthPercentageOrAuto::LengthPercentage(LengthPercentage::zero_percent())
}
/// Parses a length or a percentage, allowing the unitless length quirk.
#[inline]
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_with(context, input, |context, input| {
LengthPercentage::parse_quirky(context, input, allow_quirks)
})
}
}
/// A wrapper of LengthPercentageOrAuto, whose value must be >= 0.
pub type NonNegativeLengthPercentageOrAuto =
generics::LengthPercentageOrAuto<NonNegativeLengthPercentage>;
impl NonNegativeLengthPercentageOrAuto {
/// Returns a value representing `0%`.
#[inline]
pub fn zero_percent() -> Self {
generics::LengthPercentageOrAuto::LengthPercentage(
NonNegativeLengthPercentage::zero_percent(),
)
}
/// Parses a non-negative length-percentage, allowing the unitless length
/// quirk.
#[inline]
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_with(context, input, |context, input| {
NonNegativeLengthPercentage::parse_quirky(context, input, allow_quirks)
})
}
}
/// A wrapper of LengthPercentage, whose value must be >= 0.
pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
/// Either a NonNegativeLengthPercentage or the `normal` keyword.
pub type NonNegativeLengthPercentageOrNormal =
GenericLengthPercentageOrNormal<NonNegativeLengthPercentage>;
impl From<NoCalcLength> for NonNegativeLengthPercentage {
#[inline]
fn from(len: NoCalcLength) -> Self {
NonNegative(LengthPercentage::from(len))
}
}
impl Parse for NonNegativeLengthPercentage {
#[inline]
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_quirky(context, input, AllowQuirks::No)
}
}
impl NonNegativeLengthPercentage {
#[inline]
/// Returns a `0%` value.
pub fn zero_percent() -> Self {
NonNegative(LengthPercentage::zero_percent())
}
/// Parses a length or a percentage, allowing the unitless length quirk.
#[inline]
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
LengthPercentage::parse_non_negative_quirky(context, input, allow_quirks).map(NonNegative)
}
/// Parses a length or a percentage, allowing the unitless length quirk,
/// as well as allowing `anchor-size()`.
#[inline]
pub fn parse_non_negative_with_anchor_size<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
LengthPercentage::parse_non_negative_with_anchor_size(context, input, allow_quirks).map(NonNegative)
}
}
/// Either a `<length>` or the `auto` keyword.
///
/// Note that we use LengthPercentage just for convenience, since it pretty much
/// is everything we care about, but we could just add a similar LengthOrAuto
/// instead if we think getting rid of this weirdness is worth it.
pub type LengthOrAuto = generics::LengthPercentageOrAuto<Length>;
impl LengthOrAuto {
/// Parses a length, allowing the unitless length quirk.
#[inline]
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
Self::parse_with(context, input, |context, input| {
Length::parse_quirky(context, input, allow_quirks)
})
}
}
/// Either a non-negative `<length>` or the `auto` keyword.
pub type NonNegativeLengthOrAuto = generics::LengthPercentageOrAuto<NonNegativeLength>;
/// Either a `<length>` or a `<number>`.
pub type LengthOrNumber = GenericLengthOrNumber<Length, Number>;
/// A specified value for `min-width`, `min-height`, `width` or `height` property.
pub type Size = GenericSize<NonNegativeLengthPercentage>;
impl Parse for Size {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Size::parse_quirky(context, input, AllowQuirks::No)
}
}
macro_rules! parse_size_non_length {
($size:ident, $input:expr, $auto_or_none:expr => $auto_or_none_ident:ident) => {{
let size = $input.try_parse(|input| {
Ok(try_match_ident_ignore_ascii_case! { input,
#[cfg(feature = "gecko")]
"min-content" | "-moz-min-content" => $size::MinContent,
#[cfg(feature = "gecko")]
"max-content" | "-moz-max-content" => $size::MaxContent,
#[cfg(feature = "gecko")]
"fit-content" | "-moz-fit-content" => $size::FitContent,
#[cfg(feature = "gecko")]
"-moz-available" => $size::MozAvailable,
#[cfg(feature = "gecko")]
"-webkit-fill-available" if is_webkit_fill_available_keyword_enabled() => $size::WebkitFillAvailable,
#[cfg(feature = "gecko")]
"stretch" if is_stretch_enabled() => $size::Stretch,
$auto_or_none => $size::$auto_or_none_ident,
})
});
if size.is_ok() {
return size;
}
}};
}
#[cfg(feature = "gecko")]
fn is_webkit_fill_available_keyword_enabled() -> bool {
static_prefs::pref!("layout.css.webkit-fill-available.enabled")
}
#[cfg(feature = "gecko")]
fn is_stretch_enabled() -> bool {
static_prefs::pref!("layout.css.stretch-size-keyword.enabled")
}
#[cfg(feature = "gecko")]
fn is_fit_content_function_enabled() -> bool {
static_prefs::pref!("layout.css.fit-content-function.enabled")
}
#[cfg(feature = "servo")]
fn is_fit_content_function_enabled() -> bool {
false
}
macro_rules! parse_fit_content_function {
($size:ident, $input:expr, $context:expr, $allow_quirks:expr) => {
if is_fit_content_function_enabled() {
if let Ok(length) = $input.try_parse(|input| {
input.expect_function_matching("fit-content")?;
input.parse_nested_block(|i| {
NonNegativeLengthPercentage::parse_quirky($context, i, $allow_quirks)
})
}) {
return Ok($size::FitContentFunction(length));
}
}
};
}
impl Size {
/// Parses, with quirks.
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
parse_size_non_length!(Size, input, "auto" => Auto);
parse_fit_content_function!(Size, input, context, allow_quirks);
if let Ok(length) =
input.try_parse(|i| NonNegativeLengthPercentage::parse_non_negative_with_anchor_size(context, i, allow_quirks))
{
return Ok(GenericSize::LengthPercentage(length));
}
Ok(Self::AnchorSizeFunction(Box::new(
GenericAnchorSizeFunction::parse(context, input)?,
)))
}
/// Returns `0%`.
#[inline]
pub fn zero_percent() -> Self {
GenericSize::LengthPercentage(NonNegativeLengthPercentage::zero_percent())
}
}
/// A specified value for `max-width` or `max-height` property.
pub type MaxSize = GenericMaxSize<NonNegativeLengthPercentage>;
impl Parse for MaxSize {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
MaxSize::parse_quirky(context, input, AllowQuirks::No)
}
}
impl MaxSize {
/// Parses, with quirks.
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
parse_size_non_length!(MaxSize, input, "none" => None);
parse_fit_content_function!(MaxSize, input, context, allow_quirks);
if let Ok(length) =
input.try_parse(|i| NonNegativeLengthPercentage::parse_non_negative_with_anchor_size(context, i, allow_quirks))
{
return Ok(GenericMaxSize::LengthPercentage(length));
}
Ok(Self::AnchorSizeFunction(Box::new(
GenericAnchorSizeFunction::parse(context, input)?,
)))
}
}
/// A specified non-negative `<length>` | `<number>`.
pub type NonNegativeLengthOrNumber = GenericLengthOrNumber<NonNegativeLength, NonNegativeNumber>;
/// A specified value for `anchor-size` function.
pub type AnchorSizeFunction = GenericAnchorSizeFunction<LengthPercentage>;
/// A specified value for `margin` properties.
pub type Margin = GenericMargin<LengthPercentage>;
impl Margin {
/// Parses an inset type, allowing the unitless length quirk.
#[inline]
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
{
return Ok(Self::LengthPercentage(l));
}
if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
return Ok(Self::Auto);
}
let inner = AnchorSizeFunction::parse(context, input)?;
Ok(Self::AnchorSizeFunction(Box::new(inner)))
}
}
impl Parse for Margin {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_quirky(context, input, AllowQuirks::No)
}
}