Commit 2eb933da authored by Paolo Borelli's avatar Paolo Borelli

Implement svg switch conditions in rust

parent 0381c490
......@@ -34,7 +34,6 @@ librsvg_@RSVG_API_MAJOR_VERSION@_la_SOURCES = \
librsvg/rsvg-cairo-render.c \
librsvg/rsvg-cairo-render.h \
librsvg/rsvg-cairo.h \
librsvg/rsvg-cond.c \
librsvg/rsvg-css.c \
librsvg/rsvg-css.h \
librsvg/rsvg-defs.c \
......
......@@ -39,9 +39,6 @@
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if you have the `strtok_r' function. */
/* #undef HAVE_STRTOK_R */
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
......
......@@ -151,10 +151,6 @@ esac
GLIB_TESTS
dnl ===========================================================================
AC_CHECK_FUNCS(strtok_r)
# ===========================================================================
# GTK
# ===========================================================================
......
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 sts=4 expandtab: */
/*
rsvg-cond.c: Handle SVG conditionals
Copyright (C) 2004-2005 Dom Lachowicz <cinamod@hotmail.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Author: Dom Lachowicz
*/
#include "config.h"
#include "rsvg-private.h"
#include "rsvg-css.h"
#include "rsvg-styles.h"
#include <string.h>
#include <stdlib.h>
#include <locale.h>
/* Keep these sorted alphabetically! These are used with bsearch() */
static const char *implemented_features[] = {
"http://www.w3.org/TR/SVG11/feature#BasicFilter",
"http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute",
"http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute",
"http://www.w3.org/TR/SVG11/feature#BasicStructure",
"http://www.w3.org/TR/SVG11/feature#BasicText",
"http://www.w3.org/TR/SVG11/feature#ConditionalProcessing",
"http://www.w3.org/TR/SVG11/feature#ContainerAttribute",
"http://www.w3.org/TR/SVG11/feature#Filter",
"http://www.w3.org/TR/SVG11/feature#Gradient",
"http://www.w3.org/TR/SVG11/feature#Image",
"http://www.w3.org/TR/SVG11/feature#Marker",
"http://www.w3.org/TR/SVG11/feature#Mask",
"http://www.w3.org/TR/SVG11/feature#OpacityAttribute",
"http://www.w3.org/TR/SVG11/feature#Pattern",
"http://www.w3.org/TR/SVG11/feature#SVG",
"http://www.w3.org/TR/SVG11/feature#SVG-static",
"http://www.w3.org/TR/SVG11/feature#Shape",
"http://www.w3.org/TR/SVG11/feature#Structure",
"http://www.w3.org/TR/SVG11/feature#Style",
"http://www.w3.org/TR/SVG11/feature#View",
"org.w3c.svg.static" /* deprecated SVG 1.0 feature string */
};
static const guint nb_implemented_features = G_N_ELEMENTS (implemented_features);
static const char **implemented_extensions = NULL;
static const guint nb_implemented_extensions = 0;
static int
rsvg_feature_compare (const void *a, const void *b)
{
return strcmp ((const char *) a, *(const char **) b);
}
/* http://www.w3.org/TR/SVG/struct.html#RequiredFeaturesAttribute */
static gboolean
rsvg_cond_fulfills_requirement (const char *value, const char **features, guint nb_features)
{
guint nb_elems = 0;
char **elems;
gboolean permitted = TRUE;
elems = rsvg_css_parse_list (value, &nb_elems);
if (elems && nb_elems) {
guint i;
for (i = 0; (i < nb_elems) && permitted; i++)
if (!bsearch (elems[i], features, nb_features, sizeof (char *), rsvg_feature_compare))
permitted = FALSE;
g_strfreev (elems);
} else
permitted = FALSE;
return permitted;
}
/* http://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute */
static gboolean
rsvg_locale_compare (const char *a, const char *b)
{
const char *hyphen;
/* check for an exact-ish match first */
if (!g_ascii_strncasecmp (a, b, strlen (b)))
return TRUE;
/* check to see if there's a hyphen */
hyphen = strstr (b, "-");
if (!hyphen)
return FALSE;
/* compare up to the hyphen */
return !g_ascii_strncasecmp (a, b, (hyphen - b));
}
/* http://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute */
static gboolean
rsvg_cond_parse_system_language (const char *value)
{
guint nb_elems = 0;
char **elems;
gboolean permitted = FALSE;
elems = rsvg_css_parse_list (value, &nb_elems);
if (elems && nb_elems) {
const gchar * const *languages;
languages = g_get_language_names ();
if (languages) {
guint i, j;
for (i = 0; (i < nb_elems) && !permitted; i++) {
for (j = 0; languages[j] && !permitted; j++) {
permitted = rsvg_locale_compare (languages[j], elems[i]);
}
}
}
g_strfreev (elems);
}
return permitted;
}
/* returns TRUE if this element should be processed according to <switch> semantics
http://www.w3.org/TR/SVG/struct.html#SwitchElement */
gboolean
rsvg_eval_switch_attributes (RsvgPropertyBag * atts, gboolean * p_has_cond)
{
gboolean required_features_ok = TRUE;
gboolean required_extensions_ok = TRUE;
gboolean system_language_ok = TRUE;
gboolean has_cond = FALSE;
RsvgPropertyBagIter *iter;
const char *key;
RsvgAttribute attr;
const char *value;
iter = rsvg_property_bag_iter_begin (atts);
while (rsvg_property_bag_iter_next (iter, &key, &attr, &value)) {
switch (attr) {
case RSVG_ATTRIBUTE_REQUIRED_FEATURES:
required_features_ok = rsvg_cond_fulfills_requirement (value, implemented_features,
nb_implemented_features);
has_cond = TRUE;
break;
case RSVG_ATTRIBUTE_REQUIRED_EXTENSIONS:
required_extensions_ok = rsvg_cond_fulfills_requirement (value, implemented_extensions,
nb_implemented_extensions);
has_cond = TRUE;
break;
case RSVG_ATTRIBUTE_SYSTEM_LANGUAGE:
system_language_ok = rsvg_cond_parse_system_language (value);
has_cond = TRUE;
break;
default:
break;
}
}
rsvg_property_bag_iter_end (iter);
if (p_has_cond)
*p_has_cond = has_cond;
return required_features_ok && required_extensions_ok && system_language_ok;
}
......@@ -254,89 +254,6 @@ rsvg_css_parse_font_family (const char *str, gboolean * inherit)
return str;
}
#if !defined(HAVE_STRTOK_R)
static char *
strtok_r (char *s, const char *delim, char **last)
{
char *p;
if (s == NULL)
s = *last;
if (s == NULL)
return NULL;
while (*s && strchr (delim, *s))
s++;
if (*s == '\0') {
*last = NULL;
return NULL;
}
p = s;
while (*p && !strchr (delim, *p))
p++;
if (*p == '\0')
*last = NULL;
else {
*p = '\0';
p++;
*last = p;
}
return s;
}
#endif /* !HAVE_STRTOK_R */
gchar **
rsvg_css_parse_list (const char *in_str, guint * out_list_len)
{
char *ptr, *tok;
char *str;
guint n = 0;
GSList *string_list = NULL;
gchar **string_array = NULL;
str = g_strdup (in_str);
tok = strtok_r (str, ", \t", &ptr);
if (tok != NULL) {
if (strcmp (tok, " ") != 0) {
string_list = g_slist_prepend (string_list, g_strdup (tok));
n++;
}
while ((tok = strtok_r (NULL, ", \t", &ptr)) != NULL) {
if (strcmp (tok, " ") != 0) {
string_list = g_slist_prepend (string_list, g_strdup (tok));
n++;
}
}
}
g_free (str);
if (out_list_len)
*out_list_len = n;
if (string_list) {
GSList *slist;
string_array = g_new0 (gchar *, n + 1);
string_array[n--] = NULL;
for (slist = string_list; slist; slist = slist->next)
string_array[n--] = (gchar *) slist->data;
g_slist_free (string_list);
}
return string_array;
}
gboolean
rsvg_css_parse_overflow (const char *str, gboolean * inherit)
{
......
......@@ -104,9 +104,6 @@ const char *rsvg_css_parse_font_family (const char *str, gboolean * inherit
G_GNUC_INTERNAL
gboolean rsvg_css_parse_number_optional_number (const char *str, double *out_x, double *out_y);
G_GNUC_INTERNAL
gchar **rsvg_css_parse_list (const char *in_str, guint * out_list_len);
/* Keep in sync with rust/src/parsers.rs:NumberListLength */
typedef enum {
NUMBER_LIST_LENGTH_EXACT,
......
......@@ -472,8 +472,18 @@ GdkPixbuf *rsvg_pixbuf_from_data_with_size_data (const guchar * buff,
size_t len,
gpointer data,
const char *base_uri, GError ** error);
/* Implemented in rust/src/cond.rs */
G_GNUC_INTERNAL
gboolean rsvg_cond_check_required_features (const char *value);
/* Implemented in rust/src/cond.rs */
G_GNUC_INTERNAL
gboolean rsvg_cond_check_required_extensions (const char *value);
/* Implemented in rust/src/cond.rs */
G_GNUC_INTERNAL
gboolean rsvg_eval_switch_attributes (RsvgPropertyBag * atts, gboolean * p_has_cond);
gboolean rsvg_cond_check_system_language (const char *value);
G_GNUC_INTERNAL
void rsvg_pop_discrete_layer (RsvgDrawingCtx * ctx);
......
......@@ -1173,6 +1173,53 @@ rsvg_parse_style_pair (RsvgState *state,
}
}
/* returns TRUE if this element should be processed according to <switch> semantics
http://www.w3.org/TR/SVG/struct.html#SwitchElement */
static gboolean
rsvg_eval_switch_attributes (RsvgPropertyBag * atts, gboolean * p_has_cond)
{
gboolean required_features_ok = TRUE;
gboolean required_extensions_ok = TRUE;
gboolean system_language_ok = TRUE;
gboolean has_cond = FALSE;
RsvgPropertyBagIter *iter;
const char *key;
RsvgAttribute attr;
const char *value;
iter = rsvg_property_bag_iter_begin (atts);
while (rsvg_property_bag_iter_next (iter, &key, &attr, &value)) {
switch (attr) {
case RSVG_ATTRIBUTE_REQUIRED_FEATURES:
required_features_ok = rsvg_cond_check_required_features (value);
has_cond = TRUE;
break;
case RSVG_ATTRIBUTE_REQUIRED_EXTENSIONS:
required_extensions_ok = rsvg_cond_check_required_extensions (value);
has_cond = TRUE;
break;
case RSVG_ATTRIBUTE_SYSTEM_LANGUAGE:
system_language_ok = rsvg_cond_check_system_language (value);
has_cond = TRUE;
break;
default:
break;
}
}
rsvg_property_bag_iter_end (iter);
if (p_has_cond)
*p_has_cond = has_cond;
return required_features_ok && required_extensions_ok && system_language_ok;
}
/* take a pair of the form (fill="#ff00ff") and parse it as a style */
void
rsvg_parse_presentation_attributes (RsvgState * state, RsvgPropertyBag * atts)
......
use glib;
use glib_sys;
use libc;
use error::*;
use parsers::Parse;
use std::marker::PhantomData;
use util::utf8_cstr;
use self::glib::translate::*;
// No extensions at the moment.
static IMPLEMENTED_EXTENSIONS: &[&str] = &[];
#[derive(Debug, PartialEq)]
struct RequiredExtensions(bool);
impl Parse for RequiredExtensions {
type Data = ();
type Err = AttributeError;
// Parse a requiredExtensions attribute
// http://www.w3.org/TR/SVG/struct.html#RequiredExtensionsAttribute
fn parse(s: &str, _: ()) -> Result<RequiredExtensions, AttributeError> {
Ok(RequiredExtensions(
s.split_whitespace()
.all(|f| IMPLEMENTED_EXTENSIONS.binary_search(&f).is_ok()),
))
}
}
// Keep these sorted alphabetically for binary_search.
static IMPLEMENTED_FEATURES: &[&str] = &[
"http://www.w3.org/TR/SVG11/feature#BasicFilter",
"http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute",
"http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute",
"http://www.w3.org/TR/SVG11/feature#BasicStructure",
"http://www.w3.org/TR/SVG11/feature#BasicText",
"http://www.w3.org/TR/SVG11/feature#ConditionalProcessing",
"http://www.w3.org/TR/SVG11/feature#ContainerAttribute",
"http://www.w3.org/TR/SVG11/feature#Filter",
"http://www.w3.org/TR/SVG11/feature#Gradient",
"http://www.w3.org/TR/SVG11/feature#Image",
"http://www.w3.org/TR/SVG11/feature#Marker",
"http://www.w3.org/TR/SVG11/feature#Mask",
"http://www.w3.org/TR/SVG11/feature#OpacityAttribute",
"http://www.w3.org/TR/SVG11/feature#Pattern",
"http://www.w3.org/TR/SVG11/feature#SVG",
"http://www.w3.org/TR/SVG11/feature#SVG-static",
"http://www.w3.org/TR/SVG11/feature#Shape",
"http://www.w3.org/TR/SVG11/feature#Structure",
"http://www.w3.org/TR/SVG11/feature#Style",
"http://www.w3.org/TR/SVG11/feature#View",
"org.w3c.svg.static", // deprecated SVG 1.0 feature string
];
#[derive(Debug, PartialEq)]
struct RequiredFeatures(bool);
impl Parse for RequiredFeatures {
type Data = ();
type Err = AttributeError;
// Parse a requiredFeatures attribute
// http://www.w3.org/TR/SVG/struct.html#RequiredFeaturesAttribute
fn parse(s: &str, _: ()) -> Result<RequiredFeatures, AttributeError> {
Ok(RequiredFeatures(
s.split_whitespace()
.all(|f| IMPLEMENTED_FEATURES.binary_search(&f).is_ok()),
))
}
}
#[derive(Debug, PartialEq)]
struct SystemLanguage<'a>(bool, PhantomData<&'a i8>);
impl<'a> Parse for SystemLanguage<'a> {
type Data = &'a [String];
type Err = AttributeError;
// Parse a systemLanguage attribute
// http://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
fn parse(s: &str, system_languages: &[String]) -> Result<SystemLanguage<'a>, AttributeError> {
Ok(SystemLanguage(
s.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.any(|l| {
system_languages.iter().any(|sl| {
if sl.eq_ignore_ascii_case(l) {
return true;
}
if let Some(offset) = l.find('-') {
return sl.eq_ignore_ascii_case(&l[..offset]);
}
false
})
}),
PhantomData,
))
}
}
#[no_mangle]
pub extern "C" fn rsvg_cond_check_required_extensions(
raw_value: *const libc::c_char,
) -> glib_sys::gboolean {
let value = unsafe { utf8_cstr(raw_value) };
match RequiredExtensions::parse(value, ()) {
Ok(RequiredExtensions(res)) => res.to_glib(),
Err(_) => false.to_glib(),
}
}
#[no_mangle]
pub extern "C" fn rsvg_cond_check_required_features(
raw_value: *const libc::c_char,
) -> glib_sys::gboolean {
let value = unsafe { utf8_cstr(raw_value) };
match RequiredFeatures::parse(value, ()) {
Ok(RequiredFeatures(res)) => res.to_glib(),
Err(_) => false.to_glib(),
}
}
#[no_mangle]
pub extern "C" fn rsvg_cond_check_system_language(
raw_value: *const libc::c_char,
) -> glib_sys::gboolean {
let value = unsafe { utf8_cstr(raw_value) };
match SystemLanguage::parse(value, &glib::get_language_names()) {
Ok(SystemLanguage(res, _)) => res.to_glib(),
Err(_) => false.to_glib(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_required_extensions() {
assert_eq!(
RequiredExtensions::parse("http://test.org/NotExisting/1.0", ()),
Ok(RequiredExtensions(false))
);
}
#[test]
fn parse_required_features() {
assert_eq!(
RequiredFeatures::parse("http://www.w3.org/TR/SVG11/feature#NotExisting", ()),
Ok(RequiredFeatures(false))
);
assert_eq!(
RequiredFeatures::parse("http://www.w3.org/TR/SVG11/feature#BasicFilter", ()),
Ok(RequiredFeatures(true))
);
assert_eq!(
RequiredFeatures::parse(
"http://www.w3.org/TR/SVG11/feature#BasicFilter \
http://www.w3.org/TR/SVG11/feature#NotExisting",
()
),
Ok(RequiredFeatures(false))
);
assert_eq!(
RequiredFeatures::parse(
"http://www.w3.org/TR/SVG11/feature#BasicFilter \
http://www.w3.org/TR/SVG11/feature#BasicText",
()
),
Ok(RequiredFeatures(true))
);
}
#[test]
fn parse_system_language() {
let system_languages = vec![String::from("de"), String::from("en_US")];
assert_eq!(
SystemLanguage::parse("", &system_languages),
Ok(SystemLanguage(false, PhantomData))
);
assert_eq!(
SystemLanguage::parse("fr", &system_languages),
Ok(SystemLanguage(false, PhantomData))
);
assert_eq!(
SystemLanguage::parse("de", &system_languages),
Ok(SystemLanguage(true, PhantomData))
);
assert_eq!(
SystemLanguage::parse("en_US", &system_languages),
Ok(SystemLanguage(true, PhantomData))
);
assert_eq!(
SystemLanguage::parse("DE", &system_languages),
Ok(SystemLanguage(true, PhantomData))
);
assert_eq!(
SystemLanguage::parse("de-LU", &system_languages),
Ok(SystemLanguage(true, PhantomData))
);
assert_eq!(
SystemLanguage::parse("fr, de", &system_languages),
Ok(SystemLanguage(true, PhantomData))
);
}
}
......@@ -33,6 +33,12 @@ pub use cnode::{rsvg_rust_cnode_get_impl, rsvg_rust_cnode_new};
pub use color::{rsvg_css_parse_color, AllowCurrentColor, AllowInherit, ColorKind, ColorSpec};
pub use cond::{
rsvg_cond_check_required_extensions,
rsvg_cond_check_required_features,
rsvg_cond_check_system_language,
};
pub use draw::{rsvg_draw_pango_layout, rsvg_draw_path_builder};
pub use gradient::{rsvg_node_linear_gradient_new, rsvg_node_radial_gradient_new};
......@@ -149,6 +155,7 @@ mod chars;
mod clip_path;
mod cnode;
mod color;
mod cond;
mod draw;
mod drawing_ctx;
mod error;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment