Table of Contents

Class CssCascade

Namespace
Egami.Css
Assembly
Egami.Css.dll

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

sheets IEnumerable<(CssStyleSheet Sheet, CssOrigin Origin)>

The style sheets and their origins, in document order.

options CssCascadeOptions

The media context, the @supports callback 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

CssPropertyRegistry

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

element ICssElement

The element.

presentationHints IReadOnlyList<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.

inlineStyle IReadOnlyList<CssDeclaration>

The declarations of the element's style attribute (see ParseList(string, CssParseOptions?)).

pseudoElement string

A 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

page CssPage

The 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

cascaded CssCascadedStyle

The element's cascaded style.

parent CssComputedStyle

The parent element's computed style, or null for the root.

Returns

CssComputedStyle

The computed style.