diff options
Diffstat (limited to '')
-rw-r--r-- | src/svg/svg-color.cpp | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/src/svg/svg-color.cpp b/src/svg/svg-color.cpp new file mode 100644 index 0000000..998d955 --- /dev/null +++ b/src/svg/svg-color.cpp @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * \file + * Reading \& writing of SVG/CSS colors. + */ +/* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#include <cstdlib> +#include <cstdio> // sprintf +#include <cstring> +#include <string> +#include <cmath> +#include <glib.h> // g_assert +#include <cerrno> + +#include <map> + +#include "colorspace.h" +#include "strneq.h" +#include "preferences.h" +#include "svg-color.h" +#include "svg-icc-color.h" + +#include "color.h" + +#include <vector> +#include "object/color-profile.h" + +#include "document.h" +#include "inkscape.h" +#include "profile-manager.h" + +#include "cms-system.h" + +struct SPSVGColor { + unsigned long rgb; + const std::string name; +}; + +/* + * These are the colors defined in the SVG standard + */ +static SPSVGColor const sp_svg_color_named[] = { + { 0xF0F8FF, "aliceblue" }, + { 0xFAEBD7, "antiquewhite" }, + { 0x00FFFF, "aqua" }, + { 0x7FFFD4, "aquamarine" }, + { 0xF0FFFF, "azure" }, + { 0xF5F5DC, "beige" }, + { 0xFFE4C4, "bisque" }, + { 0x000000, "black" }, + { 0xFFEBCD, "blanchedalmond" }, + { 0x0000FF, "blue" }, + { 0x8A2BE2, "blueviolet" }, + { 0xA52A2A, "brown" }, + { 0xDEB887, "burlywood" }, + { 0x5F9EA0, "cadetblue" }, + { 0x7FFF00, "chartreuse" }, + { 0xD2691E, "chocolate" }, + { 0xFF7F50, "coral" }, + { 0x6495ED, "cornflowerblue" }, + { 0xFFF8DC, "cornsilk" }, + { 0xDC143C, "crimson" }, + { 0x00FFFF, "cyan" }, + { 0x00008B, "darkblue" }, + { 0x008B8B, "darkcyan" }, + { 0xB8860B, "darkgoldenrod" }, + { 0xA9A9A9, "darkgray" }, + { 0x006400, "darkgreen" }, + { 0xA9A9A9, "darkgrey" }, + { 0xBDB76B, "darkkhaki" }, + { 0x8B008B, "darkmagenta" }, + { 0x556B2F, "darkolivegreen" }, + { 0xFF8C00, "darkorange" }, + { 0x9932CC, "darkorchid" }, + { 0x8B0000, "darkred" }, + { 0xE9967A, "darksalmon" }, + { 0x8FBC8F, "darkseagreen" }, + { 0x483D8B, "darkslateblue" }, + { 0x2F4F4F, "darkslategray" }, + { 0x2F4F4F, "darkslategrey" }, + { 0x00CED1, "darkturquoise" }, + { 0x9400D3, "darkviolet" }, + { 0xFF1493, "deeppink" }, + { 0x00BFFF, "deepskyblue" }, + { 0x696969, "dimgray" }, + { 0x696969, "dimgrey" }, + { 0x1E90FF, "dodgerblue" }, + { 0xB22222, "firebrick" }, + { 0xFFFAF0, "floralwhite" }, + { 0x228B22, "forestgreen" }, + { 0xFF00FF, "fuchsia" }, + { 0xDCDCDC, "gainsboro" }, + { 0xF8F8FF, "ghostwhite" }, + { 0xFFD700, "gold" }, + { 0xDAA520, "goldenrod" }, + { 0x808080, "gray" }, + { 0x808080, "grey" }, + { 0x008000, "green" }, + { 0xADFF2F, "greenyellow" }, + { 0xF0FFF0, "honeydew" }, + { 0xFF69B4, "hotpink" }, + { 0xCD5C5C, "indianred" }, + { 0x4B0082, "indigo" }, + { 0xFFFFF0, "ivory" }, + { 0xF0E68C, "khaki" }, + { 0xE6E6FA, "lavender" }, + { 0xFFF0F5, "lavenderblush" }, + { 0x7CFC00, "lawngreen" }, + { 0xFFFACD, "lemonchiffon" }, + { 0xADD8E6, "lightblue" }, + { 0xF08080, "lightcoral" }, + { 0xE0FFFF, "lightcyan" }, + { 0xFAFAD2, "lightgoldenrodyellow" }, + { 0xD3D3D3, "lightgray" }, + { 0x90EE90, "lightgreen" }, + { 0xD3D3D3, "lightgrey" }, + { 0xFFB6C1, "lightpink" }, + { 0xFFA07A, "lightsalmon" }, + { 0x20B2AA, "lightseagreen" }, + { 0x87CEFA, "lightskyblue" }, + { 0x778899, "lightslategray" }, + { 0x778899, "lightslategrey" }, + { 0xB0C4DE, "lightsteelblue" }, + { 0xFFFFE0, "lightyellow" }, + { 0x00FF00, "lime" }, + { 0x32CD32, "limegreen" }, + { 0xFAF0E6, "linen" }, + { 0xFF00FF, "magenta" }, + { 0x800000, "maroon" }, + { 0x66CDAA, "mediumaquamarine" }, + { 0x0000CD, "mediumblue" }, + { 0xBA55D3, "mediumorchid" }, + { 0x9370DB, "mediumpurple" }, + { 0x3CB371, "mediumseagreen" }, + { 0x7B68EE, "mediumslateblue" }, + { 0x00FA9A, "mediumspringgreen" }, + { 0x48D1CC, "mediumturquoise" }, + { 0xC71585, "mediumvioletred" }, + { 0x191970, "midnightblue" }, + { 0xF5FFFA, "mintcream" }, + { 0xFFE4E1, "mistyrose" }, + { 0xFFE4B5, "moccasin" }, + { 0xFFDEAD, "navajowhite" }, + { 0x000080, "navy" }, + { 0xFDF5E6, "oldlace" }, + { 0x808000, "olive" }, + { 0x6B8E23, "olivedrab" }, + { 0xFFA500, "orange" }, + { 0xFF4500, "orangered" }, + { 0xDA70D6, "orchid" }, + { 0xEEE8AA, "palegoldenrod" }, + { 0x98FB98, "palegreen" }, + { 0xAFEEEE, "paleturquoise" }, + { 0xDB7093, "palevioletred" }, + { 0xFFEFD5, "papayawhip" }, + { 0xFFDAB9, "peachpuff" }, + { 0xCD853F, "peru" }, + { 0xFFC0CB, "pink" }, + { 0xDDA0DD, "plum" }, + { 0xB0E0E6, "powderblue" }, + { 0x800080, "purple" }, + { 0x663399, "rebeccapurple" }, + { 0xFF0000, "red" }, + { 0xBC8F8F, "rosybrown" }, + { 0x4169E1, "royalblue" }, + { 0x8B4513, "saddlebrown" }, + { 0xFA8072, "salmon" }, + { 0xF4A460, "sandybrown" }, + { 0x2E8B57, "seagreen" }, + { 0xFFF5EE, "seashell" }, + { 0xA0522D, "sienna" }, + { 0xC0C0C0, "silver" }, + { 0x87CEEB, "skyblue" }, + { 0x6A5ACD, "slateblue" }, + { 0x708090, "slategray" }, + { 0x708090, "slategrey" }, + { 0xFFFAFA, "snow" }, + { 0x00FF7F, "springgreen" }, + { 0x4682B4, "steelblue" }, + { 0xD2B48C, "tan" }, + { 0x008080, "teal" }, + { 0xD8BFD8, "thistle" }, + { 0xFF6347, "tomato" }, + { 0x40E0D0, "turquoise" }, + { 0xEE82EE, "violet" }, + { 0xF5DEB3, "wheat" }, + { 0xFFFFFF, "white" }, + { 0xF5F5F5, "whitesmoke" }, + { 0xFFFF00, "yellow" }, + { 0x9ACD32, "yellowgreen" } +}; + +static std::map<std::string, unsigned long> sp_svg_create_color_hash(); + +guint32 sp_svg_read_color(gchar const *str, guint32 const dfl) +{ + return sp_svg_read_color(str, nullptr, dfl); +} + +static guint32 internal_sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 def) +{ + static std::map<std::string, unsigned long> colors; + guint32 val = 0; + + if (str == nullptr) return def; + while ((*str <= ' ') && *str) str++; + if (!*str) return def; + + if (str[0] == '#') { + gint i; + for (i = 1; str[i]; i++) { + int hexval; + if (str[i] >= '0' && str[i] <= '9') + hexval = str[i] - '0'; + else if (str[i] >= 'A' && str[i] <= 'F') + hexval = str[i] - 'A' + 10; + else if (str[i] >= 'a' && str[i] <= 'f') + hexval = str[i] - 'a' + 10; + else + break; + val = (val << 4) + hexval; + } + /* handle #rgb case */ + if (i == 1 + 3) { + val = ((val & 0xf00) << 8) | + ((val & 0x0f0) << 4) | + (val & 0x00f); + val |= val << 4; + } else if (i != 1 + 6) { + /* must be either 3 or 6 digits. */ + return def; + } + 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; + + r = g_ascii_strtod(s, &e); + if (s == e) return def; + s = e; + if (*s == '%') { + hasp = true; + s += 1; + } else { + hasd = true; + } + while (*s && g_ascii_isspace(*s)) s += 1; + if (*s != ',') return def; + s += 1; + while (*s && g_ascii_isspace(*s)) s += 1; + g = g_ascii_strtod(s, &e); + if (s == e) return def; + s = e; + if (*s == '%') { + hasp = true; + s += 1; + } else { + hasd = true; + } + while (*s && g_ascii_isspace(*s)) s += 1; + if (*s != ',') return def; + s += 1; + while (*s && g_ascii_isspace(*s)) s += 1; + b = g_ascii_strtod(s, &e); + if (s == e) return def; + s = e; + if (*s == '%') { + hasp = true; + s += 1; + } else { + hasd = true; + } + while(*s && g_ascii_isspace(*s)) s += 1; + if (*s != ')') { + return def; + } + ++s; + if (hasp && hasd) return def; + if (hasp) { + val = static_cast<guint>(floor(CLAMP(r, 0.0, 100.0) * 2.559999)) << 24; + val |= (static_cast<guint>(floor(CLAMP(g, 0.0, 100.0) * 2.559999)) << 16); + val |= (static_cast<guint>(floor(CLAMP(b, 0.0, 100.0) * 2.559999)) << 8); + } else { + val = static_cast<guint>(CLAMP(r, 0, 255)) << 24; + val |= (static_cast<guint>(CLAMP(g, 0, 255)) << 16); + val |= (static_cast<guint>(CLAMP(b, 0, 255)) << 8); + } + if (end_ptr) { + *end_ptr = s; + } + return val; + } else if (strneq(str, "hsl(", 4)) { + + gchar *ptr = (gchar *) str + 4; + + gchar *e; // ptr after read + + double h = g_ascii_strtod(ptr, &e); // Read h (0-360) + if (ptr == e) return def; // Read failed + ptr = e; + + while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space + if (*ptr != ',') return def; // Need comma + ptr += 1; + while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space + + double s = g_ascii_strtod(ptr, &e); // Read s (percent) + if (ptr == e) return def; // Read failed + ptr = e; + while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space + if (*ptr != '%') return def; // Need % + ptr += 1; + + while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space + if (*ptr != ',') return def; // Need comma + ptr += 1; + while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space + + double l = g_ascii_strtod(ptr, &e); // Read l (percent) + if (ptr == e) return def; // Read failed + ptr = e; + while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space + if (*ptr != '%') return def; // Need % + ptr += 1; + + if (end_ptr) { + *end_ptr = ptr; + } + + + // Normalize to 0..1 + h /= 360.0; + s /= 100.0; + l /= 100.0; + + gfloat rgb[3]; + + SPColor::hsl_to_rgb_floatv( rgb, h, s, l ); + + val = static_cast<guint>(floor(CLAMP(rgb[0], 0.0, 1.0) * 255.9999)) << 24; + val |= (static_cast<guint>(floor(CLAMP(rgb[1], 0.0, 1.0) * 255.9999)) << 16); + val |= (static_cast<guint>(floor(CLAMP(rgb[2], 0.0, 1.0) * 255.9999)) << 8); + return val; + + } else { + gint i; + if (colors.empty()) { + colors = sp_svg_create_color_hash(); + } + gchar c[32]; + for (i = 0; i < 31; i++) { + if (str[i] == ';' || g_ascii_isspace(str[i])) { + c[i] = '\0'; + break; + } + c[i] = g_ascii_tolower(str[i]); + if (!str[i]) break; + } + c[31] = '\0'; + + if (colors.count(std::string(c))) { + val = colors[std::string(c)]; + } + else { + return def; + } + if (end_ptr) { + *end_ptr = str + i; + } + } + + return (val << 8); +} + +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); + g_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); + g_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) +{ + g_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 = nullptr; + 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. */ + std::sprintf(buf, "#%x%x%x", + (rgb24 >> 16) & 0xf, + (rgb24 >> 8) & 0xf, + rgb24 & 0xf); + } else { + std::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) +{ + g_assert(8 <= buflen); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + unsigned const rgb24 = rgba32 >> 8; + if ( prefs->getBool("/options/svgoutput/usenamedcolors") && + !prefs->getBool("/options/svgoutput/disable_optimizations" )) { + rgb24_to_css(buf, rgb24); + } else { + g_snprintf(buf, buflen, "#%06x", rgb24); + } +} + +static std::map<std::string, unsigned long> +sp_svg_create_color_hash() +{ + std::map<std::string, unsigned long> colors; + + for (const auto & i : sp_svg_color_named) { + colors[i.name] = i.rgb; + } + return colors; +} + + +void icc_color_to_sRGB(SVGICCColor* icc, guchar* r, guchar* g, guchar* b) +{ + if (icc) { + g_message("profile name: %s", icc->colorProfile.c_str()); + Inkscape::ColorProfile* prof = SP_ACTIVE_DOCUMENT->getProfileManager()->find(icc->colorProfile.c_str()); + if ( prof ) { + guchar color_out[4] = {0,0,0,0}; + cmsHTRANSFORM trans = prof->getTransfToSRGB8(); + if ( trans ) { + std::vector<colorspace::Component> comps = colorspace::getColorSpaceInfo( prof ); + + size_t count = Inkscape::CMSSystem::getChannelCount( prof ); + size_t cap = std::min(count, comps.size()); + guchar color_in[4]; + for (size_t i = 0; i < cap; i++) { + color_in[i] = static_cast<guchar>((((gdouble)icc->colors[i]) * 256.0) * (gdouble)comps[i].scale); + g_message("input[%d]: %d", (int)i, (int)color_in[i]); + } + + Inkscape::CMSSystem::doTransform( trans, color_in, color_out, 1 ); +g_message("transform to sRGB done"); + } + *r = color_out[0]; + *g = color_out[1]; + *b = color_out[2]; + } + } +} + +/* + * 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 = nullptr; + 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, nullptr, dest); +} + +/** + * Reading inkscape colors, for things like namedviews, guides, etc. + * Non-CSS / SVG specification formatted. Usually just a number. + */ +bool sp_ink_read_opacity(char const *str, guint32 *color, guint32 default_color) +{ + *color = (*color & 0xffffff00) | (default_color & 0xff); + if (!str) return false; + + gchar *check; + gdouble value = g_ascii_strtod(str, &check); + if (!check) return false; + + value = CLAMP(value, 0.0, 1.0); + *color = (*color & 0xffffff00) | (guint32) floor(value * 255.9999); + return true; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |