/*
* Copyright © 2025 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 "gtksvgtransformprivate.h"
#include "gtksvgvalueprivate.h"
#include "gtksvgnumberprivate.h"
#include "gtksvgstringutilsprivate.h"
#include "gtksvgutilsprivate.h"
#include <tgmath.h>
typedef struct
{
TransformType type;
union {
struct {
double x, y;
} translate, scale;
struct {
double angle;
double x, y;
} rotate;
struct {
double angle;
} skew_x;
struct {
double angle;
} skew_y;
union {
struct {
double xx, yx, xy, yy, dx, dy;
};
double m[6 ];
} matrix;
struct {
double x, y, z;
} translate3d, scale3d;
struct {
double x, y;
} skew;
struct {
double x, y, z;
double angle;
} rotate3d;
struct {
double depth;
} perspective;
struct {
graphene_matrix_t m;
} matrix3d;
};
} PrimitiveTransform;
typedef struct
{
SvgValue base;
unsigned int n_transforms;
PrimitiveTransform transforms[1 ];
} SvgTransform;
static unsigned int
svg_transform_size (unsigned int n)
{
return sizeof (SvgTransform) + (n - 1 ) * sizeof (PrimitiveTransform);
}
static gboolean
primitive_transform_equal (const PrimitiveTransform *t0,
const PrimitiveTransform *t1)
{
if (t0->type != t1->type)
return FALSE ;
switch (t0->type)
{
case TRANSFORM_NONE:
return TRUE ;
case TRANSFORM_TRANSLATE:
return t0->translate.x == t1->translate.x &&
t0->translate.y == t1->translate.y;
case TRANSFORM_SCALE:
return t0->scale.x == t1->scale.x &&
t0->scale.y == t1->scale.y;
case TRANSFORM_ROTATE:
return t0->rotate.angle == t1->rotate.angle &&
t0->rotate.x == t1->rotate.x &&
t0->rotate.y == t1->rotate.y;
case TRANSFORM_SKEW_X:
return t0->skew_x.angle == t1->skew_x.angle;
case TRANSFORM_SKEW_Y:
return t0->skew_y.angle == t1->skew_y.angle;
case TRANSFORM_MATRIX:
return t0->matrix.xx == t1->matrix.xx && t0->matrix.yx == t1->matrix.yx &&
t0->matrix.xy == t1->matrix.xy && t0->matrix.yy == t1->matrix.yy &&
t0->matrix.dx == t1->matrix.dx && t0->matrix.dy == t1->matrix.dy;
case TRANSFORM_TRANSLATE_3D:
return t0->translate3d.x == t1->translate3d.x &&
t0->translate3d.y == t1->translate3d.y &&
t0->translate3d.z == t1->translate3d.z;
case TRANSFORM_SCALE_3D:
return t0->scale3d.x == t1->scale3d.x &&
t0->scale3d.y == t1->scale3d.y &&
t0->scale3d.z == t1->scale3d.z;
case TRANSFORM_ROTATE_3D:
return t0->rotate3d.angle == t1->rotate3d.angle &&
t0->rotate3d.x == t1->rotate3d.x &&
t0->rotate3d.y == t1->rotate3d.y &&
t0->rotate3d.z == t1->rotate3d.z;
case TRANSFORM_SKEW:
return t0->skew.x == t1->skew.x &&
t0->skew.y == t1->skew.y;
case TRANSFORM_PERSPECTIVE:
return t0->perspective.depth == t1->perspective.depth;
case TRANSFORM_MATRIX_3D:
return graphene_matrix_equal (&t0->matrix3d.m, &t1->matrix3d.m);
default :
g_assert_not_reached ();
}
}
static gboolean
svg_transform_equal (const SvgValue *value0,
const SvgValue *value1)
{
const SvgTransform *t0 = (const SvgTransform *) value0;
const SvgTransform *t1 = (const SvgTransform *) value1;
if (t0->n_transforms != t1->n_transforms)
return FALSE ;
for (unsigned int i = 0 ; i < t1->n_transforms; i++)
{
if (!primitive_transform_equal (&t0->transforms[i], &t1->transforms[i]))
return FALSE ;
}
return TRUE ;
}
static SvgValue *svg_transform_interpolate (const SvgValue *v0,
const SvgValue *v1,
SvgComputeContext *context,
double t);
static SvgValue *svg_transform_accumulate (const SvgValue *v0,
const SvgValue *v1,
SvgComputeContext *context,
int n);
static void svg_transform_print (const SvgValue *v,
GString *string);
static double svg_transform_distance (const SvgValue *v0,
const SvgValue *v1);
static const SvgValueClass SVG_TRANSFORM_CLASS = {
"SvgTransform" ,
svg_value_default_free,
svg_transform_equal,
svg_transform_interpolate,
svg_transform_accumulate,
svg_transform_print,
svg_transform_distance,
svg_value_default_resolve,
};
static SvgTransform *
svg_transform_alloc (unsigned int n)
{
SvgTransform *t;
t = (SvgTransform *) svg_value_alloc (&SVG_TRANSFORM_CLASS, svg_transform_size (n));
t->n_transforms = n;
return t;
}
SvgValue *
svg_transform_new_none (void )
{
static SvgTransform none = { { &SVG_TRANSFORM_CLASS, 0 }, 1 , { { TRANSFORM_NONE, } }};
return (SvgValue *) &none;
}
SvgValue *
svg_transform_new_translate (double x, double y)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_TRANSLATE;
tf->transforms[0 ].translate.x = x;
tf->transforms[0 ].translate.y = y;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_scale (double x, double y)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_SCALE;
tf->transforms[0 ].scale.x = x;
tf->transforms[0 ].scale.y = y;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_rotate (double angle, double x, double y)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_ROTATE;
tf->transforms[0 ].rotate.angle = angle;
tf->transforms[0 ].rotate.x = x;
tf->transforms[0 ].rotate.y = y;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_skew_x (double angle)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_SKEW_X;
tf->transforms[0 ].skew_x.angle = angle;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_skew_y (double angle)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_SKEW_Y;
tf->transforms[0 ].skew_y.angle = angle;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_matrix (double params[6 ])
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_MATRIX;
memcpy (tf->transforms[0 ].matrix.m, params, sizeof (double ) * 6 );
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_rotate_and_shift (double angle,
graphene_point_t *orig,
graphene_point_t *final)
{
SvgTransform *tf = svg_transform_alloc (3 );
tf->transforms[0 ].type = TRANSFORM_TRANSLATE;
tf->transforms[0 ].translate.x = final->x;
tf->transforms[0 ].translate.y = final->y;
tf->transforms[1 ].type = TRANSFORM_ROTATE;
tf->transforms[1 ].rotate.angle = angle;
tf->transforms[1 ].rotate.x = 0 ;
tf->transforms[1 ].rotate.y = 0 ;
tf->transforms[2 ].type = TRANSFORM_TRANSLATE;
tf->transforms[2 ].translate.x = - orig->x;
tf->transforms[2 ].translate.y = - orig->y;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_translate_3d (double x, double y, double z)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_TRANSLATE_3D;
tf->transforms[0 ].translate3d.x = x;
tf->transforms[0 ].translate3d.y = y;
tf->transforms[0 ].translate3d.z = z;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_scale_3d (double x, double y, double z)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_SCALE_3D;
tf->transforms[0 ].scale3d.x = x;
tf->transforms[0 ].scale3d.y = y;
tf->transforms[0 ].scale3d.z = z;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_rotate_3d (double angle,
double x, double y, double z)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_ROTATE_3D;
tf->transforms[0 ].rotate3d.angle = angle;
tf->transforms[0 ].rotate3d.x = x;
tf->transforms[0 ].rotate3d.y = y;
tf->transforms[0 ].rotate3d.z = z;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_skew (double x, double y)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_SKEW;
tf->transforms[0 ].skew.x = x;
tf->transforms[0 ].skew.y = y;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_perspective (double depth)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_PERSPECTIVE;
tf->transforms[0 ].perspective.depth = depth;
return (SvgValue *) tf;
}
SvgValue *
svg_transform_new_matrix_3d (const graphene_matrix_t *m)
{
SvgTransform *tf = svg_transform_alloc (1 );
tf->transforms[0 ].type = TRANSFORM_MATRIX_3D;
graphene_matrix_init_from_matrix (&tf->transforms[0 ].matrix3d.m, m);
return (SvgValue *) tf;
}
unsigned int
css_parser_parse_number (GtkCssParser *parser,
unsigned int n,
gpointer data)
{
ParserNumber *val = data;
double d;
if (!gtk_css_parser_consume_number (parser, &d))
return 0 ;
val[n].value = d;
val[n].unit = SVG_UNIT_NUMBER;
return 1 ;
}
unsigned int
css_parser_parse_number_length (GtkCssParser *parser,
unsigned int n,
gpointer data)
{
ParserNumber *val = data;
const GtkCssToken *token;
token = gtk_css_parser_get_token (parser);
if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER))
{
val[n].value = ((const GtkCssNumberToken *) token)->number;
val[n].unit = SVG_UNIT_NUMBER;
gtk_css_parser_consume_token (parser);
return 1 ;
}
if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_DIMENSION) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_DIMENSION))
{
for (unsigned int i = 0 ; i < SVG_UNIT_TURN; i++)
{
if (svg_unit_dimension (i) == SVG_DIMENSION_LENGTH &&
strcmp (svg_unit_name (i), ((const GtkCssDimensionToken *) token)->dimension) == 0 )
{
val[n].value = ((const GtkCssDimensionToken *) token)->value;
val[n].unit = i;
gtk_css_parser_consume_token (parser);
return TRUE ;
}
}
}
return 0 ;
}
unsigned int
css_parser_parse_number_angle (GtkCssParser *parser,
unsigned int n,
gpointer data)
{
ParserNumber *val = data;
const GtkCssToken *token;
token = gtk_css_parser_get_token (parser);
if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER))
{
val[n].value = ((const GtkCssNumberToken *) token)->number;
val[n].unit = SVG_UNIT_NUMBER;
gtk_css_parser_consume_token (parser);
return 1 ;
}
if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_DIMENSION) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_DIMENSION))
{
for (unsigned int i = 0 ; i <= SVG_UNIT_TURN; i++)
{
if (svg_unit_dimension (i) == SVG_DIMENSION_ANGLE &&
strcmp (svg_unit_name (i), ((const GtkCssDimensionToken *) token)->dimension) == 0 )
{
val[n].value = ((const GtkCssDimensionToken *) token)->value;
val[n].unit = i;
gtk_css_parser_consume_token (parser);
return TRUE ;
}
}
}
return 0 ;
}
unsigned int
css_parser_parse_number_percentage (GtkCssParser *parser,
unsigned int n,
gpointer data)
{
ParserNumber *val = data;
const GtkCssToken *token;
token = gtk_css_parser_get_token (parser);
if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER))
{
val[n].value = ((const GtkCssNumberToken *) token)->number;
val[n].unit = SVG_UNIT_NUMBER;
gtk_css_parser_consume_token (parser);
return 1 ;
}
if (gtk_css_token_is (token, GTK_CSS_TOKEN_PERCENTAGE))
{
val[n].value = ((const GtkCssNumberToken *) token)->number;
val[n].unit = SVG_UNIT_PERCENTAGE;
gtk_css_parser_consume_token (parser);
return 1 ;
}
return 0 ;
}
static gboolean
parse_transform_function (GtkCssParser *self,
unsigned int min_args,
unsigned int max_args,
double *values,
unsigned int (*parse_one) (GtkCssParser *, unsigned int , gpointer))
{
const GtkCssToken *token;
gboolean result = FALSE ;
char func[64 ];
unsigned int arg;
ParserNumber *num;
num = g_newa (ParserNumber, max_args);
token = gtk_css_parser_get_token (self);
g_return_val_if_fail (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION), FALSE );
g_strlcpy (func, gtk_css_token_get_string (token), sizeof (func));
gtk_css_parser_start_block (self);
arg = 0 ;
while (TRUE )
{
unsigned int parse_args;
if (arg >= max_args)
{
gtk_css_parser_error_syntax (self, "Expected ')' at end of %s()" , func);
break ;
}
parse_args = parse_one (self, arg, num);
if (parse_args == 0 )
break ;
if (num[arg].unit == SVG_UNIT_DEG ||
num[arg].unit == SVG_UNIT_RAD ||
num[arg].unit == SVG_UNIT_GRAD ||
num[arg].unit == SVG_UNIT_TURN)
values[arg] = angle_to_deg (num[arg].value, num[arg].unit);
else
values[arg] = num[arg].value;
arg += parse_args;
token = gtk_css_parser_get_token (self);
if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
{
if (arg < min_args)
{
gtk_css_parser_error_syntax (self, "%s() requires at least %u arguments" , func, min_args);
break ;
}
else
{
result = TRUE ;
break ;
}
}
else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA))
{
if (arg >= max_args)
{
gtk_css_parser_error_syntax (self, "Expected ')' at end of %s()" , func);
break ;
}
gtk_css_parser_consume_token (self);
continue ;
}
else if (!gtk_css_parser_has_number (self))
{
gtk_css_parser_error_syntax (self, "Unexpected data at end of %s()" , func);
break ;
}
}
gtk_css_parser_end_block (self);
return result;
}
SvgValue *
svg_transform_parse_css (GtkCssParser *parser)
{
SvgTransform *tf;
GArray *array;
if (gtk_css_parser_try_ident (parser, "none" ))
return svg_transform_new_none ();
array = g_array_new (FALSE , FALSE , sizeof (PrimitiveTransform));
while (TRUE )
{
PrimitiveTransform transform;
memset (&transform, 0 , sizeof (PrimitiveTransform));
if (gtk_css_parser_has_function (parser, "rotate" ))
{
double values[3 ] = { 0 , 0 , 0 };
if (!parse_transform_function (parser, 1 , 3 , values, css_parser_parse_number_angle))
goto fail;
transform.type = TRANSFORM_ROTATE;
transform.rotate.angle = values[0 ];
transform.rotate.x = values[1 ];
transform.rotate.y = values[2 ];
}
else if (gtk_css_parser_has_function (parser, "scale" ))
{
double values[2 ] = { 0 , 0 };
if (!parse_transform_function (parser, 1 , 2 , values, css_parser_parse_number))
goto fail;
transform.type = TRANSFORM_SCALE;
transform.scale.x = values[0 ];
if (values[1 ])
transform.scale.y = values[1 ];
else
transform.scale.y = transform.scale.x;
}
else if (gtk_css_parser_has_function (parser, "translate" ))
{
double values[2 ] = { 0 , 0 };
if (!parse_transform_function (parser, 1 , 2 , values, css_parser_parse_number_length))
goto fail;
transform.type = TRANSFORM_TRANSLATE;
transform.translate.x = values[0 ];
if (values[1 ])
transform.translate.y = values[1 ];
else
transform.translate.y = 0 ;
}
else if (gtk_css_parser_has_function (parser, "skewX" ))
{
double values[1 ];
if (!parse_transform_function (parser, 1 , 1 , values, css_parser_parse_number_angle))
goto fail;
transform.type = TRANSFORM_SKEW_X;
transform.skew_x.angle = values[0 ];
}
else if (gtk_css_parser_has_function (parser, "skewY" ))
{
double values[1 ];
if (!parse_transform_function (parser, 1 , 1 , values, css_parser_parse_number_angle))
goto fail;
transform.type = TRANSFORM_SKEW_Y;
transform.skew_y.angle = values[0 ];
}
else if (gtk_css_parser_has_function (parser, "matrix" ))
{
double values[6 ];
if (!parse_transform_function (parser, 6 , 6 , values, css_parser_parse_number))
goto fail;
transform.type = TRANSFORM_MATRIX;
memcpy (transform.matrix.m, values, sizeof (double ) * 6 );
}
else if (gtk_css_parser_has_function (parser, "translate3d" ))
{
double values[3 ];
if (!parse_transform_function (parser, 3 , 3 , values, css_parser_parse_number_length))
goto fail;
transform.type = TRANSFORM_TRANSLATE_3D;
transform.translate3d.x = values[0 ];
transform.translate3d.y = values[1 ];
transform.translate3d.z = values[2 ];
}
else if (gtk_css_parser_has_function (parser, "scale3d" ))
{
double values[3 ];
if (!parse_transform_function (parser, 3 , 3 , values, css_parser_parse_number))
goto fail;
transform.type = TRANSFORM_SCALE_3D;
transform.scale3d.x = values[0 ];
transform.scale3d.y = values[1 ];
transform.scale3d.z = values[2 ];
}
else if (gtk_css_parser_has_function (parser, "rotate3d" ))
{
double values[4 ];
if (!parse_transform_function (parser, 4 , 4 , values, css_parser_parse_number_angle))
goto fail;
transform.type = TRANSFORM_ROTATE_3D;
transform.rotate3d.x = values[0 ];
transform.rotate3d.y = values[1 ];
transform.rotate3d.z = values[2 ];
transform.rotate3d.angle = values[3 ];
}
else if (gtk_css_parser_has_function (parser, "skew" ))
{
double values[2 ];
if (!parse_transform_function (parser, 2 , 2 , values, css_parser_parse_number_angle))
goto fail;
transform.type = TRANSFORM_SKEW;
transform.skew.x = values[0 ];
transform.skew.y = values[1 ];
}
else if (gtk_css_parser_has_function (parser, "perspective" ))
{
double values[1 ];
if (!parse_transform_function (parser, 1 , 1 , values, css_parser_parse_number_length))
goto fail;
transform.type = TRANSFORM_PERSPECTIVE;
transform.perspective.depth = values[0 ];
}
else if (gtk_css_parser_has_function (parser, "matrix3d" ))
{
double values[16 ];
float floats[16 ];
if (!parse_transform_function (parser, 16 , 16 , values, css_parser_parse_number))
goto fail;
transform.type = TRANSFORM_MATRIX_3D;
for (unsigned int i = 0 ; i < 16 ; i++)
floats[i] = values[i];
graphene_matrix_init_from_float (&transform.matrix3d.m, floats);
}
else
{
break ;
}
g_array_append_val (array, transform);
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA))
gtk_css_parser_skip (parser);
}
if (array->len == 0 )
goto fail;
tf = svg_transform_alloc (array->len);
memcpy (tf->transforms, array->data, sizeof (PrimitiveTransform) * array->len);
g_array_free (array, TRUE );
return (SvgValue *) tf;
fail:
g_array_free (array, TRUE );
gtk_css_parser_error_syntax (parser, "Expected a transform" );
return NULL;
}
SvgValue *
svg_transform_parse (const char *value)
{
GtkCssParser *parser = parser_new_for_string (value);
SvgValue *tf;
tf = svg_transform_parse_css (parser);
if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
g_clear_pointer (&tf, svg_value_unref);
gtk_css_parser_unref (parser);
return tf;
}
SvgValue *
primitive_transform_parse (TransformType type,
const char *string)
{
GtkCssParser *parser = parser_new_for_string (string);
double angle, x, y;
SvgUnit u;
SvgValue *value = NULL;
gtk_css_parser_skip_whitespace (parser);
switch ((unsigned int ) type)
{
case TRANSFORM_TRANSLATE:
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &x, &u))
{
skip_whitespace_and_optional_comma (parser);
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &y, &u))
value = svg_transform_new_translate (x, y);
else
value = svg_transform_new_translate (x, 0 );
}
break ;
case TRANSFORM_SCALE:
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &x, &u))
{
skip_whitespace_and_optional_comma (parser);
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &y, &u))
value = svg_transform_new_scale (x, y);
else
value = svg_transform_new_scale (x, x);
}
break ;
case TRANSFORM_ROTATE:
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &angle, &u))
{
skip_whitespace_and_optional_comma (parser);
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &x, &u))
{
skip_whitespace_and_optional_comma (parser);
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &y, &u))
value = svg_transform_new_rotate (angle, x, y);
else
value = svg_transform_new_rotate (angle, x, 0 );
}
else
value = svg_transform_new_rotate (angle, 0 , 0 );
}
break ;
case TRANSFORM_SKEW_X:
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &angle, &u))
value = svg_transform_new_skew_x (angle);
break ;
case TRANSFORM_SKEW_Y:
if (svg_number_parse2 (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER, &angle, &u))
value = svg_transform_new_skew_y (angle);
break ;
default :
g_assert_not_reached ();
}
gtk_css_parser_unref (parser);
return value;
}
void
svg_primitive_transform_print (const SvgValue *value,
GString *s)
{
const SvgTransform *tf = (const SvgTransform *) value;
g_assert (tf->n_transforms == 1 );
switch (tf->transforms[0 ].type)
{
case TRANSFORM_TRANSLATE:
string_append_double (s, "" , tf->transforms[0 ].translate.x);
string_append_double (s, " " , tf->transforms[0 ].translate.y);
break ;
case TRANSFORM_SCALE:
string_append_double (s, "" , tf->transforms[0 ].scale.x);
string_append_double (s, " " , tf->transforms[0 ].scale.y);
break ;
case TRANSFORM_ROTATE:
string_append_double (s, "" , tf->transforms[0 ].rotate.angle);
string_append_double (s, " " , tf->transforms[0 ].rotate.x);
string_append_double (s, " " , tf->transforms[0 ].rotate.y);
break ;
case TRANSFORM_SKEW_X:
string_append_double (s, "" , tf->transforms[0 ].skew_x.angle);
break ;
case TRANSFORM_SKEW_Y:
string_append_double (s, "" , tf->transforms[0 ].skew_y.angle);
break ;
case TRANSFORM_MATRIX:
case TRANSFORM_TRANSLATE_3D:
case TRANSFORM_SCALE_3D:
case TRANSFORM_ROTATE_3D:
case TRANSFORM_SKEW:
case TRANSFORM_PERSPECTIVE:
case TRANSFORM_MATRIX_3D:
case TRANSFORM_NONE:
default :
g_assert_not_reached ();
}
}
static void
svg_transform_print (const SvgValue *value,
GString *s)
{
const SvgTransform *tf = (const SvgTransform *) value;
for (unsigned int i = 0 ; i < tf->n_transforms; i++)
{
const PrimitiveTransform *t = &tf->transforms[i];
if (i > 0 )
g_string_append_c (s, ' ' );
switch (t->type)
{
case TRANSFORM_NONE:
g_string_append (s, "none" );
break ;
case TRANSFORM_TRANSLATE:
string_append_double (s, "translate(" , t->translate.x);
string_append_double (s, ", " , t->translate.y);
g_string_append (s, ")" );
break ;
case TRANSFORM_SCALE:
string_append_double (s, "scale(" , t->scale.x);
string_append_double (s, ", " , t->scale.y);
g_string_append (s, ")" );
break ;
case TRANSFORM_ROTATE:
string_append_double (s, "rotate(" , t->rotate.angle);
string_append_double (s, ", " , t->rotate.x);
string_append_double (s, ", " , t->rotate.y);
g_string_append (s, ")" );
break ;
case TRANSFORM_SKEW_X:
string_append_double (s, "skewX(" , t->skew_x.angle);
g_string_append (s, ")" );
break ;
case TRANSFORM_SKEW_Y:
string_append_double (s, "skewY(" , t->skew_y.angle);
g_string_append (s, ")" );
break ;
case TRANSFORM_MATRIX:
string_append_double (s, "matrix(" , t->matrix.xx);
string_append_double (s, ", " , t->matrix.yx);
string_append_double (s, ", " , t->matrix.xy);
string_append_double (s, ", " , t->matrix.yy);
string_append_double (s, ", " , t->matrix.dx);
string_append_double (s, ", " , t->matrix.dy);
g_string_append (s, ")" );
break ;
case TRANSFORM_TRANSLATE_3D:
string_append_double (s, "translate3d(" , t->translate3d.x);
string_append_double (s, ", " , t->translate3d.y);
string_append_double (s, ", " , t->translate3d.z);
g_string_append (s, ")" );
break ;
case TRANSFORM_SCALE_3D:
string_append_double (s, "scale3d(" , t->scale3d.x);
string_append_double (s, ", " , t->scale3d.y);
string_append_double (s, ", " , t->scale3d.z);
g_string_append (s, ")" );
break ;
case TRANSFORM_ROTATE_3D:
string_append_double (s, "rotate3d(" , t->rotate3d.x);
string_append_double (s, ", " , t->rotate3d.y);
string_append_double (s, ", " , t->rotate3d.z);
string_append_double (s, ", " , t->rotate3d.angle);
g_string_append (s, ")" );
break ;
case TRANSFORM_PERSPECTIVE:
string_append_double (s, "perspective(" , t->perspective.depth);
g_string_append (s, ")" );
break ;
case TRANSFORM_SKEW:
string_append_double (s, "skew(" , t->skew.x);
string_append_double (s, ", " , t->skew.y);
g_string_append (s, ")" );
break ;
case TRANSFORM_MATRIX_3D:
{
float v[16 ];
graphene_matrix_to_float (&t->matrix3d.m, v);
string_append_double (s, "matrix(" , v[0 ]);
for (unsigned int j = 1 ; j < 16 ; j++)
{
if (j % 4 == 0 )
string_append_double (s, ",\n " , v[j]);
else
string_append_double (s, ", " , v[j]);
}
g_string_append (s, ")" );
}
break ;
default :
g_assert_not_reached ();
}
}
}
static void
interpolate_matrices (double t,
const double m0[6 ],
const double m1[6 ],
double m[6 ])
{
graphene_matrix_t mat0, mat1, res;
graphene_matrix_init_from_2d (&mat0, m0[0 ], m0[1 ], m0[2 ], m0[3 ], m0[4 ], m0[5 ]);
graphene_matrix_init_from_2d (&mat1, m1[0 ], m1[1 ], m1[2 ], m1[3 ], m1[4 ], m1[5 ]);
graphene_matrix_interpolate (&mat0, &mat1, t, &res);
graphene_matrix_to_2d (&res, &m[0 ], &m[1 ], &m[2 ], &m[3 ], &m[4 ], &m[5 ]);
}
static GskTransform *
primitive_transform_apply (const PrimitiveTransform *transform,
GskTransform *next)
{
switch (transform->type)
{
case TRANSFORM_NONE:
return next;
case TRANSFORM_TRANSLATE:
return gsk_transform_translate (next,
&GRAPHENE_POINT_INIT (
transform->translate.x,
transform->translate.y
));
case TRANSFORM_SCALE:
return gsk_transform_scale (next,
transform->scale.x,
transform->scale.y);
case TRANSFORM_ROTATE:
return gsk_transform_translate (
gsk_transform_rotate (
gsk_transform_translate (next,
&GRAPHENE_POINT_INIT (
transform->rotate.x,
transform->rotate.y
)),
transform->rotate.angle),
&GRAPHENE_POINT_INIT (
-transform->rotate.x,
-transform->rotate.y
));
case TRANSFORM_SKEW_X:
return gsk_transform_skew (next, transform->skew_x.angle, 0 );
case TRANSFORM_SKEW_Y:
return gsk_transform_skew (next, 0 , transform->skew_y.angle);
case TRANSFORM_MATRIX:
return gsk_transform_matrix_2d (next,
transform->matrix.xx, transform->matrix.yx,
transform->matrix.xy, transform->matrix.yy,
transform->matrix.dx, transform->matrix.dy);
case TRANSFORM_TRANSLATE_3D:
return gsk_transform_translate_3d (next,
&GRAPHENE_POINT3D_INIT (
transform->translate3d.x,
transform->translate3d.y,
transform->translate3d.z));
case TRANSFORM_SCALE_3D:
return gsk_transform_scale_3d (next,
transform->scale3d.x,
transform->scale3d.y,
transform->scale3d.z);
case TRANSFORM_ROTATE_3D:
{
graphene_vec3_t v3;
return gsk_transform_rotate_3d (next,
transform->rotate3d.angle,
graphene_vec3_init (&v3,
transform->rotate3d.x,
transform->rotate3d.y,
transform->rotate3d.z));
}
case TRANSFORM_SKEW:
return gsk_transform_skew (next, transform->skew.x, transform->skew.y);
case TRANSFORM_PERSPECTIVE:
return gsk_transform_perspective (next, transform->perspective.depth);
case TRANSFORM_MATRIX_3D:
return gsk_transform_matrix (next, &transform->matrix3d.m);
default :
g_assert_not_reached ();
}
}
static SvgValue *
svg_transform_interpolate (const SvgValue *value0,
const SvgValue *value1,
SvgComputeContext *context,
double t)
{
static const PrimitiveTransform identity[] = {
{ .type = TRANSFORM_TRANSLATE, .translate = { 0 , 0 } },
{ .type = TRANSFORM_SCALE, .scale = { 1 , 1 } },
{ .type = TRANSFORM_ROTATE, .rotate = { 0 , 0 , 0 } },
{ .type = TRANSFORM_SKEW_X, .skew_x = { 0 } },
{ .type = TRANSFORM_SKEW_Y, .skew_y = { 0 } },
{ .type = TRANSFORM_MATRIX, .matrix = { .m = { 1 , 0 , 0 , 1 , 0 , 0 } } },
{ .type = TRANSFORM_TRANSLATE_3D, .translate3d = { 0 , 0 , 0 } },
{ .type = TRANSFORM_SCALE_3D, .scale3d = { 1 , 1 , 1 } },
{ .type = TRANSFORM_ROTATE_3D, .rotate3d = { 0 , 1 , 0 , 0 } },
{ .type = TRANSFORM_SKEW, .skew = { 0 , 0 } },
};
static PrimitiveTransform id_mat3 = { 0 , };
const SvgTransform *tf0 = (const SvgTransform *) value0;
const SvgTransform *tf1 = (const SvgTransform *) value1;
SvgTransform *tf;
if (id_mat3.type == 0 )
{
id_mat3.type = TRANSFORM_MATRIX_3D;
graphene_matrix_init_identity (&id_mat3.matrix3d.m);
}
tf = svg_transform_alloc (MAX (tf0->n_transforms, tf1->n_transforms));
for (unsigned int i = 0 ; i < tf->n_transforms; i++)
{
const PrimitiveTransform *p0;
const PrimitiveTransform *p1;
PrimitiveTransform *p = &tf->transforms[i];
if (i < tf0->n_transforms)
p0 = &tf0->transforms[i];
else if (tf1->transforms[i].type == TRANSFORM_PERSPECTIVE ||
tf1->transforms[i].type == TRANSFORM_MATRIX_3D)
p0 = &id_mat3;
else
p0 = &identity[tf1->transforms[i].type];
if (i < tf1->n_transforms)
p1 = &tf1->transforms[i];
else if (tf0->transforms[i].type == TRANSFORM_PERSPECTIVE ||
tf0->transforms[i].type == TRANSFORM_MATRIX_3D)
p1 = &id_mat3;
else
p1 = &identity[tf0->transforms[i].type];
if (p0->type != p1->type)
{
GskTransform *transform = NULL;
graphene_matrix_t mat1, mat2, res;
transform = NULL;
for (unsigned int j = i; j < tf0->n_transforms; j++)
transform = primitive_transform_apply (&tf0->transforms[j], transform);
gsk_transform_to_matrix (transform, &mat1);
g_clear_pointer (&transform, gsk_transform_unref);
for (unsigned int j = i; j < tf1->n_transforms; j++)
transform = primitive_transform_apply (&tf1->transforms[j], transform);
gsk_transform_to_matrix (transform, &mat2);
gsk_transform_unref (transform);
graphene_matrix_interpolate (&mat1, &mat2, t, &res);
p->type = TRANSFORM_MATRIX;
graphene_matrix_to_2d (&res,
&p->matrix.m[0 ], &p->matrix.m[1 ],
&p->matrix.m[2 ], &p->matrix.m[3 ],
&p->matrix.m[4 ], &p->matrix.m[5 ]);
tf->n_transforms = i + 1 ;
break ;
}
p->type = p0->type;
switch (p0->type)
{
case TRANSFORM_NONE:
break ;
case TRANSFORM_TRANSLATE:
p->translate.x = lerp (t, p0->translate.x, p1->translate.x);
p->translate.y = lerp (t, p0->translate.y, p1->translate.y);
break ;
case TRANSFORM_SCALE:
p->scale.x = lerp (t, p0->scale.x, p1->scale.x);
p->scale.y = lerp (t, p0->scale.y, p1->scale.y);
break ;
case TRANSFORM_ROTATE:
p->rotate.angle = lerp (t, p0->rotate.angle, p1->rotate.angle);
p->rotate.x = lerp (t, p0->rotate.x, p1->rotate.x);
p->rotate.y = lerp (t, p0->rotate.y, p1->rotate.y);
break ;
case TRANSFORM_SKEW_X:
p->skew_x.angle = lerp (t, p0->skew_x.angle, p1->skew_x.angle);
break ;
case TRANSFORM_SKEW_Y:
p->skew_y.angle = lerp (t, p0->skew_y.angle, p1->skew_y.angle);
break ;
case TRANSFORM_MATRIX:
interpolate_matrices (t, p0->matrix.m, p1->matrix.m, p->matrix.m);
break ;
case TRANSFORM_TRANSLATE_3D:
p->translate3d.x = lerp (t, p0->translate3d.x, p1->translate3d.x);
p->translate3d.y = lerp (t, p0->translate3d.y, p1->translate3d.y);
p->translate3d.z = lerp (t, p0->translate3d.z, p1->translate3d.z);
break ;
case TRANSFORM_SCALE_3D:
p->scale3d.x = lerp (t, p0->scale3d.x, p1->scale3d.x);
p->scale3d.y = lerp (t, p0->scale3d.y, p1->scale3d.y);
p->scale3d.z = lerp (t, p0->scale3d.z, p1->scale3d.z);
break ;
case TRANSFORM_ROTATE_3D:
p->rotate3d.angle = lerp (t, p0->rotate3d.angle, p1->rotate3d.angle);
p->rotate3d.x = lerp (t, p0->rotate3d.x, p1->rotate3d.x);
p->rotate3d.y = lerp (t, p0->rotate3d.y, p1->rotate3d.y);
p->rotate3d.z = lerp (t, p0->rotate3d.z, p1->rotate3d.z);
break ;
case TRANSFORM_SKEW:
p->skew.x = lerp (t, p0->skew.x, p1->skew.x);
p->skew.y = lerp (t, p0->skew.y, p1->skew.y);
break ;
case TRANSFORM_PERSPECTIVE:
p->perspective.depth = lerp (t, p0->perspective.depth, p1->perspective.depth);
break ;
case TRANSFORM_MATRIX_3D:
graphene_matrix_interpolate (&p0->matrix3d.m, &p1->matrix3d.m, t, &p->matrix3d.m);
break ;
default :
g_assert_not_reached ();
}
}
return (SvgValue *) tf;
}
static SvgValue *
svg_transform_accumulate (const SvgValue *value0,
const SvgValue *value1,
SvgComputeContext *context,
int n)
{
const SvgTransform *tf0 = (const SvgTransform *) value0;
const SvgTransform *tf1 = (const SvgTransform *) value1;
SvgTransform *tf;
int n0;
if (tf1->n_transforms == 1 && tf1->transforms[0 ].type == TRANSFORM_NONE)
return svg_value_ref ((SvgValue *) value0);
if (tf0->n_transforms == 1 && tf0->transforms[0 ].type == TRANSFORM_NONE)
{
if (n == 1 )
return svg_value_ref ((SvgValue *) value1);
n0 = 0 ;
}
else
n0 = tf0->n_transforms;
/* special-case this one */
if (tf1->n_transforms == 1 &&
tf1->transforms[0 ].type != TRANSFORM_MATRIX &&
tf1->transforms[0 ].type != TRANSFORM_MATRIX_3D &&
tf1->transforms[0 ].type != TRANSFORM_PERSPECTIVE)
{
PrimitiveTransform *p;
tf = svg_transform_alloc (n0 + 1 );
p = &tf->transforms[0 ];
memcpy (p, tf1->transforms, sizeof (PrimitiveTransform));
switch (p->type)
{
case TRANSFORM_NONE:
break ;
case TRANSFORM_TRANSLATE:
p->translate.x *= n;
p->translate.y *= n;
break ;
case TRANSFORM_SCALE:
p->scale.x = pow (p->scale.x, (double ) n);
p->scale.y = pow (p->scale.y, (double ) n);
break ;
case TRANSFORM_ROTATE:
p->rotate.angle *= n;
break ;
case TRANSFORM_SKEW_X:
p->skew_x.angle *= n;
break ;
case TRANSFORM_SKEW_Y:
p->skew_y.angle *= n;
break ;
case TRANSFORM_TRANSLATE_3D:
p->translate3d.x *= n;
p->translate3d.y *= n;
p->translate3d.z *= n;
break ;
case TRANSFORM_SCALE_3D:
p->scale3d.x = pow (p->scale3d.x, (double ) n);
p->scale3d.y = pow (p->scale3d.y, (double ) n);
p->scale3d.z = pow (p->scale3d.z, (double ) n);
break ;
case TRANSFORM_ROTATE_3D:
p->rotate3d.angle *= n;
break ;
case TRANSFORM_SKEW:
p->skew.x *= n; /* No idea if this is correct */
p->skew.y *= n;
break ;
case TRANSFORM_PERSPECTIVE:
case TRANSFORM_MATRIX:
case TRANSFORM_MATRIX_3D:
default :
g_assert_not_reached ();
}
if (n0 > 0 )
memcpy (tf->transforms + 1 ,
tf0->transforms,
n0 * sizeof (PrimitiveTransform));
}
else
{
/* For the general case, simply concatenate all the transforms */
tf = svg_transform_alloc (n0 + n * tf1->n_transforms);
for (unsigned int i = 0 ; i < n; i++)
memcpy (&tf->transforms[i * tf1->n_transforms],
tf1->transforms,
tf1->n_transforms * sizeof (PrimitiveTransform));
if (n0 > 0 )
memcpy (&tf->transforms[n * tf1->n_transforms],
tf0->transforms,
n0 * sizeof (PrimitiveTransform));
}
return (SvgValue *) tf;
}
GskTransform *
svg_transform_get_gsk (const SvgValue *value)
{
const SvgTransform *tf = (const SvgTransform *) value;
GskTransform *t = NULL;
g_assert (value->class == &SVG_TRANSFORM_CLASS);
if (!tf)
return NULL;
for (unsigned int i = 0 ; i < tf->n_transforms; i++)
t = primitive_transform_apply (&tf->transforms[i], t);
return t;
}
static double
hypot3 (double x, double y, double z)
{
return sqrt (x * x + y * y + z * z);
}
static double
svg_transform_distance (const SvgValue *value0,
const SvgValue *value1)
{
const SvgTransform *tf0 = (const SvgTransform *) value0;
const SvgTransform *tf1 = (const SvgTransform *) value1;
const PrimitiveTransform *p0 = &tf0->transforms[0 ];
const PrimitiveTransform *p1 = &tf1->transforms[0 ];
if (tf0->n_transforms > 1 || tf1->n_transforms > 1 )
{
g_warning ("Can't determine distance between complex transforms" );
return 1 ;
}
if (p0->type != p1->type)
{
g_warning ("Can't determine distance between different "
"primitive transform types" );
return 1 ;
}
switch (p0->type)
{
case TRANSFORM_NONE:
return 0 ;
case TRANSFORM_TRANSLATE:
return hypot (p0->translate.x - p1->translate.x,
p0->translate.y - p1->translate.y);
break ;
case TRANSFORM_SCALE:
return hypot (p0->scale.x - p1->scale.x,
p0->scale.y - p1->scale.y);
break ;
case TRANSFORM_ROTATE:
return hypot (p0->rotate.angle - p1->rotate.angle, 0 );
case TRANSFORM_SKEW_X:
case TRANSFORM_SKEW_Y:
case TRANSFORM_MATRIX:
case TRANSFORM_MATRIX_3D:
case TRANSFORM_SKEW:
case TRANSFORM_PERSPECTIVE:
g_warning ("Can't determine distance between these "
"primitive transforms" );
return 1 ;
case TRANSFORM_TRANSLATE_3D:
return hypot3 (p0->translate3d.x - p1->translate3d.x,
p0->translate3d.y - p1->translate3d.y,
p0->translate3d.z - p1->translate3d.z);
break ;
case TRANSFORM_SCALE_3D:
return hypot3 (p0->scale3d.x - p1->scale3d.x,
p0->scale3d.y - p1->scale3d.y,
p0->scale3d.z - p1->scale3d.z);
break ;
case TRANSFORM_ROTATE_3D:
if (p0->rotate3d.x == p1->rotate3d.x &&
p0->rotate3d.y == p1->rotate3d.y &&
p0->rotate3d.z == p1->rotate3d.z)
{
return hypot (p0->rotate3d.angle - p1->rotate3d.angle, 0 );
}
else
{
g_warning ("Can't determine distance between these "
"primitive transforms" );
return 1 ;
}
default :
g_assert_not_reached ();
}
}
TransformType
svg_transform_get_type (const SvgValue *value,
unsigned int pos)
{
const SvgTransform *tf = (const SvgTransform *) value;
g_assert (value->class == &SVG_TRANSFORM_CLASS);
g_assert (pos < tf->n_transforms);
return tf->transforms[pos].type;
}
TransformType
svg_transform_get_primitive (const SvgValue *value,
unsigned int pos,
double params[6 ])
{
const SvgTransform *tf = (const SvgTransform *) value;
g_assert (value->class == &SVG_TRANSFORM_CLASS);
g_assert (pos < tf->n_transforms);
switch (tf->transforms[pos].type)
{
case TRANSFORM_NONE:
break ;
case TRANSFORM_TRANSLATE:
params[0 ] = tf->transforms[pos].translate.x;
params[1 ] = tf->transforms[pos].translate.y;
break ;
case TRANSFORM_SCALE:
params[0 ] = tf->transforms[pos].scale.x;
params[1 ] = tf->transforms[pos].scale.y;
break ;
case TRANSFORM_ROTATE:
params[0 ] = tf->transforms[pos].rotate.angle;
params[1 ] = tf->transforms[pos].rotate.x;
params[2 ] = tf->transforms[pos].rotate.y;
break ;
case TRANSFORM_SKEW_X:
params[0 ] = tf->transforms[pos].skew_x.angle;
break ;
case TRANSFORM_SKEW_Y:
params[0 ] = tf->transforms[pos].skew_y.angle;
break ;
case TRANSFORM_MATRIX:
memcpy (params, tf->transforms[pos].matrix.m, sizeof (double ) * 6 );
break ;
case TRANSFORM_TRANSLATE_3D:
params[0 ] = tf->transforms[pos].translate3d.x;
params[1 ] = tf->transforms[pos].translate3d.y;
params[2 ] = tf->transforms[pos].translate3d.z;
break ;
case TRANSFORM_SCALE_3D:
params[0 ] = tf->transforms[pos].scale3d.x;
params[1 ] = tf->transforms[pos].scale3d.y;
params[2 ] = tf->transforms[pos].scale3d.z;
break ;
case TRANSFORM_ROTATE_3D:
params[0 ] = tf->transforms[pos].rotate3d.angle;
params[1 ] = tf->transforms[pos].rotate3d.x;
params[2 ] = tf->transforms[pos].rotate3d.y;
params[3 ] = tf->transforms[pos].rotate3d.z;
break ;
case TRANSFORM_SKEW:
params[0 ] = tf->transforms[pos].skew.x;
params[1 ] = tf->transforms[pos].skew.y;
break ;
case TRANSFORM_PERSPECTIVE:
params[0 ] = tf->transforms[pos].perspective.depth;
break ;
case TRANSFORM_MATRIX_3D:
default :
g_assert_not_reached ();
}
return tf->transforms[pos].type;
}
gboolean
svg_transform_is_none (const SvgValue *value)
{
const SvgTransform *tf = (const SvgTransform *) value;
g_assert (value->class == &SVG_TRANSFORM_CLASS);
return tf->transforms[0 ].type == TRANSFORM_NONE;
}
gboolean
svg_value_is_transform (const SvgValue *value)
{
return value->class == &SVG_TRANSFORM_CLASS;
}
unsigned int
svg_transform_get_length (const SvgValue *value)
{
const SvgTransform *tf = (const SvgTransform *) value;
g_assert (value->class == &SVG_TRANSFORM_CLASS);
return tf->n_transforms;
}
SvgValue *
svg_transform_get_transform (const SvgValue *value,
unsigned int pos)
{
SvgTransform *tf = (SvgTransform *) value;
g_assert (value->class == &SVG_TRANSFORM_CLASS);
switch (tf->transforms[pos].type)
{
case TRANSFORM_NONE:
return svg_transform_new_none ();
case TRANSFORM_TRANSLATE:
return svg_transform_new_translate (tf->transforms[pos].translate.x,
tf->transforms[pos].translate.y);
case TRANSFORM_SCALE:
return svg_transform_new_scale (tf->transforms[pos].scale.x,
tf->transforms[pos].scale.y);
case TRANSFORM_ROTATE:
return svg_transform_new_rotate (tf->transforms[pos].rotate.angle,
tf->transforms[pos].rotate.x,
tf->transforms[pos].rotate.y);
case TRANSFORM_SKEW_X:
return svg_transform_new_skew_x (tf->transforms[pos].skew_x.angle);
case TRANSFORM_SKEW_Y:
return svg_transform_new_skew_y (tf->transforms[pos].skew_y.angle);
case TRANSFORM_MATRIX:
return svg_transform_new_matrix (tf->transforms[pos].matrix.m);
case TRANSFORM_TRANSLATE_3D:
return svg_transform_new_translate_3d (tf->transforms[pos].translate3d.x,
tf->transforms[pos].translate3d.y,
tf->transforms[pos].translate3d.z);
case TRANSFORM_SCALE_3D:
return svg_transform_new_scale_3d (tf->transforms[pos].scale3d.x,
tf->transforms[pos].scale3d.y,
tf->transforms[pos].scale3d.z);
case TRANSFORM_ROTATE_3D:
return svg_transform_new_rotate_3d (tf->transforms[pos].rotate3d.angle,
tf->transforms[pos].rotate3d.x,
tf->transforms[pos].rotate3d.y,
tf->transforms[pos].rotate3d.z);
case TRANSFORM_SKEW:
return svg_transform_new_skew (tf->transforms[pos].skew.x,
tf->transforms[pos].skew.y);
case TRANSFORM_PERSPECTIVE:
return svg_transform_new_perspective (tf->transforms[pos].perspective.depth);
case TRANSFORM_MATRIX_3D:
return svg_transform_new_matrix_3d (&tf->transforms[pos].matrix3d.m);
default :
g_assert_not_reached ();
}
}
Messung V0.5 in Prozent C=98 H=95 G=96
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland