Class CssCascade
The cascade (CSS Cascade 5, without animations, transitions and cascade layers): finds the declarations that apply to an
element (or, for @page rules, a page) and picks the winner for each property.
public sealed class CssCascade
- Inheritance
-
CssCascade
- Inherited Members
Examples
// The registry says which properties inherit and their initial values; consumers add their own.
CssPropertyRegistry registry = CssPropertyRegistry.CreateDefault();
registry.Register(new CssPropertyDefinition("fill", Inherited: true, InitialValue: "black")
{
Validate = value => CssColor.TryParse(value, out _),
});
registry.RegisterShorthand(new CssShorthandDefinition("marker", ["marker-start", "marker-end"],
value => [new CssDeclaration("marker-start", value), new CssDeclaration("marker-end", value)]));
CssStyleSheet userAgent = CssStyleSheet.Parse("rect { fill: gray }");
CssStyleSheet author = CssStyleSheet.Parse("""
svg { --accent: teal; font: 12px sans-serif }
.layer { fill: var(--accent) }
#r { fill: inherit; opacity: 0.5 }
""");
var cascade = new CssCascade(
[(userAgent, CssOrigin.UserAgent), (author, CssOrigin.Author)],
new CssCascadeOptions { Registry = registry, Media = new CssMediaContext { Width = 800 } });
// Cascade each element (presentation attributes as hints, the style attribute as inline style), then compute it
// from its parent's computed style.
CssComputedStyle rootStyle = cascade.Compute(cascade.Cascade(root), null);
CssComputedStyle groupStyle = cascade.Compute(cascade.Cascade(group), rootStyle);
CssCascadedStyle cascaded = cascade.Cascade(rect,
presentationHints: [CssDeclaration.Create("fill", "red")],
inlineStyle: CssDeclaration.ParseList("stroke: blue"));
CssComputedStyle style = cascade.Compute(cascaded, groupStyle);
cascaded.TryGetDeclaration("fill", out CssDeclaration? winner, out CssOrigin origin); // fill: inherit, Author
string? fill = style.GetValueText("fill"); // "teal": inherited, var() substituted
string? fontSize = style.GetValueText("font-size"); // "12px": from the font shorthand, inherited
if (style.TryGetValue("opacity", out IReadOnlyList<CssComponentValue>? opacity))
{
// The consumer parses each property by its type.
}
Remarks
Rules are indexed by the ID, class or type of their rightmost compound selector, so each element is tested against few
rules. @media rules apply when their query matches Media (build a new cascade for a
new context); @supports rules when their condition holds; @layer blocks are treated as ordinary rules;
@import rules are not followed (load the sheets and pass them in order instead). Shorthands are expanded into
longhands while the cascade is built, so a shorthand and a longhand compete per longhand. A cascade can be used from
several threads at once.
Constructors
CssCascade(IEnumerable<(CssStyleSheet Sheet, CssOrigin Origin)>, CssCascadeOptions?)
Builds a cascade from style sheets in order of appearance.
public CssCascade(IEnumerable<(CssStyleSheet Sheet, CssOrigin Origin)> sheets, CssCascadeOptions? options = null)
Parameters
sheetsIEnumerable<(CssStyleSheet Sheet, CssOrigin Origin)>The style sheets and their origins, in document order.
optionsCssCascadeOptionsThe media context, the
@supportscallback and the property registry.
Examples
// The registry says which properties inherit and their initial values; consumers add their own.
CssPropertyRegistry registry = CssPropertyRegistry.CreateDefault();
registry.Register(new CssPropertyDefinition("fill", Inherited: true, InitialValue: "black")
{
Validate = value => CssColor.TryParse(value, out _),
});
registry.RegisterShorthand(new CssShorthandDefinition("marker", ["marker-start", "marker-end"],
value => [new CssDeclaration("marker-start", value), new CssDeclaration("marker-end", value)]));
CssStyleSheet userAgent = CssStyleSheet.Parse("rect { fill: gray }");
CssStyleSheet author = CssStyleSheet.Parse("""
svg { --accent: teal; font: 12px sans-serif }
.layer { fill: var(--accent) }
#r { fill: inherit; opacity: 0.5 }
""");
var cascade = new CssCascade(
[(userAgent, CssOrigin.UserAgent), (author, CssOrigin.Author)],
new CssCascadeOptions { Registry = registry, Media = new CssMediaContext { Width = 800 } });
// Cascade each element (presentation attributes as hints, the style attribute as inline style), then compute it
// from its parent's computed style.
CssComputedStyle rootStyle = cascade.Compute(cascade.Cascade(root), null);
CssComputedStyle groupStyle = cascade.Compute(cascade.Cascade(group), rootStyle);
CssCascadedStyle cascaded = cascade.Cascade(rect,
presentationHints: [CssDeclaration.Create("fill", "red")],
inlineStyle: CssDeclaration.ParseList("stroke: blue"));
CssComputedStyle style = cascade.Compute(cascaded, groupStyle);
cascaded.TryGetDeclaration("fill", out CssDeclaration? winner, out CssOrigin origin); // fill: inherit, Author
string? fill = style.GetValueText("fill"); // "teal": inherited, var() substituted
string? fontSize = style.GetValueText("font-size"); // "12px": from the font shorthand, inherited
if (style.TryGetValue("opacity", out IReadOnlyList<CssComponentValue>? opacity))
{
// The consumer parses each property by its type.
}
Properties
Registry
Gets the property registry the cascade expands shorthands and computes values with.
public CssPropertyRegistry Registry { get; }
Property Value
Methods
Cascade(ICssElement, IReadOnlyList<CssDeclaration>?, IReadOnlyList<CssDeclaration>?, string?)
Cascades the declarations that apply to an element (or one of its pseudo-elements).
public CssCascadedStyle Cascade(ICssElement element, IReadOnlyList<CssDeclaration>? presentationHints = null, IReadOnlyList<CssDeclaration>? inlineStyle = null, string? pseudoElement = null)
Parameters
elementICssElementThe element.
presentationHintsIReadOnlyList<CssDeclaration>Declarations from the document language's presentation attributes (SVG's
fill="red", HTML's legacy attributes): author origin, specificity zero, before every author style sheet rule.inlineStyleIReadOnlyList<CssDeclaration>The declarations of the element's
styleattribute (see ParseList(string, CssParseOptions?)).pseudoElementstringA pseudo-element such as
before, or null for the element itself.
Returns
- CssCascadedStyle
The winning declaration of each property.
CascadePage(CssPage)
Cascades the @page rules for one page (CSS Paged Media 3 section 4): every rule whose selectors match the page
competes by origin and importance, then by page selector specificity and order. Page-margin rules cascade the same
way per margin box.
public CssPageStyle CascadePage(CssPage page)
Parameters
pageCssPageThe page: its page type name and whether it is the first, a left or a blank page.
Returns
- CssPageStyle
The page context's and the margin boxes' cascaded styles.
Examples
CssStyleSheet sheet = CssStyleSheet.Parse("""
@page { size: A4; margin: 2cm; font: 9pt serif;
@bottom-center { content: counter(page) } }
@page :first { margin-top: 5cm; @bottom-center { content: none } }
@page :left { margin-left: 3cm }
@page appendix { size: A4 landscape }
h1.appendix { page: appendix; break-before: right }
""");
CssPageRule first = (CssPageRule)sheet.Rules[1];
CssPageSelector selector = first.Selectors[0]; // :first, specificity (0, 1, 0)
CssPageMarginRule footer = ((CssPageRule)sheet.Rules[0]).MarginRules[0]; // CssMarginBox.BottomCenter
// Cascade the page rules for each page the layout makes, then compute the page context from the root element's
// computed style and each margin box from the page context's.
var cascade = new CssCascade([(sheet, CssOrigin.Author)]);
CssPageStyle pageStyle = cascade.CascadePage(new CssPage(Name: null, IsFirst: false, IsLeft: true));
CssComputedStyle root = cascade.Compute(new CssCascadedStyle(), null);
CssComputedStyle page = cascade.Compute(pageStyle.Page, root);
string? marginLeft = page.GetValueText("margin-left"); // "3cm"
bool isSize = CssPageSize.TryParse(page.GetValueText("size")!, out CssPageSize a4); // 210mm by 297mm
CssComputedStyle box = cascade.Compute(pageStyle.MarginBoxes[CssMarginBox.BottomCenter], page);
string? content = box.GetValueText("content"); // "counter(page)"
string? fontSize = box.GetValueText("font-size"); // "9pt": inherited from the page context
// A named page: the appendix pages turn A4 to landscape.
CssPageStyle appendix = cascade.CascadePage(new CssPage("appendix", IsFirst: false, IsLeft: false));
bool isTurned = CssPageSize.TryParse(appendix.Page.GetValueText("size")!, out CssPageSize turned);
bool landscape = turned.Orientation == CssPageOrientation.Landscape; // true: 297mm by 210mm
Compute(CssCascadedStyle, CssComputedStyle?)
Computes an element's style from its cascaded style and its parent's computed style.
public CssComputedStyle Compute(CssCascadedStyle cascaded, CssComputedStyle? parent)
Parameters
cascadedCssCascadedStyleThe element's cascaded style.
parentCssComputedStyleThe parent element's computed style, or null for the root.
Returns
- CssComputedStyle
The computed style.