/* 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/. */
usesuper::{AllowQuirks, Number, ToComputedValue}; usecrate::computed_value_flags::ComputedValueFlags; usecrate::derives::*; usecrate::font_metrics::{FontMetrics, FontMetricsOrientation}; #[cfg(feature = "gecko")] usecrate::gecko_bindings::structs::GeckoFontMetrics; usecrate::parser::{Parse, ParserContext}; usecrate::typed_om::{NumericValue, ToTyped, TypedValue, UnitValue}; usecrate::values::computed::{self, CSSPixelLength, Context, FontSize}; usecrate::values::generics::length as generics; usecrate::values::generics::length::{
GenericAnchorSizeFunction, GenericLengthOrNumber, GenericLengthPercentageOrNormal,
GenericMargin, GenericMaxSize, GenericSize,
}; usecrate::values::generics::NonNegative; usecrate::values::specified::calc::{
AllowAnchorPositioningFunctions, CalcLengthPercentage, CalcNode,
}; usecrate::values::specified::font::QueryFontMetricsFlags; usecrate::values::specified::percentage::NoCalcPercentage; usecrate::values::specified::NonNegativeNumber; usecrate::values::tagged_numeric::{Extracted, NumericUnion, Unpacked}; usecrate::values::CSSFloat; usecrate::{Zero, ZeroNoPercent}; use app_units::AU_PER_PX; use cssparser::{match_ignore_ascii_case, Parser, Token}; use std::cmp; use std::fmt::{self, Write}; use style_traits::values::specified::AllowedNumericType; use style_traits::{
CssString, CssWriter, ParseError, ParsingMode, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
}; use thin_vec::ThinVec;
pubusesuper::image::Image; pubusesuper::image::{EndingShape as GradientEndingShape, Gradient};
/// Number of pixels per inch pubconst PX_PER_IN: CSSFloat = 96.; /// Number of pixels per centimeter pubconst PX_PER_CM: CSSFloat = PX_PER_IN / 2.54; /// Number of pixels per millimeter pubconst PX_PER_MM: CSSFloat = PX_PER_IN / 25.4; /// Number of pixels per quarter pubconst PX_PER_Q: CSSFloat = PX_PER_MM / 4.; /// Number of pixels per point pubconst PX_PER_PT: CSSFloat = PX_PER_IN / 72.; /// Number of pixels per pica pubconst PX_PER_PC: CSSFloat = PX_PER_PT * 12.;
/// The unit of a `<length>` value. Note that if any new font-relative value is /// added here, `custom_properties::NonCustomReferences::from_unit` /// must also be updated. Consult the comment in that function as to why. /// /// The variants are grouped (absolute, font-relative, viewport, container, /// servo-internal) so that `is_*` predicates can be implemented with simple /// range checks. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, ToShmem)] #[repr(u8)] #[allow(missing_docs)] pubenum LengthUnit { // Absolute lengths.
Px, In,
Cm,
Mm,
Q,
Pt,
Pc, // Font-relative lengths.
Em,
Ex,
Rex,
Ch,
Rch,
Cap,
Rcap,
Ic,
Ric,
Rem,
Lh,
Rlh, // Viewport-percentage lengths.
Vw,
Svw,
Lvw,
Dvw,
Vh,
Svh,
Lvh,
Dvh,
Vmin,
Svmin,
Lvmin,
Dvmin,
Vmax,
Svmax,
Lvmax,
Dvmax,
Vb,
Svb,
Lvb,
Dvb,
Vi,
Svi,
Lvi,
Dvi, // Container-relative lengths.
Cqw,
Cqh,
Cqi,
Cqb,
Cqmin,
Cqmax, /// HTML5 "character width", as defined in HTML5 § 14.5.4. Internal-only.
ServoCharacterWidth,
}
impl LengthUnit { /// Returns the length unit for the given string. #[inline] pubfn from_str(unit: &str) -> Result<Self, ()> { Self::from_str_with_flags(ParsingMode::DEFAULT, /* in_page_rule = */ false, unit)
}
/// Returns the length unit for the given flags and string. #[inline] pubfn from_str_with_flags(
parsing_mode: ParsingMode,
in_page_rule: bool,
unit: &str,
) -> Result<Self, ()> { let allows_computational_dependence = parsing_mode.allows_computational_dependence();
/// Whether this is an absolute length unit (px, in, cm, mm, q, pt, pc). #[inline] pubfn is_absolute(self) -> bool {
matches!( self, Self::Px | Self::In | Self::Cm | Self::Mm | Self::Q | Self::Pt | Self::Pc
)
}
/// Whether this is a font-relative unit. #[inline] pubfn is_font_relative(self) -> bool {
matches!( self, Self::Em
| Self::Ex
| Self::Rex
| Self::Ch
| Self::Rch
| Self::Cap
| Self::Rcap
| Self::Ic
| Self::Ric
| Self::Rem
| Self::Lh
| Self::Rlh
)
}
/// A source to resolve font-relative units against #[derive(Clone, Copy, Debug, PartialEq)] pubenum 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)] pubenum 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 pubfn 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.effective_zoom_for_inheritance;
style.get_parent_font().clone_font_size().zoom(zoom)
},
}
}
}
/// A `<length>` without taking `calc` expressions into account /// /// <https://drafts.csswg.org/css-values/#lengths> #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)] #[repr(C)] pubstruct NoCalcLength {
unit: LengthUnit,
value: CSSFloat,
}
impl NoCalcLength { /// Unit identifier for `em`. pubconst EM: &'static str = "em"; /// Unit identifier for `ex`. pubconst EX: &'static str = "ex"; /// Unit identifier for `rex`. pubconst REX: &'static str = "rex"; /// Unit identifier for `ch`. pubconst CH: &'static str = "ch"; /// Unit identifier for `rch`. pubconst RCH: &'static str = "rch"; /// Unit identifier for `cap`. pubconst CAP: &'static str = "cap"; /// Unit identifier for `rcap`. pubconst RCAP: &'static str = "rcap"; /// Unit identifier for `ic`. pubconst IC: &'static str = "ic"; /// Unit identifier for `ric`. pubconst RIC: &'static str = "ric"; /// Unit identifier for `rem`. pubconst REM: &'static str = "rem"; /// Unit identifier for `lh`. pubconst LH: &'static str = "lh"; /// Unit identifier for `rlh`. pubconst RLH: &'static str = "rlh";
/// Creates a length with the given unit and value. #[inline] pubfn new(unit: LengthUnit, value: CSSFloat) -> Self { Self { unit, value }
}
/// Returns the unit of this length. #[inline] pubfn length_unit(&self) -> LengthUnit { self.unit
}
/// Return the unitless, raw value. #[inline] pubfn unitless_value(&self) -> CSSFloat { self.value
}
/// Return the unit, as a string. #[inline] pubfn unit(&self) -> &'static str { self.unit.as_str()
}
/// Return the canonical unit for this value, if one exists. pubfn canonical_unit(&self) -> Option<&'static str> { ifself.unit.is_absolute() {
Some("px")
} else {
None
}
}
/// Convert this value to the specified unit, if possible. pubfn to(&self, unit: &str) -> Result<Self, ()> { let px = self.to_px_if_absolute().ok_or(())?; let (target, divisor) = match_ignore_ascii_case! { unit, "px" => (LengthUnit::Px, 1.0), "in" => (LengthUnit::In, PX_PER_IN), "cm" => (LengthUnit::Cm, PX_PER_CM), "mm" => (LengthUnit::Mm, PX_PER_MM), "q" => (LengthUnit::Q, PX_PER_Q), "pt" => (LengthUnit::Pt, PX_PER_PT), "pc" => (LengthUnit::Pc, PX_PER_PC),
_ => return Err(()),
};
Ok(Self::new(target, px / divisor))
}
/// Returns whether the value of this length without unit is less than zero. pubfn is_negative(&self) -> bool { self.value.is_sign_negative()
}
/// Returns whether the value of this length without unit is equal to zero. pubfn is_zero(&self) -> bool { self.value == 0.0
}
/// Returns whether the value of this length without unit is infinite. pubfn is_infinite(&self) -> bool { self.value.is_infinite()
}
/// Returns whether the value of this length without unit is NaN. pubfn is_nan(&self) -> bool { self.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. pubfn should_zoom_text(&self) -> bool {
!self.unit.is_font_relative() && self.unit != LengthUnit::ServoCharacterWidth
}
/// Returns the SortKey for this length. Must not be called on the internal /// `ServoCharacterWidth` unit. pub(crate) fn sort_key(&self) -> crate::values::generics::calc::SortKey { self.unit.sort_key()
}
/// Parse a given absolute or relative dimension. pubfn parse_dimension_with_flags(
parsing_mode: ParsingMode,
in_page_rule: bool,
value: CSSFloat,
unit: &str,
) -> Result<Self, ()> { let length_unit = LengthUnit::from_str_with_flags(parsing_mode, in_page_rule, unit)?;
Ok(Self::new(length_unit, value))
}
/// Parse a given absolute or relative dimension. pubfn parse_dimension_with_context(
context: &ParserContext,
value: CSSFloat,
unit: &str,
) -> Result<Self, ()> { Self::parse_dimension_with_flags(context.parsing_mode, context.in_page_rule(), value, unit)
}
pub(crate) fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> where
O: Fn(f32, f32) -> f32,
{ // For absolute lengths, normalize both to px and produce a px result. iflet (Some(a), Some(b)) = (self.to_px_if_absolute(), other.to_px_if_absolute()) { return Ok(Self::new(LengthUnit::Px, op(a, b)));
} ifself.unit != other.unit { return Err(());
}
Ok(Self::new(self.unit, op(self.value, other.value)))
}
/// Get a px value without context (so only absolute units can be handled). #[inline] pubfn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { self.to_px_if_absolute().ok_or(())
}
/// 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] pubfn to_computed_pixel_length_with_font_metrics(
&self,
get_font_metrics: Option<implFn() -> GeckoFontMetrics>,
) -> Result<CSSFloat, ()> { iflet Some(px) = self.to_px_if_absolute() { return Ok(CSSPixelLength::new(px).finite().px());
} if !self.unit.is_font_relative() { return Err(());
} let getter = match get_font_metrics {
Some(g) => g,
None => return Err(()),
}; let metrics = getter();
Ok(matchself.unit {
LengthUnit::Em => self.value * metrics.mComputedEmSize.px(),
LengthUnit::Ex => self.value * metrics.mXSize.px(),
LengthUnit::Ch => self.value * metrics.mChSize.px(),
LengthUnit::Cap => self.value * metrics.mCapHeight.px(),
LengthUnit::Ic => self.value * metrics.mIcWidth.px(), // `lh`, `rlh` are unsupported as we have no line-height context // `rem`, `rex`, `rch`, `rcap`, and `ric` are unsupported as we have no root font context.
_ => return Err(()),
})
}
/// Get an absolute length from a px value. #[inline] pubfn from_px(px_value: CSSFloat) -> NoCalcLength { Self::new(LengthUnit::Px, px_value)
}
/// Construct a font-relative em value. #[inline] pubfn from_em(value: CSSFloat) -> Self { Self::new(LengthUnit::Em, value)
}
/// Construct an internal ServoCharacterWidth length from an i32 column count. #[inline] pubfn from_servo_character_width(value: i32) -> Self { Self::new(LengthUnit::ServoCharacterWidth, value as CSSFloat)
}
/// Compute a font-relative length against the given base sizes. Must only /// be called on a font-relative unit. fn font_relative_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()
}
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))
}
/// Compute the container-relative length. Must only be called on a /// container-relative unit. fn container_relative_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);
let size = context.get_container_size_query(); let factor = self.value; let container_length = matchself.unit {
LengthUnit::Cqw => size.get_container_width(context),
LengthUnit::Cqh => size.get_container_height(context),
LengthUnit::Cqi => size.get_container_inline_size(context),
LengthUnit::Cqb => size.get_container_block_size(context),
LengthUnit::Cqmin => cmp::min(
size.get_container_inline_size(context),
size.get_container_block_size(context),
),
LengthUnit::Cqmax => cmp::max(
size.get_container_inline_size(context),
size.get_container_block_size(context),
),
_ => {
unreachable!("container_relative_to_computed_value: not a container-relative unit")
},
};
CSSPixelLength::new((container_length.to_f64_px() * factor as f64 / 100.0) as f32).finite()
}
/// Computes a ServoCharacterWidth length against a reference font size. fn servo_character_width_to_computed_value(
&self,
reference_font_size: computed::Length,
) -> computed::Length {
debug_assert_eq!(self.unit, LengthUnit::ServoCharacterWidth); let cols = self.value as i32 as CSSFloat; // This applies the *converting a character width to pixels* algorithm // as specified in HTML5 § 14.5.4. let average_advance = reference_font_size * 0.5; let max_advance = reference_font_size;
(average_advance * (cols - 1.0) + max_advance).finite()
}
/// Computes a length with a given font-relative base size. pubfn to_computed_value_with_base_size(
&self,
context: &Context,
base_size: FontBaseSize,
line_height_base: LineHeightBase,
) -> CSSPixelLength { iflet Some(px) = self.to_px_if_absolute() { return CSSPixelLength::new(px)
.zoom(context.builder.effective_zoom)
.finite();
} let unit = self.length_unit(); if unit.is_font_relative() { returnself.font_relative_to_computed_value(context, base_size, line_height_base);
} if unit.is_viewport_percentage() { returnself.viewport_percentage_to_computed_value(context);
} if unit.is_container_relative() { returnself.container_relative_to_computed_value(context);
}
debug_assert_eq!(unit, LengthUnit::ServoCharacterWidth); self.servo_character_width_to_computed_value(
context.style().get_font().clone_font_size().computed_size(),
)
}
}
impl ToComputedValue for NoCalcLength { type ComputedValue = computed::Length;
impl ToTyped for NoCalcLength { fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> { let value = self.unitless_value(); let unit = CssString::from(self.unit());
dest.push(TypedValue::Numeric(NumericValue::Unit(UnitValue {
value,
unit,
})));
Ok(())
}
}
/// An extension to `NoCalcLength` to parse `calc` expressions. /// This is commonly used for the `<length>` values. /// /// Either stored inline as length + unit without calc or as a boxed calc node. /// /// <https://drafts.csswg.org/css-values/#lengths> #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pubstruct Length(NumericUnion<LengthUnit, f32, CalcLengthPercentage>);
/// A `<length-percentage>` value. This can be either a `<length>`, a /// `<percentage>`, or a combination of both via `calc()`. /// /// https://drafts.csswg.org/css-values-4/#typedef-length-percentage #[allow(missing_docs)] #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)] pubenum LengthPercentage {
Length(NoCalcLength),
Percentage(NoCalcPercentage),
Calc(Box<CalcLengthPercentage>),
}
/// Parses allowing the unitless length quirk, as well as allowing /// anchor-positioning related function, `anchor-size()`. #[inline] fn parse_quirky_with_anchor_size_function<'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::AllowAnchorSize,
)
}
/// Parses allowing the unitless length quirk, as well as allowing /// anchor-positioning related functions, `anchor()` and `anchor-size()`. #[inline] pubfn 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()`. pubfn 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] pubfn 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)
}
/// Check if this equal to a specific percentage. pubtrait EqualsPercentage { /// Returns true if this is a specific percentage value. This should exclude calc() even if it /// only contains percentage component. fn equals_percentage(&self, v: CSSFloat) -> bool;
}
/// A wrapper of LengthPercentageOrAuto, whose value must be >= 0. pubtype NonNegativeLengthPercentageOrAuto =
generics::LengthPercentageOrAuto<NonNegativeLengthPercentage>;
impl NonNegativeLengthPercentageOrAuto { /// Returns a value representing `0%`. #[inline] pubfn zero_percent() -> Self {
generics::LengthPercentageOrAuto::LengthPercentage(
NonNegativeLengthPercentage::zero_percent(),
)
}
/// A wrapper of LengthPercentage, whose value must be >= 0. pubtype NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
/// Either a NonNegativeLengthPercentage or the `normal` keyword. pubtype NonNegativeLengthPercentageOrNormal =
GenericLengthPercentageOrNormal<NonNegativeLengthPercentage>;
/// Parses a length or a percentage, allowing the unitless length quirk. /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk> #[inline] pubfn 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] pubfn 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. pubtype LengthOrAuto = generics::LengthPercentageOrAuto<Length>;
fn is_webkit_fill_available_enabled_in_all_size_properties() -> bool { // For convenience at the callsites, we check both prefs here, // since both must be 'true' in order for the keyword to be // enabled in all size properties.
static_prefs::pref!("layout.css.webkit-fill-available.enabled")
&& static_prefs::pref!("layout.css.webkit-fill-available.all-size-properties.enabled")
}
/// Parses, with quirks and configurable support for /// whether the '-webkit-fill-available' keyword is allowed. /// TODO(dholbert) Fold this function into callsites in bug 1989073 when /// removing 'layout.css.webkit-fill-available.all-size-properties.enabled'. fn parse_quirky_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
allow_webkit_fill_available: bool,
allow_anchor_functions: ParseAnchorFunctions,
) -> Result<Self, ParseError<'i>> {
parse_size_non_length!(Size, input, allow_webkit_fill_available, "auto" => Auto);
parse_fit_content_function!(Size, input, context, allow_quirks);
let allow_anchor = allow_anchor_functions == ParseAnchorFunctions::Yes
&& static_prefs::pref!("layout.css.anchor-positioning.enabled"); match input
.try_parse(|i| NonNegativeLengthPercentage::parse_quirky(context, i, allow_quirks))
{
Ok(length) => return Ok(GenericSize::LengthPercentage(length)),
Err(e) if !allow_anchor => return Err(e.into()),
Err(_) => (),
}; iflet Ok(length) = input.try_parse(|i| {
NonNegativeLengthPercentage::parse_non_negative_with_anchor_size(
context,
i,
allow_quirks,
)
}) { return Ok(GenericSize::AnchorContainingCalcFunction(length));
}
Ok(Self::AnchorSizeFunction(Box::new(
GenericAnchorSizeFunction::parse(context, input)?,
)))
}
/// Parse a size for width or height, where -webkit-fill-available /// support is only controlled by one pref (vs. other properties where /// there's an additional pref check): /// TODO(dholbert) Remove this custom parse func in bug 1989073, along with /// 'layout.css.webkit-fill-available.all-size-properties.enabled'. pubfn parse_size_for_width_or_height_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> { let allow_webkit_fill_available = is_webkit_fill_available_enabled_in_width_and_height(); Self::parse_quirky_internal(
context,
input,
allow_quirks,
allow_webkit_fill_available,
ParseAnchorFunctions::Yes,
)
}
/// Parse a size for width or height, where -webkit-fill-available /// support is only controlled by one pref (vs. other properties where /// there's an additional pref check): /// TODO(dholbert) Remove this custom parse func in bug 1989073, along with /// 'layout.css.webkit-fill-available.all-size-properties.enabled'. pubfn parse_size_for_width_or_height<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> { let allow_webkit_fill_available = is_webkit_fill_available_enabled_in_width_and_height(); Self::parse_quirky_internal(
context,
input,
AllowQuirks::No,
allow_webkit_fill_available,
ParseAnchorFunctions::Yes,
)
}
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.