/*
* Copyright © 2026 Red Hat , Inc
*
* SPDX - License - Identifier : LGPL - 2 . 1 - or - later
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 . 1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see < http : //www.gnu.org/licenses/>.
*
* Authors : Matthias Clasen < mclasen @ redhat . com >
*/
#include "config.h"
#include "gtksvgmediaqueryprivate.h"
#include "gtkcssmediaqueryprivate.h"
#include "gtksvgprivate.h"
#include "gtksvgutilsprivate.h"
void
svg_css_media_condition_free (SvgCssMediaCondition *condition)
{
switch (condition->type)
{
case SVG_CSS_MEDIA_NOT:
svg_css_media_condition_free (condition->not .condition);
break ;
case SVG_CSS_MEDIA_AND:
case SVG_CSS_MEDIA_OR:
for (unsigned int i = 0 ; i < condition->and_or.length; i++)
svg_css_media_condition_free (condition->and_or.condition[i]);
g_free (condition->and_or.condition);
break ;
case SVG_CSS_MEDIA_FEATURE:
default :
/* nothing to do */ ;
}
g_free (condition);
}
gboolean
svg_css_media_condition_evaluate (SvgCssMediaCondition *condition,
GArray *features)
{
switch (condition->type)
{
case SVG_CSS_MEDIA_NOT:
return !svg_css_media_condition_evaluate (condition->not .condition, features);
case SVG_CSS_MEDIA_AND:
for (unsigned int i = 0 ; i < condition->and_or.length; i++)
if (!svg_css_media_condition_evaluate (condition->and_or.condition[i], features))
return FALSE ;
return TRUE ;
case SVG_CSS_MEDIA_OR:
for (unsigned int i = 0 ; i < condition->and_or.length; i++)
if (svg_css_media_condition_evaluate (condition->and_or.condition[i], features))
return TRUE ;
return FALSE ;
case SVG_CSS_MEDIA_FEATURE:
break ;
default :
g_assert_not_reached ();
}
for (unsigned int i = 0 ; i < features->len; i++)
{
GtkCssDiscreteMediaFeature *feature = &g_array_index (features, GtkCssDiscreteMediaFeature, i);
if (strcmp (feature->name, condition->feature.name) == 0 )
return strcmp (feature->value, condition->feature.value) == 0 ;
}
g_warning ("Undefined @media feature '%s'" , condition->feature.value);
return FALSE ;
}
void
svg_css_media_block_free (SvgCssMediaBlock *media)
{
svg_css_media_condition_free (media->condition);
g_free (media);
}
void
svg_css_media_block_evaluate (SvgCssMediaBlock *media,
GArray *features)
{
if (media->next && !media->next->value)
media->value = FALSE ;
else if (media->condition)
media->value = svg_css_media_condition_evaluate (media->condition, features);
else
media->value = TRUE ;
}
static SvgCssMediaCondition *
svg_css_media_condition_not (SvgCssMediaCondition *condition)
{
SvgCssMediaCondition *c;
if (condition == NULL)
return NULL;
c = g_new0 (SvgCssMediaCondition, 1 );
c->type = SVG_CSS_MEDIA_NOT;
c->not .condition = condition;
return c;
}
static SvgCssMediaCondition *
svg_css_media_condition_compound (SvgCssMediaType type,
size_t length,
SvgCssMediaCondition **condition)
{
SvgCssMediaCondition *c;
c = g_new0 (SvgCssMediaCondition, 1 );
c->type = type;
c->and_or.length = length;
c->and_or.condition = condition;
return c;
}
static SvgCssMediaCondition *
svg_css_media_condition_feature (const char *name,
const char *value)
{
SvgCssMediaCondition *c;
c = g_new (SvgCssMediaCondition, 1 );
c->type = SVG_CSS_MEDIA_FEATURE;
c->feature.name = name;
c->feature.value = value;
return c;
}
static SvgCssMediaCondition *
parse_media_feature (GtkCssParser *parser)
{
const char *media_features[] = { "prefers-color-scheme" , "prefers-contrast" , "prefers-reduced-motion" };
const char *color_scheme_vals[] = { "light" , "dark" };
const char *contrast_vals[] = { "no-preference" , "less" , "more" , "custom" };
const char *reduced_motion_vals[] = { "no-preference" , "reduce" };
unsigned int feature;
if (parser_try_enum (parser, media_features, G_N_ELEMENTS (media_features), &feature))
{
const char **vals;
size_t n_vals;
unsigned int val;
if (!gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COLON))
{
gtk_css_parser_error_syntax (parser, "Expected ':' after @media feature name" );
return NULL;
}
if (feature == 0 )
{
vals = color_scheme_vals;
n_vals = G_N_ELEMENTS (color_scheme_vals);
}
else if (feature == 1 )
{
vals = contrast_vals;
n_vals = G_N_ELEMENTS (contrast_vals);
}
else if (feature == 2 )
{
vals = reduced_motion_vals;
n_vals = G_N_ELEMENTS (reduced_motion_vals);
}
else
g_assert_not_reached ();
if (parser_try_enum (parser, vals, n_vals, &val))
return svg_css_media_condition_feature (media_features[feature], vals[val]);
else
{
gtk_css_parser_warn_syntax (parser, "Undefined %s value '%s'" ,
media_features[feature],
gtk_css_token_to_string (gtk_css_parser_get_token (parser)));
gtk_css_parser_consume_token (parser);
}
}
else
{
gtk_css_parser_warn_syntax (parser, "Undefined @media feature '%s'" ,
gtk_css_token_to_string (gtk_css_parser_get_token (parser)));
gtk_css_parser_consume_token (parser);
}
return NULL;
}
static SvgCssMediaCondition * parse_media_condition (GtkCssParser *parser);
static SvgCssMediaCondition *
parse_media_in_parens (GtkCssParser *parser)
{
SvgCssMediaCondition *condition;
if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_OPEN_PARENS))
{
gtk_css_parser_error_syntax (parser, "Expected '(' after @media query" );
return NULL;
}
gtk_css_parser_start_block (parser);
if (gtk_css_parser_has_ident (parser, "not" ) ||
gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_OPEN_PARENS))
condition = parse_media_condition (parser);
else
condition = parse_media_feature (parser);
gtk_css_parser_end_block (parser);
return condition;
}
static SvgCssMediaCondition *
parse_media_condition (GtkCssParser *parser)
{
SvgCssMediaCondition *condition;
gboolean is_and;
if (gtk_css_parser_try_ident (parser, "not" ))
condition = svg_css_media_condition_not (parse_media_in_parens (parser));
else
condition = parse_media_in_parens (parser);
if (condition == NULL)
return NULL;
if ((is_and = gtk_css_parser_has_ident (parser, "and" )) ||
gtk_css_parser_has_ident (parser, "or" ))
{
GPtrArray *a;
a = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_css_media_condition_free);
g_ptr_array_add (a, condition);
while (gtk_css_parser_try_ident (parser, is_and ? "and" : "or" ))
{
condition = parse_media_in_parens (parser);
if (condition == NULL)
{
g_ptr_array_unref (a);
return NULL;
}
g_ptr_array_add (a, condition);
}
condition = svg_css_media_condition_compound (is_and ? SVG_CSS_MEDIA_AND : SVG_CSS_MEDIA_OR,
a->len, (SvgCssMediaCondition **) a->pdata);
g_ptr_array_free (a, FALSE );
}
return condition;
}
SvgCssMediaCondition *
parse_media_query (GtkCssParser *parser)
{
SvgCssMediaCondition *condition;
condition = parse_media_condition (parser);
if (condition == NULL)
return NULL;
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA))
{
GPtrArray *a = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_css_media_condition_free);
g_ptr_array_add (a, condition);
while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA))
{
condition = parse_media_condition (parser);
if (condition == NULL)
{
g_ptr_array_unref (a);
return NULL;
}
g_ptr_array_add (a, condition);
}
condition = svg_css_media_condition_compound (SVG_CSS_MEDIA_OR, a->len, (SvgCssMediaCondition **) a->pdata);
g_ptr_array_free (a, FALSE );
}
return condition;
}
void
gtk_svg_update_media (GtkSvg *self)
{
GArray *features;
GtkCssDiscreteMediaFeature feature;
GtkInterfaceColorScheme prefers_color_scheme = GTK_INTERFACE_COLOR_SCHEME_LIGHT;
GtkInterfaceContrast prefers_contrast = GTK_INTERFACE_CONTRAST_NO_PREFERENCE;
GtkReducedMotion prefers_reduced_motion = GTK_REDUCED_MOTION_NO_PREFERENCE;
const char *color_scheme_vals[] = { "light" , "light" , "dark" , "light" };
const char *contrast_vals[] = { "no-preference" , "no-preference" , "more" , "less" };
const char *reduced_motion_vals[] = { "no-preference" , "reduce" };
features = g_array_sized_new (FALSE , FALSE , sizeof (GtkCssDiscreteMediaFeature), 3 );
if (self->settings)
g_object_get (self->settings,
"gtk-interface-color-scheme" , &prefers_color_scheme,
"gtk-interface-contrast" , &prefers_contrast,
"gtk-interface-reduced-motion" , &prefers_reduced_motion,
NULL);
feature.name = "prefers-color-scheme" ;
feature.value = color_scheme_vals[prefers_color_scheme];
g_array_append_vals (features, &feature, 1 );
feature.name = "prefers-contrast" ;
feature.value = contrast_vals[prefers_contrast];
g_array_append_vals (features, &feature, 1 );
feature.name = "prefers-reduced-motion" ;
feature.value = reduced_motion_vals[prefers_reduced_motion];
g_array_append_vals (features, &feature, 1 );
for (GList *l = self->media; l; l = l->next)
{
SvgCssMediaBlock *media = l->data;
svg_css_media_block_evaluate (media, features);
}
g_array_unref (features);
}
Messung V0.5 in Prozent C=98 H=95 G=96
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland