Fossil

Artifact [8e2493937a]
Login

Artifact [8e2493937a]

Artifact 8e2493937a1f259edfe46274df59f7e6574d164897fd7a278b3712a182bcb416:


/*
** 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 <string.h>
#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 ){
      for(v=0, i=1; i<4; i++) v = v*256 + fossil_hexvalue(zName[i]);
      return v;
    }
    if( i==7 ){
      return v;
    }
    return -1;
  }else if( sqlite3_strlike("rgb%)", zName,0)==0 ){
    return -1;
  }else if( sqlite3_strlike("hsl%)",zName,0)==0 ){
    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;
  }
}

/*
** 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.
*/
char *reasonable_bg_color(const char *zRequested, int iFgClr){
  int iRGB = color_name_to_rgb(zRequested);
  int cc[3];
  int lo, hi;
  int r, g, b;
  static int systemFg = 0;   /* 1==black-foreground 2==white-foreground */
  int fg;
  static char zColor[10];
  int K = 70;                /* Tune for background color saturation */

  if( iFgClr ){
    fg = iFgClr;
  }else if( systemFg==0 ){
    fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1;
  }else{
    fg = systemFg;
  }

  if( iRGB<0 ) return 0;
  if( fg==0 ) fg = skin_detail_boolean("white-foreground") ? 2 : 1;
  cc[0] = (iRGB>>16) & 0xff;
  cc[1] = (iRGB>>8) & 0xff;
  cc[2] = iRGB & 0xff;
  lo = cc[0]<cc[1] ? 0 : 1;
  if( cc[2]<cc[lo] ) lo = 2;
  hi = cc[0]>cc[1] ? 0 : 1;
  if( cc[2]>cc[hi] ) hi = 2;
  if( cc[lo]==cc[hi] ){
    /* Requested color is some shade of gray */
    r = (K*cc[0])/255;
    if( fg==1 ) r += (255-K);
    g = b = r;
  }else{
    int d = cc[hi] - cc[lo];
    r = (K*(cc[0] - cc[lo]))/d;
    g = (K*(cc[1] - cc[lo]))/d;
    b = (K*(cc[2] - cc[lo]))/d;
    if( fg==1 ){
      r += (255-K);
      g += (255-K);
      b += (255-K);
    }
  }
  sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b);
  return zColor;
}

/*
** Compute a hash on a branch or user name
*/
static unsigned int hash_of_name(const char *z){
  unsigned int h = 0;
  int i;
  for(i=0; z[i]; i++ ){
    h = (h<<11) ^ (h<<1) ^ (h>>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<nMap; i++) azMap[i][anMap[i]] = 0;
    }
  }
  for(i=0; i<nMap-1; i+=2){
    if( strcmp(zLogin, azMap[i])==0 ) return hash_color(azMap[i+1]);
  }
  return hash_color(zLogin);
}

/*
** COMMAND: test-hash-color
**
** Usage: %fossil test-hash-color TAG ...
**
** Print out the color names associated with each tag.  Used for
** testing the hash_color() function.
*/
void test_hash_color(void){
  int i;
  for(i=2; i<g.argc; i++){
    fossil_print("%20s: %s\n", g.argv[i], hash_color(g.argv[i]));
  }
}

/*
** WEBPAGE: hash-color-test
**
** Print out the color names associated with each tag.  Used for
** testing the hash_color() function.
*/
void test_hash_color_page(void){
  const char *zBr;
  char zNm[10];
  int i, cnt;
  login_check_credentials();
  if( P("rand")!=0 ){
    int j;
    for(i=0; i<10; i++){
      sqlite3_uint64 u;
      char zClr[10];
      sqlite3_randomness(sizeof(u), &u);
      cnt = 3+(u%2);
      u /= 2;
      for(j=0; j<cnt; j++){
         zClr[j] = 'a' + (u%26);
         u /= 26;
       }
      zClr[j] = 0;
      sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
      cgi_replace_parameter(fossil_strdup(zNm), fossil_strdup(zClr));
    }
  }
  style_set_current_feature("test");
  style_header("Hash Color Test");
  for(i=cnt=0; i<10; i++){
    sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
    zBr = P(zNm);
    if( zBr && zBr[0] ){
      @ <p style='border:1px solid;background-color:%s(hash_color(zBr));'>
      @ %h(zBr) - hash 0x%x(hash_of_name(zBr)) - color %s(hash_color(zBr)) -
      @ Omnes nos quasi oves erravimus unusquisque in viam
      @ suam declinavit.</p>
      cnt++;
    }
  }
  if( cnt ){
    @ <hr>
  }
  @ <form method="POST">
  @ <p>Enter candidate branch names below and see them displayed in their
  @ default background colors above.</p>
  for(i=0; i<10; i++){
    sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
    zBr = P(zNm);
    @ <input type="text" size="30" name='%s(zNm)' value='%h(PD(zNm,""))'><br>
  }
  @ <input type="submit" value="Submit">
  @ <input type="submit" name="rand" value="Random">
  @ </form>
  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"
  };
  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<10; i++){
    sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
    zReq = PD(zNm,azDflt[i]);
    if( zReq==0 || zReq[0]==0 ) continue;
    if( cnt==0 ){
      @ <table border="1" cellspacing="0" cellpadding="10">
      @ <tr>
      @ <th>Requested Background
      @ <th>Light mode
      @ <th>Dark mode
      @ </tr>
    }
    cnt++;
    zBG = reasonable_bg_color(zReq, 0);
    if( zBG==0 ){
      @ <tr><td colspan="3" align="center">\
      @ "%h(zReq)" is not a recognized color name</td></tr>
      continue;
    }
    iClr = color_name_to_rgb(zReq);
    r = (iClr>>16) & 0xff;
    g = (iClr>>8) & 0xff;
    b = iClr & 0xff;
    if( 3*r + 6*g + b > 5*255 ){
      zFg = "black";
    }else{
      zFg = "white";
    }
    if( zReq[0]!='#' ){
      char zReqRGB[12];
      sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq));
      @ <tr><td style='color:%h(zFg);background-color:%h(zReq);'>\
      @ Requested color "%h(zReq)" (%h(zReqRGB))</td>
    }else{
      @ <tr><td style='color:%h(zFg);background-color:%s(zReq);'>\
      @ Requested color "%h(zReq)"</td>
    }
    zBg1 = reasonable_bg_color(zReq,1);
    @ <td style='color:black;background-color:%h(zBg1);'>\
    @ Background color for dark text: %h(zBg1)</td>
    zBg1 = reasonable_bg_color(zReq,2);
    @ <td style='color:white;background-color:%h(zBg1);'>\
    @ Background color for light text: %h(zBg1)</td></tr>
  }
  if( cnt ){
    @ </table>
    @ <hr>
  }
  @ <form method="POST">
  @ <p>Enter CSS color names below and see them shifted into corresponding
  @ background colors above.</p>
  for(i=0; i<10; i++){
    sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
    @ <input type="text" size="30" name='%s(zNm)' \
    @ value='%h(PD(zNm,azDflt[i]))'><br>
  }
  @ <input type="submit" value="Submit">
  @ </form>
  style_finish_page();
}