/* 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/. */
//! The main cascading algorithm of the style system.
/// Whether we're resolving a style with the purposes of reparenting for ::first-line. #[derive(Copy, Clone)] #[allow(missing_docs)] pubenum FirstLineReparenting<'a> {
No,
Yes { /// The style we're re-parenting for ::first-line. ::first-line only affects inherited /// properties so we use this to avoid some work and also ensure correctness by copying the /// reset structs from this style.
style_to_reparent: &'a ComputedValues,
},
}
/// Performs the CSS cascade, computing new styles for an element from its parent style. /// /// The arguments are: /// /// * `device`: Used to get the initial viewport and other external state. /// /// * `rule_node`: The rule node in the tree that represent the CSS rules that /// matched. /// /// * `parent_style`: The parent style, if applicable; if `None`, this is the root node. /// /// Returns the computed values. /// * `flags`: Various flags. /// pubfn cascade<E>(
stylist: &Stylist,
pseudo: Option<&PseudoElement>,
rule_node: &StrongRuleNode,
guards: &StylesheetGuards,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
first_line_reparenting: FirstLineReparenting,
visited_rules: Option<&StrongRuleNode>,
cascade_input_flags: ComputedValueFlags,
rule_cache: Option<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues> where
E: TElement,
{
cascade_rules(
stylist,
pseudo,
rule_node,
guards,
parent_style,
layout_parent_style,
first_line_reparenting,
CascadeMode::Unvisited { visited_rules },
cascade_input_flags,
rule_cache,
rule_cache_conditions,
element,
)
}
struct DeclarationIterator<'a> { // Global to the iteration.
guards: &'a StylesheetGuards<'a>,
restriction: Option<PropertyFlags>, // The rule we're iterating over.
current_rule_node: Option<&'a StrongRuleNode>, // Per rule state.
declarations: DeclarationImportanceIterator<'a>,
origin: Origin,
importance: Importance,
priority: CascadePriority,
}
iflet Some(restriction) = self.restriction { // decl.id() is either a longhand or a custom // property. Custom properties are always allowed, but // longhands are only allowed if they have our // restriction flag set. iflet PropertyDeclarationId::Longhand(id) = decl.id() { if !id.flags().contains(restriction) && self.origin != Origin::UserAgent { continue;
}
}
}
/// NOTE: This function expects the declaration with more priority to appear /// first. pubfn apply_declarations<'a, E, I>(
stylist: &'a Stylist,
pseudo: Option<&'a PseudoElement>,
rules: &StrongRuleNode,
guards: &StylesheetGuards,
iter: I,
parent_style: Option<&'a ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
first_line_reparenting: FirstLineReparenting<'a>,
cascade_mode: CascadeMode,
cascade_input_flags: ComputedValueFlags,
rule_cache: Option<&'a RuleCache>,
rule_cache_conditions: &'a mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues> where
E: TElement + 'a,
I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>,
{
debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); let device = stylist.device(); let inherited_style = parent_style.unwrap_or(device.default_computed_values()); let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root());
let container_size_query =
ContainerSizeQuery::for_option_element(element, Some(inherited_style), pseudo.is_some());
letmut context = computed::Context::new( // We'd really like to own the rules here to avoid refcount traffic, but // animation's usage of `apply_declarations` make this tricky. See bug // 1375525.
StyleBuilder::new(
device,
Some(stylist),
parent_style,
pseudo,
Some(rules.clone()),
is_root_element,
),
stylist.quirks_mode(),
rule_cache_conditions,
container_size_query,
);
context.style().add_flags(cascade_input_flags);
let using_cached_reset_properties; let ignore_colors = context.builder.device.forced_colors().is_active(); letmut cascade = Cascade::new(first_line_reparenting, ignore_colors); letmut declarations = Default::default(); letmut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); let properties_to_apply = match cascade_mode {
CascadeMode::Visited { unvisited_context } => {
context.builder.custom_properties = unvisited_context.builder.custom_properties.clone();
context.builder.writing_mode = unvisited_context.builder.writing_mode;
context.builder.color_scheme = unvisited_context.builder.color_scheme; // We never insert visited styles into the cache so we don't need to try looking it up. // It also wouldn't be super-profitable, only a handful :visited properties are // non-inherited.
using_cached_reset_properties = false; // TODO(bug 1859385): If we match the same rules when visited and unvisited, we could // try to avoid gathering the declarations. That'd be: // unvisited_context.builder.rules.as_ref() == Some(rules)
iter_declarations(iter, &mut declarations, None);
LonghandIdSet::visited_dependent()
},
CascadeMode::Unvisited { visited_rules } => { let deferred_custom_properties = { letmut builder = CustomPropertiesBuilder::new(stylist, &mut context);
iter_declarations(iter, &mut declarations, Some(&mut builder)); // Detect cycles, remove properties participating in them, and resolve properties, except: // * Registered custom properties that depend on font-relative properties (Resolved) // when prioritary properties are resolved), and // * Any property that, in turn, depend on properties like above.
builder.build(DeferFontRelativeCustomPropertyResolution::Yes)
};
// Resolve prioritary properties - Guaranteed to not fall into a cycle with existing custom // properties.
cascade.apply_prioritary_properties(&mut context, &declarations, &style='color:red'>mut shorthand_cache);
if context.builder.modified_reset() || using_cached_reset_properties { // If we adjusted any reset structs, we can't cache this ComputedValues. // // Also, if we re-used existing reset structs, don't bother caching it back again. (Aside // from being wasted effort, it will be wrong, since context.rule_cache_conditions won't be // set appropriately if we didn't compute those reset properties.)
context.rule_cache_conditions.borrow_mut().set_uncacheable();
}
context.builder.build()
}
/// For ignored colors mode, we sometimes want to do something equivalent to /// "revert-or-initial", where we `revert` for a given origin, but then apply a /// given initial value if nothing in other origins did override it. /// /// This is a bit of a clunky way of achieving this. type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>;
if !longhand_id.ignored_when_document_colors_disabled() { return;
}
let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent); if is_ua_or_user_rule { return;
}
// Always honor colors if forced-color-adjust is set to none. #[cfg(feature = "gecko")]
{ let forced = context
.builder
.get_inherited_text()
.clone_forced_color_adjust(); if forced == computed::ForcedColorAdjust::None { return;
}
}
// Don't override background-color on ::-moz-color-swatch. It is set as an // author style (via the style attribute), but it's pretty important for it // to show up for obvious reasons :) if context
.builder
.pseudo
.map_or(false, |p| p.is_color_swatch()) &&
longhand_id == LonghandId::BackgroundColor
{ return;
}
fn alpha_channel(color: &Color, context: &computed::Context) -> f32 { // We assume here currentColor is opaque.
color
.to_computed_value(context)
.resolve_to_absolute(&AbsoluteColor::BLACK)
.alpha
}
// A few special-cases ahead. match **declaration { // Honor CSS-wide keywords like unset / revert / initial...
PropertyDeclaration::CSSWideKeyword(..) => return,
PropertyDeclaration::BackgroundColor(ref color) => { // We honor system colors and transparent colors unconditionally. // // NOTE(emilio): We honor transparent unconditionally, like we do // for color, even though it causes issues like bug 1625036. The // reasoning is that the conditions that trigger that (having // mismatched widget and default backgrounds) are both uncommon, and // broken in other applications as well, and not honoring // transparent makes stuff uglier or break unconditionally // (bug 1666059, bug 1755713). if color.honored_in_forced_colors_mode(/* allow_transparent = */ true) { return;
} // For background-color, we revert or initial-with-preserved-alpha // otherwise, this is needed to preserve semi-transparent // backgrounds. let alpha = alpha_channel(color, context); if alpha == 0.0 { return;
} letmut color = context.builder.device.default_background_color();
color.alpha = alpha;
declarations_to_apply_unless_overridden
.push(PropertyDeclaration::BackgroundColor(color.into()))
},
PropertyDeclaration::Color(ref color) => { // We honor color: transparent and system colors. if color
.0
.honored_in_forced_colors_mode(/* allow_transparent = */ true)
{ return;
} // If the inherited color would be transparent, but we would // override this with a non-transparent color, then override it with // the default color. Otherwise just let it inherit through. if context
.builder
.get_parent_inherited_text()
.clone_color()
.alpha == 0.0
{ let color = context.builder.device.default_color();
declarations_to_apply_unless_overridden.push(PropertyDeclaration::Color(
specified::ColorPropertyValue(color.into()),
))
}
}, // We honor url background-images if backplating. #[cfg(feature = "gecko")]
PropertyDeclaration::BackgroundImage(ref bkg) => { usecrate::values::generics::image::Image; if static_prefs::pref!("browser.display.permit_backplate") { if bkg
.0
.iter()
.all(|image| matches!(*image, Image::Url(..) | Image::None))
{ return;
}
}
},
_ => { // We honor system colors more generally for all colors. // // We used to honor transparent but that causes accessibility // regressions like bug 1740924. // // NOTE(emilio): This doesn't handle caret-color and accent-color // because those use a slightly different syntax (<color> | auto for // example). // // That's probably fine though, as using a system color for // caret-color doesn't make sense (using currentColor is fine), and // we ignore accent-color in high-contrast-mode anyways. iflet Some(color) = declaration.color_value() { if color.honored_in_forced_colors_mode(/* allow_transparent = */ false) { return;
}
}
},
}
/// We track the index only for prioritary properties. For other properties we can just iterate. type DeclarationIndex = u16;
/// "Prioritary" properties are properties that other properties depend on in one way or another. /// /// We keep track of their position in the declaration vector, in order to be able to cascade them /// separately in precise order. #[derive(Copy, Clone)] struct PrioritaryDeclarationPosition { // DeclarationIndex::MAX signals no index.
most_important: DeclarationIndex,
least_important: DeclarationIndex,
}
/// The set of property declarations from our rules. #[derive(Default)] struct Declarations<'a> { /// Whether we have any prioritary property. This is just a minor optimization.
has_prioritary_properties: bool, /// A list of all the applicable longhand declarations.
longhand_declarations: SmallVec<[Declaration<'a>; 64]>, /// The prioritary property position data.
prioritary_positions: [PrioritaryDeclarationPosition; property_counts::PRIORITARY],
}
impl<'a> Declarations<'a> { fn note_prioritary_property(&mutself, id: PrioritaryPropertyId) { let new_index = self.longhand_declarations.len(); if new_index >= DeclarationIndex::MAX as usize { // This prioritary property is past the amount of declarations we can track. Let's give // up applying it to prevent getting confused. return;
}
self.has_prioritary_properties = true; let new_index = new_index as DeclarationIndex; let position = &mutself.prioritary_positions[id as usize]; if position.most_important == DeclarationIndex::MAX { // We still haven't seen this property, record the current position as the most // prioritary index.
position.most_important = new_index;
} else { // Let the previous item in the list know about us. self.longhand_declarations[position.least_important as usize].next_index = new_index;
}
position.least_important = new_index;
}
fn substitute_variables_if_needed<'cache, 'decl>(
&self,
context: &mut computed::Context,
shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
declaration: &'decl PropertyDeclaration,
) -> Cow<'decl, PropertyDeclaration> where 'cache: 'decl,
{ let declaration = match *declaration {
PropertyDeclaration::WithVariables(ref declaration) => declaration, ref d => return Cow::Borrowed(d),
};
if !declaration.id.inherited() {
context.rule_cache_conditions.borrow_mut().set_uncacheable();
// NOTE(emilio): We only really need to add the `display` / // `content` flag if the CSS variable has not been specified on our // declarations, but we don't have that information at this point, // and it doesn't seem like an important enough optimization to // warrant it. match declaration.id {
LonghandId::Display => {
context
.builder
.add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE);
},
LonghandId::Content => {
context
.builder
.add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE);
},
_ => {},
}
}
debug_assert!(
context.builder.stylist.is_some(), "Need a Stylist to substitute variables!"
);
declaration.value.substitute_variables(
declaration.id,
context.builder.custom_properties(),
context.builder.stylist.unwrap(),
context,
shorthand_cache,
)
}
fn apply_one_prioritary_property(
&mutself,
context: &mut computed::Context,
decls: &Declarations,
cache: &mut ShorthandsWithPropertyReferencesCache,
id: PrioritaryPropertyId,
) -> bool { letmut index = decls.prioritary_positions[id as usize].most_important; if index == DeclarationIndex::MAX { returnfalse;
}
let longhand_id = id.to_longhand();
debug_assert!(
!longhand_id.is_logical(), "That could require more book-keeping"
); loop { let decl = decls.longhand_declarations[index as usize]; self.apply_one_longhand(context, longhand_id, decl.decl, decl.priority, cache); ifself.seen.contains(longhand_id) { returntrue; // Common case, we're done.
}
debug_assert!( self.reverted_set.contains(longhand_id), "How else can we fail to apply a prioritary property?"
);
debug_assert!(
decl.next_index == 0 || decl.next_index > index, "should make progress! {} -> {}",
index,
decl.next_index,
);
index = decl.next_index; if index == 0 { break;
}
} false
}
let has_writing_mode = apply!(WritingMode) | apply!(Direction) | apply!(TextOrientation); if has_writing_mode {
context.builder.writing_mode = WritingMode::new(context.builder.get_inherited_box())
}
if apply!(Zoom) {
context.builder.effective_zoom = context
.builder
.inherited_effective_zoom()
.compute_effective(context.builder.specified_zoom()); // NOTE(emilio): This is a bit of a hack, but matches the shipped WebKit and Blink // behavior for now. Ideally, in the future, we have a pass over all // implicitly-or-explicitly-inherited properties that can contain lengths and // re-compute them properly, see https://github.com/w3c/csswg-drafts/issues/9397. self.recompute_font_size_for_zoom_change(&mut context.builder);
}
// Compute font-family. let has_font_family = apply!(FontFamily); let has_lang = apply!(XLang); if has_lang { self.recompute_initial_font_family_if_needed(&mut context.builder);
} if has_font_family { self.prioritize_user_fonts_if_needed(&mut context.builder);
}
// Compute font-size. if apply!(XTextScale) { self.unzoom_fonts_if_needed(&mut context.builder);
} let has_font_size = apply!(FontSize); let has_math_depth = apply!(MathDepth); let has_min_font_size_ratio = apply!(MozMinFontSizeRatio);
if has_math_depth && has_font_size { self.recompute_math_font_size_if_needed(context);
} if has_lang || has_font_family { self.recompute_keyword_font_size_if_needed(context);
} if has_font_size || has_min_font_size_ratio || has_lang || has_font_family { self.constrain_font_size_if_needed(&mut context.builder);
}
// Compute the rest of the first-available-font-affecting properties.
apply!(FontWeight);
apply!(FontStretch);
apply!(FontStyle); #[cfg(feature = "gecko")]
apply!(FontSizeAdjust);
#[cfg(feature = "gecko")]
apply!(ForcedColorAdjust); // color-scheme needs to be after forced-color-adjust, since it's one of the "skipped in // forced-colors-mode" properties. if apply!(ColorScheme) {
context.builder.color_scheme = context.builder.get_inherited_ui().color_scheme_bits();
}
apply!(LineHeight);
}
fn apply_non_prioritary_properties(
&mutself,
context: &mut computed::Context,
longhand_declarations: &[Declaration],
shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
properties_to_apply: &LonghandIdSet,
) {
debug_assert!(!properties_to_apply.contains_any(LonghandIdSet::prioritary_properties()));
debug_assert!(self.declarations_to_apply_unless_overridden.is_empty()); for declaration in &*longhand_declarations { letmut longhand_id = declaration.decl.id().as_longhand().unwrap(); if !properties_to_apply.contains(longhand_id) { continue;
}
debug_assert!(PrioritaryPropertyId::from_longhand(longhand_id).is_none()); let is_logical = longhand_id.is_logical(); if is_logical { let wm = context.builder.writing_mode;
context
.rule_cache_conditions
.borrow_mut()
.set_writing_mode_dependency(wm);
longhand_id = longhand_id.to_physical(wm);
} self.apply_one_longhand(
context,
longhand_id,
declaration.decl,
declaration.priority,
shorthand_cache,
);
} if !self.declarations_to_apply_unless_overridden.is_empty() {
debug_assert!(self.ignore_colors); for declaration in std::mem::take(&mutself.declarations_to_apply_unless_overridden) { let longhand_id = declaration.id().as_longhand().unwrap();
debug_assert!(!longhand_id.is_logical()); if !self.seen.contains(longhand_id) { unsafe { self.do_apply_declaration(context, longhand_id, &declaration);
}
}
}
}
}
// When document colors are disabled, do special handling of // properties that are marked as ignored in that mode. ifself.ignore_colors {
tweak_when_ignoring_colors(
context,
longhand_id,
origin,
&mut declaration,
&mutself.declarations_to_apply_unless_overridden,
);
}
let is_unset = match declaration.get_css_wide_keyword() {
Some(keyword) => match keyword {
CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { let origin_revert = keyword == CSSWideKeyword::Revert; // We intentionally don't want to insert it into `self.seen`, `reverted` takes // care of rejecting other declarations as needed. self.reverted_set.insert(longhand_id); self.reverted.insert(longhand_id, (priority, origin_revert)); return;
},
CSSWideKeyword::Unset => true,
CSSWideKeyword::Inherit => longhand_id.inherited(),
CSSWideKeyword::Initial => !longhand_id.inherited(),
},
None => false,
};
self.seen.insert(longhand_id); if origin == Origin::Author { self.author_specified.insert(longhand_id);
}
#[inline] unsafefn do_apply_declaration(
&self,
context: &mut computed::Context,
longhand_id: LonghandId,
declaration: &PropertyDeclaration,
) {
debug_assert!(!longhand_id.is_logical()); // We could (and used to) use a pattern match here, but that bloats this // function to over 100K of compiled code! // // To improve i-cache behavior, we outline the individual functions and // use virtual dispatch instead.
(CASCADE_PROPERTY[longhand_id as usize])(&declaration, context);
}
// We could call apply_declarations directly, but that'd cause // another instantiation of this function which is not great. let style = cascade_rules(
context.builder.stylist.unwrap(),
context.builder.pseudo,
visited_rules,
guards,
visited_parent!(parent_style),
visited_parent!(layout_parent_style), self.first_line_reparenting,
CascadeMode::Visited {
unvisited_context: &*context,
}, // Cascade input flags don't matter for the visited style, they are // in the main (unvisited) style.
Default::default(), // The rule cache doesn't care about caching :visited // styles, we cache the unvisited style instead. We still do // need to set the caching dependencies properly if present // though, so the cache conditions need to match.
None, // rule_cache
&mut *context.rule_cache_conditions.borrow_mut(),
element,
);
context.builder.visited_style = Some(style);
}
// We're using the same reset style as another element, and we'll skip // applying the relevant properties. So we need to do the relevant // bookkeeping here to keep these bits correct. // // Note that the border/background properties are non-inherited, so we // don't need to do anything else other than just copying the bits over. // // When using this optimization, we also need to copy whether the old // style specified viewport units / used font-relative lengths, this one // would as well. It matches the same rules, so it is the right thing // to do anyways, even if it's only used on inherited properties. let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND |
ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS |
ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS |
ComputedValueFlags::USES_CONTAINER_UNITS |
ComputedValueFlags::USES_VIEWPORT_UNITS;
builder.add_flags(style.flags & bits_to_copy);
true
}
/// The initial font depends on the current lang group so we may need to /// recompute it if the language changed. #[inline] #[cfg(feature = "gecko")] fn recompute_initial_font_family_if_needed(&self, builder: &an style='color:red'>mut StyleBuilder) { usecrate::gecko_bindings::bindings; usecrate::values::computed::font::FontFamily;
let default_font_type = { let font = builder.get_font();
if !font.mFont.family.is_initial { return;
}
let default_font_type = unsafe {
bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
builder.device.document(),
font.mLanguage.mRawPtr,
)
};
let initial_generic = font.mFont.family.families.single_generic();
debug_assert!(
initial_generic.is_some(), "Initial font should be just one generic font"
); if initial_generic == Some(default_font_type) { return;
}
/// Prioritize user fonts if needed by pref. #[inline] #[cfg(feature = "gecko")] fn prioritize_user_fonts_if_needed(&self, builder: &='color:red'>mut StyleBuilder) { usecrate::gecko_bindings::bindings;
// Check the use_document_fonts setting for content, but for chrome // documents they're treated as always enabled. if static_prefs::pref!("browser.display.use_document_fonts") != 0 ||
builder.device.chrome_rules_enabled_for_document()
{ return;
}
let default_font_type = { let font = builder.get_font();
if font.mFont.family.is_system_font { return;
}
if !font.mFont.family.families.needs_user_font_prioritization() { return;
}
let font = builder.mutate_font();
font.mFont
.family
.families
.prioritize_first_generic_or_prepend(default_font_type);
}
/// Some keyword sizes depend on the font family and language. #[cfg(feature = "gecko")] fn recompute_keyword_font_size_if_needed(&self, context: & style='color:red'>mut computed::Context) { usecrate::values::computed::ToComputedValue;
if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) { return;
}
let new_size = { let font = context.builder.get_font(); let info = font.clone_font_size().keyword_info; let new_size = match info.kw {
specified::FontSizeKeyword::None => return,
_ => {
context.for_non_inherited_property = false;
specified::FontSize::Keyword(info).to_computed_value(context)
},
};
if font.mScriptUnconstrainedSize == new_size.computed_size { return;
}
/// Some properties, plus setting font-size itself, may make us go out of /// our minimum font-size range. #[cfg(feature = "gecko")] fn constrain_font_size_if_needed(&self, builder: &mut StyleBuilder) { usecrate::gecko_bindings::bindings; usecrate::values::generics::NonNegative;
let min_font_size = { let font = builder.get_font(); let min_font_size = unsafe {
bindings::Gecko_nsStyleFont_ComputeMinSize(&**font, builder.device.document())
};
/// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up /// the struct when this happens by unzooming its contained font values, which will have been /// zoomed in the parent. #[cfg(feature = "gecko")] fn unzoom_fonts_if_needed(&self, builder: &mut StyleBuilder) {
debug_assert!(self.seen.contains(LonghandId::XTextScale));
let parent_text_scale = builder.get_parent_font().clone__x_text_scale(); let text_scale = builder.get_font().clone__x_text_scale(); if parent_text_scale == text_scale { return;
}
debug_assert_ne!(
parent_text_scale.text_zoom_enabled(),
text_scale.text_zoom_enabled(), "There's only one value that disables it"
);
debug_assert!(
!text_scale.text_zoom_enabled(), "We only ever disable text zoom never enable it"
); let device = builder.device;
builder.mutate_font().unzoom_fonts(device);
}
fn recompute_font_size_for_zoom_change(&self, builder: &tyle='color:red'>mut StyleBuilder) {
debug_assert!(self.seen.contains(LonghandId::Zoom)); // NOTE(emilio): Intentionally not using the effective zoom here, since all the inherited // zooms are already applied. let old_size = builder.get_font().clone_font_size(); let new_size = old_size.zoom(builder.resolved_specified_zoom()); if old_size == new_size { return;
}
builder.mutate_font().set_font_size(new_size);
}
/// Special handling of font-size: math (used for MathML). /// https://w3c.github.io/mathml-core/#the-math-script-level-property /// TODO: Bug: 1548471: MathML Core also does not specify a script min size /// should we unship that feature or standardize it? #[cfg(feature = "gecko")] fn recompute_math_font_size_if_needed(&self, context: &yle='color:red'>mut computed::Context) { usecrate::values::generics::NonNegative;
// Do not do anything if font-size: math or math-depth is not set. if context.builder.get_font().clone_font_size().keyword_info.kw !=
specified::FontSizeKeyword::Math
{ return;
}
// Helper function that calculates the scale factor applied to font-size // when math-depth goes from parent_math_depth to computed_math_depth. // This function is essentially a modification of the MathML3's formula // 0.71^(parent_math_depth - computed_math_depth) so that a scale factor // of parent_script_percent_scale_down is applied when math-depth goes // from 0 to 1 and parent_script_script_percent_scale_down is applied // when math-depth goes from 0 to 2. This is also a straightforward // implementation of the specification's algorithm: // https://w3c.github.io/mathml-core/#the-math-script-level-property fn scale_factor_for_math_depth_change(
parent_math_depth: i32,
computed_math_depth: i32,
parent_script_percent_scale_down: Option<f32>,
parent_script_script_percent_scale_down: Option<f32>,
) -> f32 { letmut a = parent_math_depth; letmut b = computed_math_depth; let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE; let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c); let scale_between_0_and_2 =
parent_script_script_percent_scale_down.unwrap_or_else(|| c * c); letmut s = 1.0; letmut invert_scale_factor = false; if a == b { return s;
} if b < a {
std::mem::swap(&mut a, &mut b);
invert_scale_factor = true;
} letmut e = b - a; if a <= 0 && b >= 2 {
s *= scale_between_0_and_2;
e -= 2;
} elseif a == 1 {
s *= scale_between_0_and_2 / scale_between_0_and_1;
e -= 1;
} elseif b == 1 {
s *= scale_between_0_and_1;
e -= 1;
}
s *= (c as f32).powi(e); if invert_scale_factor { 1.0 / s.max(f32::MIN_POSITIVE)
} else {
s
}
}
let (new_size, new_unconstrained_size) = { let builder = &context.builder; let font = builder.get_font(); let parent_font = builder.get_parent_font();
let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth);
if delta == 0 { return;
}
letmut min = parent_font.mScriptMinSize; if font.mXTextScale.text_zoom_enabled() {
min = builder.device.zoom_text(min);
}
// Calculate scale factor following MathML Core's algorithm. let scale = { // Script scale factors are independent of orientation. let font_metrics = context.query_font_metrics(
FontBaseSize::InheritedStyle,
FontMetricsOrientation::Horizontal, /* retrieve_math_scales = */ true,
);
scale_factor_for_math_depth_change(
parent_font.mMathDepth as i32,
font.mMathDepth as i32,
font_metrics.script_percent_scale_down,
font_metrics.script_script_percent_scale_down,
)
};
let parent_size = parent_font.mSize.0; let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0; let new_size = parent_size.scale_by(scale); let new_unconstrained_size = parent_unconstrained_size.scale_by(scale);
if scale <= 1. { // The parent size can be smaller than scriptminsize, e.g. if it // was specified explicitly. Don't scale in this case, but we // don't want to set it to scriptminsize either since that will // make it larger. if parent_size <= min {
(parent_size, new_unconstrained_size)
} else {
(min.max(new_size), new_unconstrained_size)
}
} else { // If the new unconstrained size is larger than the min size, // this means we have escaped the grasp of scriptminsize and can // revert to using the unconstrained size. // However, if the new size is even larger (perhaps due to usage // of em units), use that instead.
(
new_size.min(new_unconstrained_size.max(min)),
new_unconstrained_size,
)
}
}; let font = context.builder.mutate_font();
font.mFont.size = NonNegative(new_size);
font.mSize = NonNegative(new_size);
font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size);
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-06-18)
¤
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.