Skip to documentation
SLOP

tiny.css.cascade

Reference tiny.css cascade

Defined in tiny.css.

The cascade: origin, importance, specificity, source order, inheritance, the four CSS wide keywords, custom properties, and var() substitution.

API (23)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callstiny.csscascade
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/css/src/cascade/resolve.zig:64

zig
/// A computed style: one value per property, dense and indexed by identifier.pub const Computed = struct {    values: [property.count]Value = @splat(.{}),    pub fn get(self: *const Computed, id: property.Id) Value {        std.debug.assert(id != .invalid);        return self.values[@backingInt(id)];    }    pub fn set(self: *Computed, id: property.Id, item: Value) void {        std.debug.assert(id != .invalid);        self.values[@backingInt(id)] = item;    }    /// The computed style of an element with no declarations and no parent.    pub fn initial() Computed {        var result = Computed{};        for (property.entries, 1..) |entry, index| result.values[index] = entry.initial;        return result;    }};

Source: lib/css/src/cascade/resolve.zig:48

zig
/// One custom property declaration. Its value stays raw text, because a custom/// property's value is a token sequence rather than a typed value.pub const Custom = struct {    name: u32,    start: u32,    end: u32,    important: bool = false,};

Source: lib/css/src/cascade/resolve.zig:86

zig
/// One custom property's winning declaration.pub const CustomSlot = struct {    name: u32 = 0,    start: u32 = 0,    end: u32 = 0,    level: Level = .user_agent_normal,    specificity: Specificity = .{},    order: u32 = 0,    inherited: bool = false,};

Source: lib/css/src/cascade/resolve.zig:98

zig
/// The custom properties in scope for one element. Overflow rejects the extra/// declaration and counts it rather than evicting a resolved one.pub const CustomTable = struct {    slots: [max_custom_properties]CustomSlot = @splat(.{}),    count: u32 = 0,    rejected: u32 = 0,    pub fn find(self: *const CustomTable, name: u32) ?usize {        var index: usize = 0;        while (index < self.count) : (index += 1) {            if (self.slots[index].name == name) return index;        }        return null;    }    fn clear(self: *CustomTable) void {        self.count = 0;        self.rejected = 0;    }};

Source: lib/css/src/cascade/resolve.zig:21

zig
/// The cascade levels in increasing precedence. Importance inverts the origin/// order, which is the whole reason the levels are named rather than derived.pub const Level = enum(u8) {    user_agent_normal = 0,    user_normal = 1,    author_normal = 2,    author_important = 3,    user_important = 4,    user_agent_important = 5,};

Source: lib/css/src/cascade/resolve.zig:13

zig
/// The three cascade origins this engine carries.pub const Origin = enum(u8) {    user_agent = 0,    user = 1,    author = 2,};

Source: lib/css/src/cascade/resolve.zig:122

zig
/// Resolves one element's cascade. The caller owns it and reuses it across/// elements, so the cascade allocates nothing.////// A substituted string value borrows the resolver's substitution buffer and/// stays valid until the next `begin`.pub const Resolver = struct {    slots: [property.count][level_count]Slot = @splat(@splat(.{})),    customs: CustomTable = .{},    buffer: [max_substitution_bytes]u8 = undefined,    names: ?*const atom.Table = null,    used: u32 = 0,    source: []const u8 = &.{},    substitution_rejected: u32 = 0,    /// Starts a fresh element. `parent_customs` seeds the inherited custom    /// properties, which is how a custom property reaches a subtree.    pub fn begin(        self: *Resolver,        source: []const u8,        names: ?*const atom.Table,        parent_customs: ?*const CustomTable,    ) void {        self.slots = @splat(@splat(.{}));        self.names = names;        self.customs.clear();        self.used = 0;        self.source = source;        self.substitution_rejected = 0;        if (parent_customs) |table| {            var index: usize = 0;            while (index < table.count and index < max_custom_properties) : (index += 1) {                self.customs.slots[index] = table.slots[index];                self.customs.slots[index].inherited = true;            }            self.customs.count = @intCast(index);        }    }    /// Offers one declaration to the cascade.    pub fn add(        self: *Resolver,        declaration: value.Declaration,        origin: Origin,        specificity: Specificity,        order: u32,    ) void {        const id: property.Id = @fromBackingInt(@intCast(declaration.property));        if (id == .invalid) return;        const level = levelOf(origin, declaration.important());        const slot = &self.slots[declaration.property][@backingInt(level)];        const candidate = Slot{            .value = declaration.value(),            .specificity = specificity,            .order = order,            .present = true,        };        if (!slot.present or wins(candidate, slot.*)) slot.* = candidate;    }    /// Offers one custom property declaration to the cascade.    pub fn addCustom(        self: *Resolver,        custom: Custom,        origin: Origin,        specificity: Specificity,        order: u32,    ) void {        const level = levelOf(origin, custom.important);        const candidate = CustomSlot{            .name = custom.name,            .start = custom.start,            .end = custom.end,            .level = level,            .specificity = specificity,            .order = order,        };        if (self.customs.find(custom.name)) |index| {            const existing = self.customs.slots[index];            if (existing.inherited or customWins(candidate, existing)) {                self.customs.slots[index] = candidate;            }            return;        }        if (self.customs.count == max_custom_properties) {            self.customs.rejected += 1;            return;        }        self.customs.slots[self.customs.count] = candidate;        self.customs.count += 1;    }    /// Resolves every property against `parent` and writes the computed style.    pub fn finish(self: *Resolver, parent: ?*const Computed, out: *Computed) void {        out.values[0] = .{};        for (property.entries, 1..) |entry, index| {            const id: property.Id = @fromBackingInt(@intCast(index));            const from_parent = if (parent) |source| source.get(id) else entry.initial;            const winner = self.resolve(index);            out.values[index] = self.compute(id, winner, entry, from_parent);        }    }    fn compute(        self: *Resolver,        id: property.Id,        winner: ?Slot,        entry: property.Metadata,        from_parent: Value,    ) Value {        const fallback = if (entry.inherited) from_parent else entry.initial;        const slot = winner orelse return fallback;        const resolved = if (slot.value.flags & flag_pending != 0)            self.substituted(id, slot.value) orelse return fallback        else            slot.value;        return switch (resolved.asKeyword()) {            .inherit => from_parent,            .initial, .revert => entry.initial,            .unset => fallback,            else => resolved,        };    }    fn resolve(self: *Resolver, index: usize) ?Slot {        var excluded: u8 = 0;        var round: u32 = 0;        while (round <= 3) : (round += 1) {            const found = self.pick(index, excluded) orelse return null;            if (found.slot.value.asKeyword() != .revert) return found.slot;            const origin = originOf(found.level);            excluded |= originBit(origin);        }        return null;    }    const Winner = struct {        slot: Slot,        level: Level,    };    fn pick(self: *Resolver, index: usize, excluded: u8) ?Winner {        var level: usize = level_count;        while (level > 0) {            level -= 1;            const candidate: Level = @fromBackingInt(@intCast(level));            if (excluded & originBit(originOf(candidate)) != 0) continue;            const slot = self.slots[index][level];            if (slot.present) return .{ .slot = slot, .level = candidate };        }        return null;    }    fn substituted(self: *Resolver, id: property.Id, pending: Value) ?Value {        const begin_offset = self.used;        var visiting: u64 = 0;        if (!self.expandSpan(pending.a, pending.a + pending.b, 0, &visiting)) {            self.used = begin_offset;            self.substitution_rejected += 1;            return null;        }        const text = self.buffer[begin_offset..self.used];        const grammar = property.metadata(id).grammar;        const parsed = value.parse(grammar, text, 0, @intCast(text.len));        if (!parsed.present()) return null;        return parsed;    }    fn expandSpan(self: *Resolver, start: u32, end: u32, depth: u32, visiting: *u64) bool {        if (depth > max_substitution_depth) return false;        std.debug.assert(end <= self.source.len);        var tokenizer = token.Tokenizer{ .source = self.source[0..end], .index = start };        var copied = start;        var guard: usize = 0;        while (guard <= self.source.len + 1) : (guard += 1) {            const next = tokenizer.next();            if (next.kind == .eof) break;            if (next.kind != .function) continue;            if (!std.ascii.eqlIgnoreCase(next.value(self.source), "var")) continue;            const close = closeParen(&tokenizer, self.source.len) orelse return false;            if (!self.write(self.source[copied..next.start])) return false;            if (!self.expandVariable(next.end, close, depth, visiting)) return false;            copied = close + 1;            tokenizer.index = close + 1;        }        return self.write(self.source[copied..end]);    }    fn expandVariable(self: *Resolver, start: u32, end: u32, depth: u32, visiting: *u64) bool {        const body = self.source[start..end];        const comma = topLevelComma(body);        const name = std.mem.trim(u8, body[0 .. comma orelse body.len], " \t\r\n\x0C");        if (!property.isCustom(name)) return false;        if (self.lookupCustom(name)) |index| {            const mask = @as(u64, 1) << @intCast(index);            if (visiting.* & mask != 0) return false;            visiting.* |= mask;            const slot = self.customs.slots[index];            const expanded = self.expandSpan(slot.start, slot.end, depth + 1, visiting);            visiting.* &= ~mask;            if (expanded) return true;        }        const offset = comma orelse return false;        const fallback_start = start + @as(u32, @intCast(offset)) + 1;        return self.expandSpan(fallback_start, end, depth + 1, visiting);    }    fn lookupCustom(self: *const Resolver, name: []const u8) ?usize {        const table = self.names orelse return null;        const id = table.lookup(name);        if (id == atom.none) return null;        if (self.customs.count > max_custom_properties) return null;        return self.customs.find(id);    }    fn write(self: *Resolver, text: []const u8) bool {        if (self.used + text.len > self.buffer.len) return false;        @memcpy(self.buffer[self.used..][0..text.len], text);        self.used += @intCast(text.len);        return true;    }};

