Code

Fix compile when lcms is disabled (someone please check that this fix is correct).
[inkscape.git] / src / svg / svg-color.cpp
index 5a7602e4ffee8e5d6fc1ec1dff3c0ecaa32d69f4..7d43b68eef18d75c2e82c6f7d4e967ceb1f2ee95 100644 (file)
 # include "config.h"
 #endif
 
+#include <cstdlib>
+#include <cstdio> // sprintf
+#include <cstring>
+#include <string>
+#include <cassert>
 #include <math.h>
+#include <glib/gmem.h>
+#include <glib.h> // g_assert
+#include <glib/gmessages.h>
 #include <glib/gstrfuncs.h>
 #include <glib/ghash.h>
 #include <glib/gutils.h>
+#include <errno.h>
 
-#include "svg.h"
+#include "strneq.h"
+#include "preferences.h"
+#include "svg-color.h"
+#include "svg-icc-color.h"
+#include "svg-device-color.h"
+
+#if ENABLE_LCMS
+#include <lcms.h>
+#include "color.h"
+#include "color-profile.h"
+#include "document.h"
+#include "inkscape.h"
+#include "profile-manager.h"
+#endif // ENABLE_LCMS
+
+using std::sprintf;
 
 struct SPSVGColor {
     unsigned long rgb;
@@ -182,22 +206,20 @@ static SPSVGColor const sp_svg_color_named[] = {
     { 0x9ACD32, "yellowgreen" }
 };
 
-#define SP_SVG_NUMCOLORS (sizeof(sp_svg_color_named) / sizeof(sp_svg_color_named[0]))
-
 static GHashTable *sp_svg_create_color_hash();
 
 guint32
-sp_svg_read_color(gchar const *str, guint32 def)
+sp_svg_read_color(gchar const *str, guint32 const dfl)
+{
+    return sp_svg_read_color(str, NULL, dfl);
+}
+
+static guint32
+internal_sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 def)
 {
     static GHashTable *colors = NULL;
-    gchar c[32];
     guint32 val = 0;
 
-    /*
-     * todo: handle the rgb (r, g, b) and rgb ( r%, g%, b%), syntax
-     * defined in http://www.w3.org/TR/REC-CSS2/syndata.html#color-units
-     */
-
     if (str == NULL) return def;
     while ((*str <= ' ') && *str) str++;
     if (!*str) return def;
@@ -226,23 +248,26 @@ sp_svg_read_color(gchar const *str, guint32 def)
             /* must be either 3 or 6 digits. */
             return def;
         }
-    } else if (!strncmp(str, "rgb(", 4)) {
-        gboolean hasp, hasd;
+        if (end_ptr) {
+            *end_ptr = str + i;
+        }
+    } else if (strneq(str, "rgb(", 4)) {
+        bool hasp, hasd;
         gchar *s, *e;
         gdouble r, g, b;
 
         s = (gchar *) str + 4;
-        hasp = FALSE;
-        hasd = FALSE;
+        hasp = false;
+        hasd = false;
 
         r = g_ascii_strtod(s, &e);
         if (s == e) return def;
         s = e;
         if (*s == '%') {
-            hasp = TRUE;
+            hasp = true;
             s += 1;
         } else {
-            hasd = TRUE;
+            hasd = true;
         }
         while (*s && g_ascii_isspace(*s)) s += 1;
         if (*s != ',') return def;
@@ -252,10 +277,10 @@ sp_svg_read_color(gchar const *str, guint32 def)
         if (s == e) return def;
         s = e;
         if (*s == '%') {
-            hasp = TRUE;
+            hasp = true;
             s += 1;
         } else {
-            hasd = TRUE;
+            hasd = true;
         }
         while (*s && g_ascii_isspace(*s)) s += 1;
         if (*s != ',') return def;
@@ -265,10 +290,16 @@ sp_svg_read_color(gchar const *str, guint32 def)
         if (s == e) return def;
         s = e;
         if (*s == '%') {
-            hasp = TRUE;
+            hasp = true;
+            s += 1;
         } else {
-            hasd = TRUE;
+            hasd = true;
+        }
+        while(*s && g_ascii_isspace(*s)) s += 1;
+        if (*s != ')') {
+            return def;
         }
+        ++s;
         if (hasp && hasd) return def;
         if (hasp) {
             val = (guint) floor(CLAMP(r, 0.0, 100.0) * 2.559999) << 24;
@@ -279,14 +310,18 @@ sp_svg_read_color(gchar const *str, guint32 def)
             val |= ((guint) CLAMP(g, 0, 255) << 16);
             val |= ((guint) CLAMP(b, 0, 255) << 8);
         }
+        if (end_ptr) {
+            *end_ptr = s;
+        }
         return val;
     } else {
         gint i;
         if (!colors) {
             colors = sp_svg_create_color_hash();
         }
+        gchar c[32];
         for (i = 0; i < 31; i++) {
-            if (str[i] == ';') {
+            if (str[i] == ';' || g_ascii_isspace(str[i])) {
                 c[i] = '\0';
                 break;
             }
@@ -301,15 +336,118 @@ sp_svg_read_color(gchar const *str, guint32 def)
         } else {
             return def;
         }
+        if (end_ptr) {
+            *end_ptr = str + i;
+        }
     }
 
     return (val << 8);
 }
 
-gint
-sp_svg_write_color(gchar *buf, gint buflen, guint32 color)
+guint32
+sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 dfl)
+{
+    /* I've been rather hurried in editing the above to add support for end_ptr, so I'm adding
+     * this check wrapper. */
+    gchar const *end = str;
+    guint32 const ret = internal_sp_svg_read_color(str, &end, dfl);
+    assert(((ret == dfl) && (end == str))
+           || (((ret & 0xff) == 0)
+               && (str < end)));
+    if (str < end) {
+        gchar *buf = (gchar *) g_malloc(end + 1 - str);
+        memcpy(buf, str, end - str);
+        buf[end - str] = '\0';
+        gchar const *buf_end = buf;
+        guint32 const check = internal_sp_svg_read_color(buf, &buf_end, 1);
+        assert(check == ret
+               && buf_end - buf == end - str);
+        g_free(buf);
+
+        if ( end_ptr ) {
+            *end_ptr = end;
+        }
+    }
+    return ret;
+}
+
+
+/**
+ * Converts an RGB colour expressed in form 0x00rrggbb to a CSS/SVG representation of that colour.
+ * The result is valid even in SVG Tiny or non-SVG CSS.
+ */
+static void
+rgb24_to_css(char *const buf, unsigned const rgb24)
+{
+    assert(rgb24 < (1u << 24));
+
+    /* SVG 1.1 Full allows additional colour names not supported by SVG Tiny, but we don't bother
+     * with them: it's good for these colours to be copyable to non-SVG CSS stylesheets and for
+     * documents to be more viewable in SVG Tiny/Basic viewers; and some of the SVG Full names are
+     * less meaningful than hex equivalents anyway.  And it's easier for a person to map from the
+     * restricted set because the only component values are {00,80,ff}, other than silver 0xc0c0c0.
+     */
+
+    char const *src = NULL;
+    switch (rgb24) {
+        /* Extracted mechanically from the table at
+         * http://www.w3.org/TR/REC-html40/types.html#h-6.5 .*/
+        case 0x000000: src = "black"; break;           
+        case 0xc0c0c0: src = "silver"; break;          
+        case 0x808080: src = "gray"; break;            
+        case 0xffffff: src = "white"; break;           
+        case 0x800000: src = "maroon"; break;          
+        case 0xff0000: src = "red"; break;             
+        case 0x800080: src = "purple"; break;          
+        case 0xff00ff: src = "fuchsia"; break;                 
+        case 0x008000: src = "green"; break; 
+        case 0x00ff00: src = "lime"; break;  
+        case 0x808000: src = "olive"; break; 
+        case 0xffff00: src = "yellow"; break;
+        case 0x000080: src = "navy"; break;  
+        case 0x0000ff: src = "blue"; break;  
+        case 0x008080: src = "teal"; break;  
+        case 0x00ffff: src = "aqua"; break;  
+
+        default: {
+            if ((rgb24 & 0xf0f0f) * 0x11 == rgb24) {
+                /* Can use the shorter three-digit form #rgb instead of #rrggbb. */
+                sprintf(buf, "#%x%x%x",
+                        (rgb24 >> 16) & 0xf,
+                        (rgb24 >> 8) & 0xf,
+                        rgb24 & 0xf);
+            } else {
+                sprintf(buf, "#%06x", rgb24);
+            }
+            break;
+        }
+    }
+    if (src) {
+        strcpy(buf, src);
+    }
+
+    // assert(sp_svg_read_color(buf, 0xff) == (rgb24 << 8));
+}
+
+/**
+ * Converts an RGBA32 colour to a CSS/SVG representation of the RGB portion of that colour.  The
+ * result is valid even in SVG Tiny or non-SVG CSS.
+ *
+ * \param rgba32 Colour expressed in form 0xrrggbbaa.
+ * \pre buflen \>= 8.
+ */
+void
+sp_svg_write_color(gchar *buf, unsigned const buflen, guint32 const rgba32)
 {
-    return g_snprintf(buf, buflen, "#%06x", color >> 8);
+    g_assert(8 <= buflen);
+
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    unsigned const rgb24 = rgba32 >> 8;
+    if (prefs->getBool("/options/svgoutput/usenamedcolors")) {
+        rgb24_to_css(buf, rgb24);
+    } else {
+        g_snprintf(buf, buflen, "#%06x", rgb24);
+    }
 }
 
 static GHashTable *
@@ -317,7 +455,7 @@ sp_svg_create_color_hash()
 {
     GHashTable *colors = g_hash_table_new(g_str_hash, g_str_equal);
 
-    for (unsigned i = 0 ; i < SP_SVG_NUMCOLORS ; i++) {
+    for (unsigned i = 0 ; i < G_N_ELEMENTS(sp_svg_color_named) ; i++) {
         g_hash_table_insert(colors,
                             (gpointer)(sp_svg_color_named[i].name),
                             (gpointer)(&sp_svg_color_named[i].rgb));
@@ -326,6 +464,253 @@ sp_svg_create_color_hash()
     return colors;
 }
 
+#if ENABLE_LCMS
+//helper function borrowed from src/widgets/sp-color-icc-selector.cpp:
+void getThings( DWORD space, gchar const**& namers, gchar const**& tippies, guint const*& scalies );
+
+void icc_color_to_sRGB(SVGICCColor* icc, guchar* r, guchar* g, guchar* b){
+    guchar color_out[4];
+    guchar color_in[4];
+    if (icc){
+g_message("profile name: %s", icc->colorProfile.c_str());
+        Inkscape::ColorProfile* prof = SP_ACTIVE_DOCUMENT->profileManager->find(icc->colorProfile.c_str());
+        if ( prof ) {
+            cmsHTRANSFORM trans = prof->getTransfToSRGB8();
+            if ( trans ) {
+                gchar const** names = 0;
+                gchar const** tips = 0;
+                guint const* scales = 0;
+                getThings( prof->getColorSpace(), names, tips, scales );
+
+                guint count = _cmsChannelsOf( prof->getColorSpace() );
+                if (count>4) count=4; //do we need it? Should we allow an arbitrary number of color values? Or should we limit to a maximum? (max==4?)
+                for (guint i=0;i<count; i++){
+                    color_in[i] = (guchar) ((((gdouble)icc->colors[i])*256.0) * (gdouble)scales[i]);
+g_message("input[%d]: %d",i, color_in[i]);
+                }
+
+                cmsDoTransform( trans, color_in, color_out, 1 );
+g_message("transform to sRGB done");
+            }
+            *r = color_out[0];
+            *g = color_out[1];
+            *b = color_out[2];
+        }
+    }
+}
+#endif //ENABLE_LCMS
+
+/*
+ * Some discussion at http://markmail.org/message/bhfvdfptt25kgtmj
+ * Allowed ASCII first characters:  ':', 'A'-'Z', '_', 'a'-'z'
+ * Allowed ASCII remaining chars add: '-', '.', '0'-'9',
+ */
+bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor* dest )
+{
+    bool good = true;
+
+    if ( end_ptr ) {
+        *end_ptr = str;
+    }
+    if ( dest ) {
+        dest->colorProfile.clear();
+        dest->colors.clear();
+    }
+
+    if ( !str ) {
+        // invalid input
+        good = false;
+    } else {
+        while ( g_ascii_isspace(*str) ) {
+            str++;
+        }
+
+        good = strneq( str, "icc-color(", 10 );
+
+        if ( good ) {
+            str += 10;
+            while ( g_ascii_isspace(*str) ) {
+                str++;
+            }
+
+            if ( !g_ascii_isalpha(*str)
+                 && ( !(0x080 & *str) )
+                 && (*str != '_')
+                 && (*str != ':') ) {
+                // Name must start with a certain type of character
+                good = false;
+            } else {
+                while ( g_ascii_isdigit(*str) || g_ascii_isalpha(*str)
+                        || (*str == '-') || (*str == ':') || (*str == '_') || (*str == '.') ) {
+                    if ( dest ) {
+                        dest->colorProfile += *str;
+                    }
+                    str++;
+                }
+                while ( g_ascii_isspace(*str) || *str == ',' ) {
+                    str++;
+                }
+            }
+        }
+
+        if ( good ) {
+            while ( *str && *str != ')' ) {
+                if ( g_ascii_isdigit(*str) || *str == '.' || *str == '-' || *str == '+') {
+                    gchar* endPtr = 0;
+                    gdouble dbl = g_ascii_strtod( str, &endPtr );
+                    if ( !errno ) {
+                        if ( dest ) {
+                            dest->colors.push_back( dbl );
+                        }
+                        str = endPtr;
+                    } else {
+                        good = false;
+                        break;
+                    }
+
+                    while ( g_ascii_isspace(*str) || *str == ',' ) {
+                        str++;
+                    }
+                } else {
+                    break;
+                }
+            }
+        }
+
+        // We need to have ended on a closing parenthesis
+        if ( good ) {
+            while ( g_ascii_isspace(*str) ) {
+                str++;
+            }
+            good &= (*str == ')');
+        }
+    }
+
+    if ( good ) {
+        if ( end_ptr ) {
+            *end_ptr = str;
+        }
+    } else {
+        if ( dest ) {
+            dest->colorProfile.clear();
+            dest->colors.clear();
+        }
+    }
+
+    return good;
+}
+
+
+bool sp_svg_read_icc_color( gchar const *str, SVGICCColor* dest )
+{
+    return sp_svg_read_icc_color(str, NULL, dest);
+}
+
+bool sp_svg_read_device_color( gchar const *str, gchar const **end_ptr, SVGDeviceColor* dest)
+{
+    bool good = true;
+    unsigned int max_colors;
+
+    if ( end_ptr ) {
+        *end_ptr = str;
+    }
+    if ( dest ) {
+        dest->colors.clear();
+    }
+
+    if ( !str ) {
+        // invalid input
+        good = false;
+    } else {
+        while ( g_ascii_isspace(*str) ) {
+            str++;
+        }
+
+        dest->type = DEVICE_COLOR_INVALID;
+        if (strneq( str, "device-gray(", 12 )){
+                dest->type = DEVICE_GRAY;
+                max_colors=1;
+                str += 12;
+        }
+
+        if (strneq( str, "device-rgb(", 11 )){
+                dest->type = DEVICE_RGB;
+                max_colors=3;
+                str += 11;
+        }
+
+        if (strneq( str, "device-cmyk(", 12 )){
+                dest->type = DEVICE_CMYK;
+                max_colors=4;
+                str += 12;
+        }
+
+        if (strneq( str, "device-nchannel(", 16 )){
+                dest->type = DEVICE_NCHANNEL;
+                max_colors=0;
+                str += 16;
+        }
+
+        if ( dest->type != DEVICE_COLOR_INVALID ) {
+            while ( g_ascii_isspace(*str) ) {
+                str++;
+            }
+
+            while ( *str && *str != ')' ) {
+                if ( g_ascii_isdigit(*str) || *str == '.' || *str == '-' || *str == '+') {
+                    gchar* endPtr = 0;
+                    gdouble dbl = g_ascii_strtod( str, &endPtr );
+                    if ( !errno ) {
+                        if ( dest ) {
+                            dest->colors.push_back( dbl );
+                        }
+                        str = endPtr;
+                    } else {
+                        good = false;
+                        break;
+                    }
+
+                    while ( g_ascii_isspace(*str) || *str == ',' ) {
+                        str++;
+                    }
+                } else {
+                    break;
+                }
+            }
+        }
+
+        // We need to have ended on a closing parenthesis
+        if ( good ) {
+            while ( g_ascii_isspace(*str) ) {
+                str++;
+            }
+            good &= (*str == ')');
+        }
+    }
+
+    if ( dest->colors.size() == 0) good=false;
+    if ( dest->type != DEVICE_NCHANNEL && (dest->colors.size() != max_colors)) good=false;
+
+    if ( good ) {
+        if ( end_ptr ) {
+            *end_ptr = str;
+        }
+    } else {
+        if ( dest ) {
+            dest->type = DEVICE_COLOR_INVALID;
+            dest->colors.clear();
+        }
+    }
+
+    return good;
+}
+
+
+bool sp_svg_read_device_color( gchar const *str, SVGDeviceColor* dest)
+{
+    return sp_svg_read_device_color(str, NULL, dest);
+}
+
 /*
   Local Variables:
   mode:c++