/* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** 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. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code used to select colors based on branch and ** user names. ** */ #include "config.h" #include #include "color.h" /* ** 140 standard CSS color names and their corresponding RGB values, ** in alphabetical order by name so that we can do a binary search ** for lookup. */ static const struct CssColors { const char *zName; /* CSS Color name, lower case */ unsigned int iRGB; /* Corresponding RGB value */ } aCssColors[] = { { "aliceblue", 0xf0f8ff }, { "antiquewhite", 0xfaebd7 }, { "aqua", 0x00ffff }, { "aquamarine", 0x7fffd4 }, { "azure", 0xf0ffff }, { "beige", 0xf5f5dc }, { "bisque", 0xffe4c4 }, { "black", 0x000000 }, { "blanchedalmond", 0xffebcd }, { "blue", 0x0000ff }, { "blueviolet", 0x8a2be2 }, { "brown", 0xa52a2a }, { "burlywood", 0xdeb887 }, { "cadetblue", 0x5f9ea0 }, { "chartreuse", 0x7fff00 }, { "chocolate", 0xd2691e }, { "coral", 0xff7f50 }, { "cornflowerblue", 0x6495ed }, { "cornsilk", 0xfff8dc }, { "crimson", 0xdc143c }, { "cyan", 0x00ffff }, { "darkblue", 0x00008b }, { "darkcyan", 0x008b8b }, { "darkgoldenrod", 0xb8860b }, { "darkgray", 0xa9a9a9 }, { "darkgreen", 0x006400 }, { "darkkhaki", 0xbdb76b }, { "darkmagenta", 0x8b008b }, { "darkolivegreen", 0x556b2f }, { "darkorange", 0xff8c00 }, { "darkorchid", 0x9932cc }, { "darkred", 0x8b0000 }, { "darksalmon", 0xe9967a }, { "darkseagreen", 0x8fbc8f }, { "darkslateblue", 0x483d8b }, { "darkslategray", 0x2f4f4f }, { "darkturquoise", 0x00ced1 }, { "darkviolet", 0x9400d3 }, { "deeppink", 0xff1493 }, { "deepskyblue", 0x00bfff }, { "dimgray", 0x696969 }, { "dodgerblue", 0x1e90ff }, { "firebrick", 0xb22222 }, { "floralwhite", 0xfffaf0 }, { "forestgreen", 0x228b22 }, { "fuchsia", 0xff00ff }, { "gainsboro", 0xdcdcdc }, { "ghostwhite", 0xf8f8ff }, { "gold", 0xffd700 }, { "goldenrod", 0xdaa520 }, { "gray", 0x808080 }, { "green", 0x008000 }, { "greenyellow", 0xadff2f }, { "honeydew", 0xf0fff0 }, { "hotpink", 0xff69b4 }, { "indianred", 0xcd5c5c }, { "indigo", 0x4b0082 }, { "ivory", 0xfffff0 }, { "khaki", 0xf0e68c }, { "lavender", 0xe6e6fa }, { "lavenderblush", 0xfff0f5 }, { "lawngreen", 0x7cfc00 }, { "lemonchiffon", 0xfffacd }, { "lightblue", 0xadd8e6 }, { "lightcoral", 0xf08080 }, { "lightcyan", 0xe0ffff }, { "lightgoldenrodyellow", 0xfafad2 }, { "lightgrey", 0xd3d3d3 }, { "lightgreen", 0x90ee90 }, { "lightpink", 0xffb6c1 }, { "lightsalmon", 0xffa07a }, { "lightseagreen", 0x20b2aa }, { "lightskyblue", 0x87cefa }, { "lightslategray", 0x778899 }, { "lightsteelblue", 0xb0c4de }, { "lightyellow", 0xffffe0 }, { "lime", 0x00ff00 }, { "limegreen", 0x32cd32 }, { "linen", 0xfaf0e6 }, { "magenta", 0xff00ff }, { "maroon", 0x800000 }, { "mediumaquamarine", 0x66cdaa }, { "mediumblue", 0x0000cd }, { "mediumorchid", 0xba55d3 }, { "mediumpurple", 0x9370d8 }, { "mediumseagreen", 0x3cb371 }, { "mediumslateblue", 0x7b68ee }, { "mediumspringgreen", 0x00fa9a }, { "mediumturquoise", 0x48d1cc }, { "mediumvioletred", 0xc71585 }, { "midnightblue", 0x191970 }, { "mintcream", 0xf5fffa }, { "mistyrose", 0xffe4e1 }, { "moccasin", 0xffe4b5 }, { "navajowhite", 0xffdead }, { "navy", 0x000080 }, { "oldlace", 0xfdf5e6 }, { "olive", 0x808000 }, { "olivedrab", 0x6b8e23 }, { "orange", 0xffa500 }, { "orangered", 0xff4500 }, { "orchid", 0xda70d6 }, { "palegoldenrod", 0xeee8aa }, { "palegreen", 0x98fb98 }, { "paleturquoise", 0xafeeee }, { "palevioletred", 0xd87093 }, { "papayawhip", 0xffefd5 }, { "peachpuff", 0xffdab9 }, { "peru", 0xcd853f }, { "pink", 0xffc0cb }, { "plum", 0xdda0dd }, { "powderblue", 0xb0e0e6 }, { "purple", 0x800080 }, { "red", 0xff0000 }, { "rosybrown", 0xbc8f8f }, { "royalblue", 0x4169e1 }, { "saddlebrown", 0x8b4513 }, { "salmon", 0xfa8072 }, { "sandybrown", 0xf4a460 }, { "seagreen", 0x2e8b57 }, { "seashell", 0xfff5ee }, { "sienna", 0xa0522d }, { "silver", 0xc0c0c0 }, { "skyblue", 0x87ceeb }, { "slateblue", 0x6a5acd }, { "slategray", 0x708090 }, { "snow", 0xfffafa }, { "springgreen", 0x00ff7f }, { "steelblue", 0x4682b4 }, { "tan", 0xd2b48c }, { "teal", 0x008080 }, { "thistle", 0xd8bfd8 }, { "tomato", 0xff6347 }, { "turquoise", 0x40e0d0 }, { "violet", 0xee82ee }, { "wheat", 0xf5deb3 }, { "white", 0xffffff }, { "whitesmoke", 0xf5f5f5 }, { "yellow", 0xffff00 }, { "yellowgreen", 0x9acd32 }, }; /* ** Attempt to translate a CSS color name into an integer that ** represents the equivalent RGB value. Ignore alpha if provided. ** If the name cannot be translated, return -1. */ int color_name_to_rgb(const char *zName){ if( zName==0 || zName[0]==0 ) return -1; if( zName[0]=='#' ){ int i, v = 0; for(i=1; i<=6 && fossil_isxdigit(zName[i]); i++){ v = v*16 + fossil_hexvalue(zName[i]); } if( i==4 ){ v = fossil_hexvalue(zName[1])*0x110000 + fossil_hexvalue(zName[2])*0x1100 + fossil_hexvalue(zName[3])*0x11; return v; } if( i==7 ){ return v; } return -1; }else{ int iMin = 0; int iMax = count(aCssColors)-1; while( iMin<=iMax ){ int iMid = (iMin+iMax)/2; int c = sqlite3_stricmp(aCssColors[iMid].zName, zName); if( c==0 ) return aCssColors[iMid].iRGB; if( c<0 ){ iMin = iMid+1; }else{ iMax = iMid-1; } } return -1; } } /* ** SETTING: raw-bgcolor boolean default=off ** ** Fossil usually tries to adjust user-specified background colors ** for check-ins so that the text is readable and so that the color ** is not too garish. This setting disables that filter. When ** this setting is on, the user-selected background colors are shown ** exactly as requested. */ /* ** Shift a color provided by the user so that it is suitable ** for use as a background color in the current skin. ** ** The return value is a #HHHHHH color name contained in ** static space that is overwritten on the next call. ** ** If we cannot make sense of the background color recommendation ** that is the input, then return NULL. ** ** The iFgClr parameter is normally 0. But for testing purposes, set ** it to 1 for a black foregrounds and 2 for a white foreground. */ const char *reasonable_bg_color(const char *zRequested, int iFgClr){ int iRGB = color_name_to_rgb(zRequested); int r, g, b; /* RGB components of requested color */ static int systemFg = 0; /* 1==black-foreground 2==white-foreground */ int fg; /* Foreground color to actually use */ static char zColor[10]; /* Return value */ if( iFgClr ){ fg = iFgClr; }else if( systemFg==0 ){ if( db_get_boolean("raw-bgcolor",0) ){ fg = systemFg = 3; }else{ fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1; } }else{ fg = systemFg; } if( fg>=3 ) return zRequested; if( iRGB<0 ) return 0; r = (iRGB>>16) & 0xff; g = (iRGB>>8) & 0xff; b = iRGB & 0xff; if( fg==1 ){ /* Dark text on a light background. Adjust so that ** no color component is less than 255-K, resulting in ** a pastel background color. Color adjustment is quadratic ** so that colors that are further out of range have a greater ** adjustment. */ const int K = 79; int k, x, m; m = rb ) m = b; k = (m*m)/255 + K; x = 255 - k; r = (k*r)/255 + x; g = (k*g)/255 + x; b = (k*b)/255 + x; }else{ /* Light text on a dark background. Adjust so that ** no color component is greater than K, resulting in ** a low-intensity, low-saturation background color. ** The color adjustment is quadratic so that colors that ** are further out of range have a greater adjustment. */ const int K = 112; int k, m; m = r>g ? r : g; if( m>3) ^ z[i]; } return h; } /* ** Hash a string and use the hash to determine a background color. ** ** This value returned is in static space and is overwritten with ** each subsequent call. */ char *hash_color(const char *z){ unsigned int h = 0; /* Hash on the branch name */ int r, g, b; /* Values for red, green, and blue */ int h1, h2, h3, h4; /* Elements of the hash value */ int mx, mn; /* Components of HSV */ static char zColor[10]; /* The resulting color */ static int ix[3] = {0,0}; /* Color chooser parameters */ if( ix[0]==0 ){ if( skin_detail_boolean("white-foreground") ){ ix[0] = 0x50; ix[1] = 0x20; }else{ ix[0] = 0xf8; ix[1] = 0x20; } } h = hash_of_name(z); h1 = h % 6; h /= 6; h3 = h % 10; h /= 10; h4 = h % 10; h /= 10; mx = ix[0] - h3; mn = mx - h4 - ix[1]; h2 = (h%(mx - mn)) + mn; switch( h1 ){ case 0: r = mx; g = h2, b = mn; break; case 1: r = h2; g = mx, b = mn; break; case 2: r = mn; g = mx, b = h2; break; case 3: r = mn; g = h2, b = mx; break; case 4: r = h2; g = mn, b = mx; break; default: r = mx; g = mn, b = h2; break; } sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b); return zColor; } /* ** Determine a color for users based on their login string. ** ** SETTING: user-color-map width=40 block-text ** ** The user-color-map setting can be used to override user color choices. ** The setting is a list of space-separated words pairs. The first word ** of each pair is a login name. The second word is an alternative name ** used by the color chooser algorithm. ** ** This list is intended to be relatively short. The idea is to only use ** this map to resolve color collisions between common users. ** ** Visit /hash-color-test?rand for a list of suggested names for the ** second word of each pair in the list. */ char *user_color(const char *zLogin){ static int once = 0; static int nMap = 0; static char **azMap = 0; static int *anMap = 0; int i; if( !once ){ char *zMap = (char*)db_get("user-color-map",0); once = 1; if( zMap && zMap[0] ){ if( !g.interp ) Th_FossilInit(0); Th_SplitList(g.interp, zMap, (int)strlen(zMap), &azMap, &anMap, &nMap); for(i=0; i @ %h(zBr) - hash 0x%x(hash_of_name(zBr)) - color %s(hash_color(zBr)) - @ Omnes nos quasi oves erravimus unusquisque in viam @ suam declinavit.