Source: lib/css/src/cascade/resolve.zig:56

zig
/// The winning declaration at one cascade level.pub const Slot = struct {    value: Value = .{},    specificity: Specificity = .{},    order: u32 = 0,    present: bool = false,};
Called byCallsNo direct callstest sourcelib.css.src.cascade.resolvetest: an undeclared property inherits...test sourcelib.css.src.cascade.resolvetest: importance inverts the cascade ...test sourcelib.css.src.cascade.resolvetest: revert drops its own origin and...test sourcelib.css.src.cascade.resolvetest: specificity settles a level bef...test sourcelib.css.src.cascade.resolvetest: the four wide keywords resolve ...cascade.Computedget
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.css.src.cascade.resolvetest: an undeclared property inherits...test sourcelib.css.src.cascade.resolvetest: the four wide keywords resolve ...cascade.Computedinitial
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callscascade.ResolveraddCustomprivate sourcelib.css.src.cascade.resolve.ResolverlookupCustomcascade.CustomTablefind
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.css.src.cascade.resolvetest: importance inverts the cascade ...test sourcelib.css.src.cascade.resolvetest: revert drops its own origin and...test sourcelib.css.src.cascade.resolvetest: specificity settles a level bef...test sourcelib.css.src.cascade.resolvetest: the four wide keywords resolve ...cascadelevelOfprivate sourcelib.css.src.cascade.resolvewinscascade.Resolveradd
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callerscascade.CustomTablefindprivate sourcelib.css.src.cascade.resolvecustomWinscascadelevelOfcascade.ResolveraddCustom
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.css.src.cascade.resolvetest: an undeclared property inherits...test sourcelib.css.src.cascade.resolvetest: importance inverts the cascade ...test sourcelib.css.src.cascade.resolvetest: revert drops its own origin and...test sourcelib.css.src.cascade.resolvetest: specificity settles a level bef...test sourcelib.css.src.cascade.resolvetest: the four wide keywords resolve ...private sourcelib.css.src.cascade.resolve.CustomTableclearcascade.Resolverbegin
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.css.src.cascade.resolvetest: an undeclared property inherits...test sourcelib.css.src.cascade.resolvetest: importance inverts the cascade ...test sourcelib.css.src.cascade.resolvetest: revert drops its own origin and...test sourcelib.css.src.cascade.resolvetest: specificity settles a level bef...test sourcelib.css.src.cascade.resolvetest: the four wide keywords resolve ...private sourcelib.css.src.cascade.resolve.Resolvercomputeprivate sourcelib.css.src.cascade.resolve.Resolverresolvecascade.Resolverfinish
Static calls · unresolved targets: 1 · external targets: 0.

