Skip to documentation
SLOP

tiny.css.media

Reference tiny.css media

Defined in tiny.css.

Media types, the media environment, and media query evaluation.

API (6)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallsNo direct callersprivate sourcelib.css.src.mediafindMediaQueryEndprivate sourcelib.css.src.mediamediaQueryAppliesscanreadIdentscanskipCssSpacemediamediaApplies
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.css.src.mediatest: media evaluates aspect ratio an...private sourcelib.css.src.mediafindMediaQueryEndprivate sourcelib.css.src.mediamediaQueryAppliesscanskipCssSpacemediamediaListApplies
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/css/src/media.zig

zig
//! Media types, the media environment, and media query evaluation.//!//! Queries are evaluated during ingestion so that inactive rule subtrees never//! reach the rule store. An unknown feature evaluates to unknown rather than//! false, which keeps a negated unknown query inactive as the specification//! requires.const std = @import("std");const scan = @import("scan.zig");const consumePrefixIgnoreCase = scan.consumePrefixIgnoreCase;const consumeWord = scan.consumeWord;const containsTopLevelLogical = scan.containsTopLevelLogical;const endsWithIgnoreCase = scan.endsWithIgnoreCase;const findParenEnd = scan.findParenEnd;const findTopLevelByte = scan.findTopLevelByte;const readIdent = scan.readIdent;const skipCssSpace = scan.skipCssSpace;const skipString = scan.skipString;pub const MediaType = enum {    screen,    print,};pub const MediaEnvironment = struct {    media_type: MediaType = .screen,    width: f32 = 800,    height: f32 = 600,    font_size: f32 = 16,    color_scheme: ColorScheme = .light,    reduced_motion: ReducedMotion = .no_preference,    pub fn default() MediaEnvironment {        return .{};    }    pub fn screen(width: f32, height: f32) MediaEnvironment {        return .{ .media_type = .screen, .width = width, .height = height };    }};pub const ColorScheme = enum { light, dark };pub const ReducedMotion = enum { no_preference, reduce };pub fn mediaListApplies(query_list: []const u8, media: MediaEnvironment) bool {    const trimmed = std.mem.trim(u8, query_list, " \t\r\n");    if (trimmed.len == 0) return true;    var query_start: usize = 0;    while (query_start < trimmed.len) {        const query_end = findMediaQueryEnd(trimmed, query_start);        if (mediaQueryApplies(trimmed[query_start..query_end], media)) return true;        query_start = if (query_end < trimmed.len) skipCssSpace(trimmed, query_end + 1) else trimmed.len;    }    return false;}pub fn mediaApplies(prelude: []const u8, media: MediaEnvironment) bool {    const trimmed = std.mem.trim(u8, prelude, " \t\r\n");    const name_end = readIdent(trimmed, 0);    if (name_end == 0 or !std.ascii.eqlIgnoreCase(trimmed[0..name_end], "media")) return false;    var query_start = skipCssSpace(trimmed, name_end);    if (query_start >= trimmed.len) return true;    while (query_start < trimmed.len) {        const query_end = findMediaQueryEnd(trimmed, query_start);        if (mediaQueryApplies(trimmed[query_start..query_end], media)) return true;        query_start = if (query_end < trimmed.len) skipCssSpace(trimmed, query_end + 1) else trimmed.len;    }    return false;}fn findMediaQueryEnd(source: []const u8, start: usize) usize {    var index = start;    var paren_depth: usize = 0;    while (index < source.len) : (index += 1) {        const byte = source[index];        if (byte == '"' or byte == '\'') {            index = skipString(source, index);            continue;        }        switch (byte) {            '(' => paren_depth += 1,            ')' => {                if (paren_depth > 0) paren_depth -= 1;            },            ',' => if (paren_depth == 0) return index,            else => {},        }    }    return source.len;}const QueryResult = enum {    match,    miss,    unknown,};const Comparison = enum {    lt,    lte,    eq,    gte,    gt,};const ComparisonPosition = struct {    index: usize,    comparison: Comparison,    width: usize,};fn mediaQueryApplies(query: []const u8, media: MediaEnvironment) bool {    return evaluateMediaQuery(query, media) == .match;}fn evaluateMediaQuery(query: []const u8, media: MediaEnvironment) QueryResult {    const trimmed = std.mem.trim(u8, query, " \t\r\n");    if (trimmed.len == 0) return .miss;    var index: usize = 0;    var negated = false;    if (consumeWord(trimmed, &index, "not")) negated = true;    _ = consumeWord(trimmed, &index, "only");    index = skipCssSpace(trimmed, index);    var result = QueryResult.match;    const type_start = index;    const type_end = readIdent(trimmed, index);    if (type_end != type_start) {        const word = trimmed[type_start..type_end];        if (mediaTypeWord(word)) {            result = queryAnd(result, mediaTypeMatches(word, media));            index = skipCssSpace(trimmed, type_end);            if (index < trimmed.len) {                if (!consumeWord(trimmed, &index, "and")) return .miss;                result = queryAnd(result, evaluateMediaCondition(trimmed[index..], media));            }        } else {            result = evaluateMediaCondition(trimmed[index..], media);        }    } else {        result = evaluateMediaCondition(trimmed[index..], media);    }    return if (negated) queryNot(result) else result;}fn evaluateMediaCondition(source: []const u8, media: MediaEnvironment) QueryResult {    const trimmed = std.mem.trim(u8, source, " \t\r\n");    if (trimmed.len == 0) return .unknown;    var index: usize = 0;    const result = evaluateMediaConditionSequence(trimmed, &index, media);    index = skipCssSpace(trimmed, index);    return if (index == trimmed.len) result else .unknown;}fn evaluateMediaConditionSequence(source: []const u8, index: *usize, media: MediaEnvironment) QueryResult {    var result = QueryResult.match;    var first = true;    var next_operator: enum { logical_and, logical_or } = .logical_and;    while (true) {        index.* = skipCssSpace(source, index.*);        var negated = false;        while (consumeWord(source, index, "not")) {            negated = !negated;            index.* = skipCssSpace(source, index.*);        }        if (index.* >= source.len or source[index.*] != '(') return .unknown;        const close = findParenEnd(source, index.*) orelse return .unknown;        const body = std.mem.trim(u8, source[index.* + 1 .. close], " \t\r\n");        index.* = close + 1;        var term = if (body.len == 0)            QueryResult.unknown        else if (body[0] == '(' or containsTopLevelLogical(body)) nested: {            var nested_index: usize = 0;            const nested_result = evaluateMediaConditionSequence(body, &nested_index, media);            nested_index = skipCssSpace(body, nested_index);            break :nested if (nested_index == body.len) nested_result else .unknown;        } else evaluateMediaFeature(body, media);        if (negated) term = queryNot(term);        if (first) {            result = term;            first = false;        } else {            result = switch (next_operator) {                .logical_and => queryAnd(result, term),                .logical_or => queryOr(result, term),            };        }        index.* = skipCssSpace(source, index.*);        if (index.* >= source.len) return result;        if (consumeWord(source, index, "and")) {            next_operator = .logical_and;            continue;        }        if (consumeWord(source, index, "or")) {            next_operator = .logical_or;            continue;        }        return .unknown;    }}fn evaluateMediaFeature(source: []const u8, media: MediaEnvironment) QueryResult {    const trimmed = std.mem.trim(u8, source, " \t\r\n");    if (trimmed.len == 0) return .unknown;    if (findComparison(trimmed, 0)) |comparison| return evaluateRangeFeature(trimmed, comparison, media);    if (findTopLevelByte(trimmed, 0, ':')) |colon| {        const raw_name = std.mem.trim(u8, trimmed[0..colon], " \t\r\n");        const raw_value = std.mem.trim(u8, trimmed[colon + 1 ..], " \t\r\n");        if (raw_name.len == 0 or raw_value.len == 0) return .unknown;        var comparison = Comparison.eq;        var name = raw_name;        if (consumePrefixIgnoreCase(raw_name, "min-")) |rest| {            comparison = .gte;            name = rest;        } else if (consumePrefixIgnoreCase(raw_name, "max-")) |rest| {            comparison = .lte;            name = rest;        }        if (std.ascii.eqlIgnoreCase(name, "orientation")) {            if (comparison != .eq) return .unknown;            return boolResult(orientationMatches(raw_value, media) orelse return .unknown);        }        if (std.ascii.eqlIgnoreCase(name, "aspect-ratio")) {            const actual = aspectRatio(media) orelse return .unknown;            const expected = parseAspectRatio(raw_value) orelse return .unknown;            return boolResult(compareNumbers(actual, comparison, expected));        }        if (std.ascii.eqlIgnoreCase(name, "prefers-color-scheme")) {            if (comparison != .eq) return .unknown;            const expected: ColorScheme = if (std.ascii.eqlIgnoreCase(raw_value, "light"))                .light            else if (std.ascii.eqlIgnoreCase(raw_value, "dark"))                .dark            else                return .unknown;            return boolResult(media.color_scheme == expected);        }        if (std.ascii.eqlIgnoreCase(name, "prefers-reduced-motion")) {            if (comparison != .eq) return .unknown;            const expected: ReducedMotion = if (std.ascii.eqlIgnoreCase(raw_value, "reduce"))                .reduce            else if (std.ascii.eqlIgnoreCase(raw_value, "no-preference"))                .no_preference            else                return .unknown;            return boolResult(media.reduced_motion == expected);        }        const actual = mediaFeatureLength(name, media) orelse return .unknown;        const expected = parseMediaLength(raw_value, media.font_size) orelse return .unknown;        return boolResult(compareNumbers(actual, comparison, expected));    }    if (std.ascii.eqlIgnoreCase(trimmed, "width")) return boolResult(media.width != 0);    if (std.ascii.eqlIgnoreCase(trimmed, "height")) return boolResult(media.height != 0);    if (std.ascii.eqlIgnoreCase(trimmed, "orientation")) return .match;    if (std.ascii.eqlIgnoreCase(trimmed, "aspect-ratio")) return boolResult(aspectRatio(media) != null);    return .unknown;}fn evaluateRangeFeature(source: []const u8, first: ComparisonPosition, media: MediaEnvironment) QueryResult {    const left = std.mem.trim(u8, source[0..first.index], " \t\r\n");    const right_source = source[first.index + first.width ..];    if (findComparison(right_source, 0)) |second| {        const middle = std.mem.trim(u8, right_source[0..second.index], " \t\r\n");        const right = std.mem.trim(u8, right_source[second.index + second.width ..], " \t\r\n");        return queryAnd(evaluateComparison(left, first.comparison, middle, media), evaluateComparison(middle, second.comparison, right, media));    }    const right = std.mem.trim(u8, right_source, " \t\r\n");    return evaluateComparison(left, first.comparison, right, media);}fn evaluateComparison(left: []const u8, comparison: Comparison, right: []const u8, media: MediaEnvironment) QueryResult {    if (std.ascii.eqlIgnoreCase(left, "aspect-ratio")) {        const actual = aspectRatio(media) orelse return .unknown;        const expected = parseAspectRatio(right) orelse return .unknown;        return boolResult(compareNumbers(actual, comparison, expected));    }    if (std.ascii.eqlIgnoreCase(right, "aspect-ratio")) {        const expected = aspectRatio(media) orelse return .unknown;        const actual = parseAspectRatio(left) orelse return .unknown;        return boolResult(compareNumbers(actual, comparison, expected));    }    const left_value = mediaOperand(left, media) orelse return .unknown;    const right_value = mediaOperand(right, media) orelse return .unknown;    return boolResult(compareNumbers(left_value, comparison, right_value));}fn aspectRatio(media: MediaEnvironment) ?f32 {    if (!std.math.isFinite(media.width) or !std.math.isFinite(media.height) or media.height <= 0 or media.width < 0) return null;    return media.width / media.height;}fn parseAspectRatio(source: []const u8) ?f32 {    const trimmed = std.mem.trim(u8, source, " \t\r\n");    if (std.mem.indexOfScalar(u8, trimmed, '/')) |slash| {        const numerator = std.fmt.parseUnsigned(u32, std.mem.trim(u8, trimmed[0..slash], " \t\r\n"), 10) catch return null;        const denominator = std.fmt.parseUnsigned(u32, std.mem.trim(u8, trimmed[slash + 1 ..], " \t\r\n"), 10) catch return null;        if (denominator == 0) return null;        return @as(f32, @floatFromInt(numerator)) / @as(f32, @floatFromInt(denominator));    }    const numerator = std.fmt.parseUnsigned(u32, trimmed, 10) catch return null;    return @floatFromInt(numerator);}test "media evaluates aspect ratio and user preferences" {    const env = MediaEnvironment{        .width = 1200,        .height = 800,        .color_scheme = .dark,        .reduced_motion = .reduce,    };    try std.testing.expect(mediaListApplies("(min-aspect-ratio: 3/2)", env));    try std.testing.expect(mediaListApplies("(1/1 < aspect-ratio < 2/1)", env));    try std.testing.expect(!mediaListApplies("(aspect-ratio: 16/9)", env));    try std.testing.expect(mediaListApplies("(prefers-color-scheme: dark) and (prefers-reduced-motion: reduce)", env));    try std.testing.expect(!mediaListApplies("(prefers-reduced-motion: no-preference)", env));    try std.testing.expect(!mediaListApplies("not (resolution: 2dppx)", env));    try std.testing.expect(!mediaListApplies("not (-webkit-device-pixel-ratio: 2)", env));}fn mediaOperand(source: []const u8, media: MediaEnvironment) ?f32 {    const trimmed = std.mem.trim(u8, source, " \t\r\n");    if (mediaFeatureLength(trimmed, media)) |value| return value;    return parseMediaLength(trimmed, media.font_size);}fn mediaFeatureLength(name: []const u8, media: MediaEnvironment) ?f32 {    if (std.ascii.eqlIgnoreCase(name, "width")) return media.width;    if (std.ascii.eqlIgnoreCase(name, "height")) return media.height;    return null;}fn mediaTypeWord(word: []const u8) bool {    return std.ascii.eqlIgnoreCase(word, "all") or        std.ascii.eqlIgnoreCase(word, "screen") or        std.ascii.eqlIgnoreCase(word, "print") or        std.ascii.eqlIgnoreCase(word, "tty") or        std.ascii.eqlIgnoreCase(word, "tv") or        std.ascii.eqlIgnoreCase(word, "projection") or        std.ascii.eqlIgnoreCase(word, "handheld") or        std.ascii.eqlIgnoreCase(word, "braille") or        std.ascii.eqlIgnoreCase(word, "embossed") or        std.ascii.eqlIgnoreCase(word, "aural") or        std.ascii.eqlIgnoreCase(word, "speech");}fn mediaTypeMatches(word: []const u8, media: MediaEnvironment) QueryResult {    if (std.ascii.eqlIgnoreCase(word, "all")) return .match;    if (std.ascii.eqlIgnoreCase(word, "screen")) return boolResult(media.media_type == .screen);    if (std.ascii.eqlIgnoreCase(word, "print")) return boolResult(media.media_type == .print);    return .miss;}fn orientationMatches(value: []const u8, media: MediaEnvironment) ?bool {    if (std.ascii.eqlIgnoreCase(value, "portrait")) return media.height >= media.width;    if (std.ascii.eqlIgnoreCase(value, "landscape")) return media.width > media.height;    return null;}fn parseMediaLength(value: []const u8, font_size: f32) ?f32 {    const trimmed = std.mem.trim(u8, value, " \t\r\n");    if (std.mem.eql(u8, trimmed, "0")) return 0;    if (endsWithIgnoreCase(trimmed, "rem")) {        const number = std.fmt.parseFloat(f32, trimmed[0 .. trimmed.len - 3]) catch return null;        return number * font_size;    }    if (endsWithIgnoreCase(trimmed, "em")) {        const number = std.fmt.parseFloat(f32, trimmed[0 .. trimmed.len - 2]) catch return null;        return number * font_size;    }    if (endsWithIgnoreCase(trimmed, "px")) {        return std.fmt.parseFloat(f32, trimmed[0 .. trimmed.len - 2]) catch null;    }    return null;}fn compareNumbers(left: f32, comparison: Comparison, right: f32) bool {    return switch (comparison) {        .lt => left < right,        .lte => left <= right,        .eq => left == right,        .gte => left >= right,        .gt => left > right,    };}fn queryNot(value: QueryResult) QueryResult {    return switch (value) {        .match => .miss,        .miss => .match,        .unknown => .unknown,    };}fn queryAnd(left: QueryResult, right: QueryResult) QueryResult {    if (left == .miss or right == .miss) return .miss;    if (left == .unknown or right == .unknown) return .unknown;    return .match;}fn queryOr(left: QueryResult, right: QueryResult) QueryResult {    if (left == .match or right == .match) return .match;    if (left == .unknown or right == .unknown) return .unknown;    return .miss;}fn boolResult(value: bool) QueryResult {    return if (value) .match else .miss;}fn findComparison(source: []const u8, start: usize) ?ComparisonPosition {    var index = start;    while (index < source.len) : (index += 1) {        switch (source[index]) {            '<' => {                if (index + 1 < source.len and source[index + 1] == '=') return .{ .index = index, .comparison = .lte, .width = 2 };                return .{ .index = index, .comparison = .lt, .width = 1 };            },            '>' => {                if (index + 1 < source.len and source[index + 1] == '=') return .{ .index = index, .comparison = .gte, .width = 2 };                return .{ .index = index, .comparison = .gt, .width = 1 };            },            '=' => return .{ .index = index, .comparison = .eq, .width = 1 },            else => {},        }    }    return null;}

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

zig
pub const media = @import("media.zig");

Audit

Definitions5
Public names5
Members4
Version26.7.0
Revisiondaab053ee433