cnt++; } } if( cnt ){ @
} @
@

Enter candidate branch names below and see them displayed in their @ default background colors above.

for(i=0; i<10; i++){ sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); zBr = P(zNm); @
} @ @ @
style_finish_page(); } /* ** WEBPAGE: test-bgcolor ** ** Show how user-specified background colors will be rendered ** using the reasonable_bg_color() algorithm. */ void test_bgcolor_page(void){ const char *zReq; /* Requested color name */ const char *zBG; /* Actual color provided */ const char *zBg1; char zNm[10]; static const char *azDflt[] = { "red", "orange", "yellow", "green", "blue", "indigo", "violet", "tan", "brown", "gray", }; const int N = count(azDflt); int i, cnt, iClr, r, g, b; char *zFg; login_check_credentials(); style_set_current_feature("test"); style_header("Background Color Test"); for(i=cnt=0; i @ @ Requested Background @ Light mode @ Dark mode @ } cnt++; zBG = reasonable_bg_color(zReq, 0); if( zBG==0 ){ @ \ @ "%h(zReq)" is not a recognized color name continue; } iClr = color_name_to_rgb(zReq); r = (iClr>>16) & 0xff; g = (iClr>>8) & 0xff; b = iClr & 0xff; if( 3*r + 7*g + b > 6*255 ){ zFg = "black"; }else{ zFg = "white"; } if( zReq[0]!='#' ){ char zReqRGB[12]; sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq)); @ \ @ Requested color "%h(zReq)" (%h(zReqRGB)) }else{ @ \ @ Requested color "%h(zReq)" } zBg1 = reasonable_bg_color(zReq,1); @ \ @ Background color for dark text: %h(zBg1) zBg1 = reasonable_bg_color(zReq,2); @ \ @ Background color for light text: %h(zBg1) } if( cnt ){ @ @
} @
@

Enter CSS color names below and see them shifted into corresponding @ background colors above.

for(i=0; i
} @ @ style_finish_page(); }