Source: lib/css/src/cascade/resolve.zig:44

zig
/// Bit one of a value's flag word marks a declaration whose text still holds a/// `var()` reference. Its `a` and `b` words are the source span, not a value.pub const flag_pending: u16 = 2;

Source: lib/css/src/cascade/resolve.zig:359

zig
/// The cascade level a declaration lands in.pub fn levelOf(origin: Origin, important: bool) Level {    return switch (origin) {        .user_agent => if (important) .user_agent_important else .user_agent_normal,        .user => if (important) .user_important else .user_normal,        .author => if (important) .author_important else .author_normal,    };}
Called byCallsNo direct callscascade.Resolveraddcascade.ResolveraddCustomcascadelevelOf
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/cascade/resolve.zig:31

zig
/// The number of cascade levels.pub const level_count: usize = 6;

Source: lib/css/src/cascade/resolve.zig:34

zig
/// The most custom properties one element may carry.pub const max_custom_properties: usize = 64;

Source: lib/css/src/cascade/resolve.zig:37

zig
/// The substitution buffer one element's `var()` expansions share.pub const max_substitution_bytes: usize = 4096;

Source: lib/css/src/cascade/resolve.zig:40

zig
/// The deepest `var()` nesting the resolver expands before failing closed.pub const max_substitution_depth: u32 = 8;

Source: lib/css/src/cascade/resolve.zig:372

zig
/// The origin behind a cascade level.pub fn originOf(level: Level) Origin {    return switch (level) {        .user_agent_normal, .user_agent_important => .user_agent,        .user_normal, .user_important => .user,        .author_normal, .author_important => .author,    };}
Called byCallsNo direct callsprivate sourcelib.css.src.cascade.resolve.Resolverpickprivate sourcelib.css.src.cascade.resolve.ResolverresolvecascadeoriginOf
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/cascade/root.zig

zig
//! The cascade: origin, importance, specificity, source order, inheritance,//! the four CSS wide keywords, custom properties, and `var()` substitution.//!//! `Resolver` holds one winning declaration per property per cascade level, so//! `revert` is answered by re-picking with the reverting origin removed rather//! than by a second pass over the matched rules. The caller owns the resolver//! and reuses it across elements, so the cascade allocates nothing.//!//! A computed style is a dense array indexed by property identifier, which is//! what makes inheritance a copy and comparison a memcmp.const resolve = @import("resolve.zig");pub const Computed = resolve.Computed;pub const Custom = resolve.Custom;pub const CustomSlot = resolve.CustomSlot;pub const CustomTable = resolve.CustomTable;pub const Level = resolve.Level;pub const Origin = resolve.Origin;pub const Resolver = resolve.Resolver;pub const Slot = resolve.Slot;pub const flag_pending = resolve.flag_pending;pub const level_count = resolve.level_count;pub const levelOf = resolve.levelOf;pub const max_custom_properties = resolve.max_custom_properties;pub const max_substitution_bytes = resolve.max_substitution_bytes;pub const max_substitution_depth = resolve.max_substitution_depth;pub const originOf = resolve.originOf;

Source: lib/css/src/root.zig:37

zig
pub const cascade = @import("cascade/root.zig");

Audit

Definitions24
Public names24
Members35
Version26.7.0
Revisiondaab053ee433