text: /**
   * UTF-8 utility functions
   *
   * (c) 2010 Steve Bennett <steveb@workware.net.au>
   *
   * See LICENCE for licence details.
   */
  
  #include <ctype.h>
  #include <stdlib.h>
  #include <string.h>
  #include <stdio.h>
  #include <assert.h>
  #include "utf8.h"
  
  /* This one is always implemented */
  int utf8_fromunicode(char *p, unsigned short uc)
  {
      if (uc <= 0x7f) {
          *p = uc;
          return 1;
      }
      else if (uc <= 0x7ff) {
          *p++ = 0xc0 | ((uc & 0x7c0) >> 6);
          *p = 0x80 | (uc & 0x3f);
          return 2;
      }
      else {
          *p++ = 0xe0 | ((uc & 0xf000) >> 12);
          *p++ = 0x80 | ((uc & 0xfc0) >> 6);
          *p = 0x80 | (uc & 0x3f);
          return 3;
      }
  }
  
  #if defined(JIM_UTF8) && !defined(JIM_BOOTSTRAP)
  int utf8_charlen(int c)
  {
      if ((c & 0x80) == 0) {
          return 1;
      }
      if ((c & 0xe0) == 0xc0) {
          return 2;
      }
      if ((c & 0xf0) == 0xe0) {
          return 3;
      }
      if ((c & 0xf8) == 0xf0) {
          return 4;
      }
      /* Invalid sequence */
      return -1;
  }
  
  int utf8_strlen(const char *str, int bytelen)
  {
      int charlen = 0;
      if (bytelen < 0) {
          bytelen = strlen(str);
      }
      while (bytelen) {
          int c;
          int l = utf8_tounicode(str, &c);
          charlen++;
          str += l;
          bytelen -= l;
      }
      return charlen;
  }
  
  int utf8_index(const char *str, int index)
  {
      const char *s = str;
      while (index--) {
          int c;
          s += utf8_tounicode(s, &c);
      }
      return s - str;
  }
  
  int utf8_charequal(const char *s1, const char *s2)
  {
      int c1, c2;
  
      utf8_tounicode(s1, &c1);
      utf8_tounicode(s2, &c2);
  
      return c1 == c2;
  }
  
  int utf8_prev_len(const char *str, int len)
  {
      int n = 1;
  
      assert(len > 0);
  
      /* Look up to len chars backward for a start-of-char byte */
      while (--len) {
          if ((str[-n] & 0x80) == 0) {
              /* Start of a 1-byte char */
              break;
          }
          if ((str[-n] & 0xc0) == 0xc0) {
              /* Start of a multi-byte char */
              break;
          }
          n++;
      }
      return n;
  }
  
  int utf8_tounicode(const char *str, int *uc)
  {
      unsigned const char *s = (unsigned const char *)str;
  
      if (s[0] < 0xc0) {
          *uc = s[0];
          return 1;
      }
      if (s[0] < 0xe0) {
          if ((s[1] & 0xc0) == 0x80) {
              *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80);
              return 2;
          }
      }
      else if (s[0] < 0xf0) {
          if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) {
              *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80);
              return 3;
          }
      }
  
      /* Invalid sequence, so just return the byte */
      *uc = *s;
      return 1;
  }
  
  struct casemap {
      unsigned short code;    /* code point */
      signed char lowerdelta; /* add for lowercase, or if -128 use the ext table */
      signed char upperdelta; /* add for uppercase, or offset into the ext table */
  };
  
  /* Extended table for codepoints where |delta| > 127 */
  struct caseextmap {
      unsigned short lower;
      unsigned short upper;
  };
  
  /* Generated mapping tables */
  #include "_unicode_mapping.c"
  
  #define NUMCASEMAP sizeof(unicode_case_mapping) / sizeof(*unicode_case_mapping)
  
  static int cmp_casemap(const void *key, const void *cm)
  {
      return *(int *)key - (int)((const struct casemap *)cm)->code;
  }
  
  static int utf8_map_case(int uc, int upper)
  {
      const struct casemap *cm = bsearch(&uc, unicode_case_mapping, NUMCASEMAP, sizeof(*unicode_case_mapping), cmp_casemap);
  
      if (cm) {
          if (cm->lowerdelta == -128) {
              uc = upper ? unicode_extmap[cm->upperdelta].upper : unicode_extmap[cm->upperdelta].lower;
          }
          else {
              uc += upper ? cm->upperdelta : cm->lowerdelta;
          }
      }
      return uc;
  }
  
  int utf8_upper(int uc)
  {
      if (isascii(uc)) {
          return toupper(uc);
      }
      return utf8_map_case(uc, 1);
  }
  
  int utf8_lower(int uc)
  {
      if (isascii(uc)) {
          return tolower(uc);
      }
  
      return utf8_map_case(uc, 0);
  }
  
  #endif /* JIM_BOOTSTRAP */
  