/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

int ON_IsValidUnicodeCodePoint(ON__UINT32 u)
{
  return (u < 0xD800 || (u >= 0xE000 && u <= 0x10FFFF));
}

int ON_IsValidUTF32Value(
  ON__UINT32 c
  )
{
  return (c < 0xD800 || (c >= 0xE000 && c <= 0x10FFFF));
}

int ON_IsValidSingleElementUTF16Value(ON__UINT32 c)
{
  return ((c <= 0xD7FF) || (c >= 0xE000 && c <= 0xFFFF));
}

int ON_IsValidUTF16Singleton(ON__UINT32 c)
{
  return ((c <= 0xD7FF) || (c >= 0xE000 && c <= 0xFFFF));
}

enum ON_UnicodeEncoding ON_UnicodeNativeCPU_UTF16()
{
  return (ON::endian::little_endian== ON::Endian()) ? ON_UTF_16LE : ON_UTF_16BE;
}

enum ON_UnicodeEncoding ON_UnicodeNativeCPU_UTF32()
{
  return (ON::endian::little_endian== ON::Endian()) ? ON_UTF_32LE : ON_UTF_32BE;
}

int ON_IsValidSingleByteUTF8CharValue(
  char c
  )
{
  return (c >= 0 && c <= 0x7F);
}

int ON_IsValidUTF8SingletonChar(
  char c
  )
{
  return (c >= 0 && c <= 0x7F);
}

int ON_IsValidSingleElementUTF8Value(
  ON__UINT32 c
  )
{
  return (c <= 0x7F);
}

int ON_IsValidUTF8Singleton(
  ON__UINT32 c
  )
{
  return (c <= 0x7FU);
}

int ON_IsValidUTF16SurrogatePair(
  unsigned int w1,
  unsigned int w2
  )
{
  return ( w1 >= 0xD800U && w1 < 0xDC00 && w2 >= 0xDC00 && w2 < 0xE000 );
}

unsigned int ON_DecodeUTF16SurrogatePair(
  unsigned int u1,
  unsigned int u2,
  unsigned int error_code_point
)
{
  if (u1 >= 0xD800U && u1 < 0xDC00 && u2 >= 0xDC00 && u2 < 0xE000)
  {
    return ((u1-0xD800)*0x400 + (u2-0xDC00) + 0x10000);
  }
  return error_code_point;
}


int ON_IsValidSingleElementWideCharValue(
  wchar_t w
  )
{
#pragma ON_PRAGMA_WARNING_PUSH
// warning C4127: conditional expression is constant
#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4127 )
  if (1 == sizeof(w))
    return ON_IsValidSingleElementUTF8Value((ON__UINT32)w);
  if (2 == sizeof(w))
    return ON_IsValidSingleElementUTF16Value((ON__UINT32)w);
  return ON_IsValidUTF32Value((ON__UINT32)w);
#pragma ON_PRAGMA_WARNING_POP
}

enum ON_UnicodeEncoding ON_IsUTFByteOrderMark(
  const void* buffer,
  size_t sizeof_buffer
  )
{  
  if ( 0 != buffer && sizeof_buffer >= 2 )
  {
    const unsigned char* b = static_cast<const unsigned char*>(buffer);

    if ( 0 == b[0] )
    {
      if ( sizeof_buffer >= 4 && 0 == b[1] && 0xFE == b[2] && 0xFF == b[3] )
        return ON_UTF_32BE;
    }
    else if ( 0xEF == b[0] )
    {
      if ( sizeof_buffer >= 3 && 0xBB == b[1] && 0xBF == b[2] )
        return ON_UTF_8;
    }
    else if ( 0xFE == b[0] )
    {
      if ( 0xFF == b[1] )
        return ON_UTF_16BE;
    }
    else if ( 0xFF == b[0] && 0xFE == b[1] )
    {
      return ( sizeof_buffer >= 4 && 0 == b[2] && 0 == b[3] )
        ? ON_UTF_32LE
        : ON_UTF_16LE;
    }

  }

  return ON_UTF_unset;
}

unsigned int ON_UTFSizeofByteOrderMark(
  ON_UnicodeEncoding e
  )
{
  unsigned int sizeof_bom;
  switch (e)
  {
  case ON_UTF_8:
    sizeof_bom = 3;
    break;

  case ON_UTF_16:
  case ON_UTF_16BE:
  case ON_UTF_16LE:
    sizeof_bom = 2;
    break;

  case ON_UTF_32:
  case ON_UTF_32BE:
  case ON_UTF_32LE:
    sizeof_bom = 4;
    break;

  default:
    sizeof_bom = 0;
    break;
  }

  return sizeof_bom;
}

static int ON_IsUTF8ByteOrderMark(
  const char* sUTF8,
  int sUTF8_count
  )
{
  if ( 0 == sUTF8 )
    return 0;
  if ( -1 != sUTF8_count || sUTF8_count < 3 )
    return 0;
  return (0xEF == (unsigned char)(sUTF8[0]) && 0xBB == (unsigned char)(sUTF8[1]) && 0xBF == (unsigned char)(sUTF8[2]));
}

bool ON_IsUnicodeControlCodePoint(
  ON__UINT32 code_point,
  bool bNullReturnValue
)
{
  if (0 == code_point)
    return bNullReturnValue ? true : false;
  if (code_point < 0x0020)
    return true; // below space
  if (code_point < 0x007f)
    return false;
  if (code_point <= 0x00A0)
    return true; // del to 0xA0
  if (code_point < 0x00AD)
    return false;
  if (code_point == 0x00AD)
    return true; // soft hyphen

  return false;
}

int ON_EncodeUTF8( ON__UINT32 u, char sUTF8[6] )
{
  ON__UINT32 c;

  if ( u <= 0x7F )
  {
    // 1 byte UTF8 encoding: 0xxxxxxx (7 bits of u)
    sUTF8[0] = (char)u;
    return 1;
  }

  if ( u <= 0x7FF )
  {
    // 2 byte UTF8 encoding: 110xxxxx, 10xxxxxx (11 bits of u)
    c = (u / 0x40);  // c  = 000xxxxx
    c |= 0xC0;                      //   |= 11000000
    sUTF8[0] = (char)c;
    c = (u & 0x3F);
    c |= 0x80;
    sUTF8[1] = (char)c;
    return 2;
  }

  if ( u <= 0xFFFF )
  {
    // 3 byte UTF8 encoding: 1110xxxx, 10xxxxxx, 10xxxxxx (16 bits of u)
    c = (u / 0x1000); // c  = 0000xxxx
    c |= 0xE0;                       //   |= 11100000
    sUTF8[0] = (char)c;
    c = ((u & 0xFFF) / 0x40);
    c |= 0x80;
    sUTF8[1] = (char)c;
    c = u & 0x3F;
    c |= 0x80;
    sUTF8[2] = (char)c;
    return 3;
  }

  if ( u <= 0x1FFFFF )
  {
    // (maximum valid unicode codepoint is 0x10FFFF)
    // 4 byte UTF8 encoding: 11110xxx, 10xxxxxx, 10xxxxxx, 10xxxxxx (21 bits of u)
    // Note: 0x10FFFF is the maximum valid unicode code point.
    // For u > 0x10FFFF and u <= 0x1FFFFF, this calculation encodes the low 21 bits of u.
    c = (u / 0x40000);  // c  = 00000xxx
    c |= 0xF0;                         //   |= 11110000
    sUTF8[0] = (char)c;
    c = ((u & 0x3FFFF)/0x1000);
    c |= 0x80;
    sUTF8[1] = (char)c;
    c = ((u & 0xFFF) / 0x40);
    c |= 0x80;
    sUTF8[2] = (char)c;
    c = u & 0x3F;
    c |= 0x80;
    sUTF8[3] = (char)c;
    return 4;
  }

  if ( u <= 0x3FFFFFF )
  {
    // 5 byte encoding: 111110xx, 10xxxxxx, 10xxxxxx, 10xxxxxx, 10xxxxxx (26 bits of u)
    // Note: 0x10FFFF is the maximum valid unicode code point.
    c = (u / 0x1000000); // c  = 000000xx
    c |= 0xF8;                         //   |= 11111000
    sUTF8[0] = (char)c;
    c = ((u & 0xFFFFFF)/0x40000);
    c |= 0x80;
    sUTF8[1] = (char)c;
    c = ((u & 0x3FFFF)/0x1000);
    c |= 0x80;
    sUTF8[2] = (char)c;
    c = ((u & 0xFFF) / 0x40);
    c |= 0x80;
    sUTF8[3] = (char)c;
    c = u & 0x3F;
    c |= 0x80;
    sUTF8[4] = (char)c;
    return 5;
  }

  if ( u <= 0x7FFFFFFF )
  {
    // 6 byte encoding: 1111110x, 10xxxxxx, 10xxxxxx, 10xxxxxx, 10xxxxxx, 10xxxxxx (31 bits of u)
    // Note: 0x10FFFF is the maximum valid unicode code point.
    c = (u / 0x40000000); // c  = 00000000x
    c |= 0xFC;                           //   |= 11111100
    sUTF8[0] = (char)c;
    c = ((u & 0x3FFFFFFF)/0x1000000);
    c |= 0x80;
    sUTF8[1] = (char)c;
    c = ((u & 0xFFFFFF)/0x40000);
    c |= 0x80;
    sUTF8[2] = (char)c;
    c = ((u & 0x3FFFF)/0x1000);
    c |= 0x80;
    sUTF8[3] = (char)c;
    c = ((u & 0xFFF) / 0x40);
    c |= 0x80;
    sUTF8[4] = (char)c;
    c = u & 0x3F;
    c |= 0x80;
    sUTF8[5] = (char)c;
    return 6;
  }

  return 0;
}

static int ON_DecodeUTF8Helper(
    const char* sUTF8,
    int sUTF8_count,
    ON__UINT32* value,
    unsigned int* error_status
    )
{
#define INPUT_BUFFER_TOO_SHORT 16
#define INVALID_CONTINUATION_VALUE 16
#define OVERLONG_ENCODING 8

  ON__UINT32 u;
  char c;

  c = sUTF8[0];
   
  if ( 0 == (0x80 & c) )
  {
    // 1 byte ASCII encoding: 0xxxxxxx
    *value = c;
    return 1;
  }

  if ( 0xC0 == ( 0xE0 & c) )
  {
    // 2 byte character encoding: 10xxxxxx, 10xxxxxx
    if ( sUTF8_count < 2 )
    {
      *error_status |= INPUT_BUFFER_TOO_SHORT; // input buffer too short
      return 0;
    }
    u = (0x1F & c);
    c = sUTF8[1];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    if ( u <= 0x7F )
    {
      *error_status |= OVERLONG_ENCODING; // overlong 2 byte character encoding
    }
    *value = u;
    return 2;
  }

  if ( 0xE0 == ( 0xF0 & c) )
  {
    // 3 byte character encoding: 110xxxxx, 10xxxxxx, 10xxxxxx
    if ( sUTF8_count < 3 )
    {
      *error_status |= INPUT_BUFFER_TOO_SHORT; // input buffer too short
      return 0;
    }
    u = (0x0F & c);
    c = sUTF8[1];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[2];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    if ( u <= 0x7FF )
    {
      *error_status |= OVERLONG_ENCODING; // overlong 3 byte character encoding
    }
    *value = u;
    return 3;
  }

  if ( 0xF0 == ( 0xF8 & c) )
  {
    // 4 byte character encoding: 11110xxx, 10xxxxxx, 10xxxxxx, 10xxxxxx
    if ( sUTF8_count < 4 )
    {
      *error_status |= INPUT_BUFFER_TOO_SHORT; // input buffer too short
      return 0;
    }

    u = (0x07 & c);
    c = sUTF8[1];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[2];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[3];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    if ( u <= 0xFFFF )
    {
      *error_status |= OVERLONG_ENCODING; // overlong 4 byte character encoding
    }
    *value = u;
    return 4;
  }
  
  if ( 0xF8 == ( 0xFC & c) )
  {
    // 5 byte character encoding: 111110xx, 10xxxxxx, 10xxxxxx, 10xxxxxx, 10xxxxxx
    if ( sUTF8_count < 5 )
    {
      *error_status |= INPUT_BUFFER_TOO_SHORT; // input buffer too short
      return 0;
    }

    u = (0x03 & c);
    c = sUTF8[1];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[2];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[3];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[4];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    if ( u <= 0x1FFFFF )
    {
      *error_status |= OVERLONG_ENCODING; // overlong 5 byte character encoding
    }
    *value = u;
    return 5;
  }

  if ( 0xFC == ( 0xFE & c) )
  {
    // 6 byte character encoding: 110xxxxx, 10xxxxxx, 10xxxxxx, 10xxxxxx, 10xxxxxx, 10xxxxxx
    if ( sUTF8_count < 6 )
    {
      *error_status |= INPUT_BUFFER_TOO_SHORT; // input buffer too short
      return 0;
    }

    u = (0x01 & c);
    c = sUTF8[1];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[2];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[3];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[4];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    c = sUTF8[5];
    if (  0x80 != ( 0xC0 & c) )
    {
      *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 continuation value
      return 0;
    }
    u *= 64;
    u |= (0x3F & c);
    if ( u <= 0x3FFFFFF )
    {
      *error_status |= OVERLONG_ENCODING; // overlong 6 byte character encoding
    }
    *value = u;
    return 6;
  }

  *error_status |= INVALID_CONTINUATION_VALUE; // invalid UTF=8 start value
  return 0;

#undef INPUT_BUFFER_TOO_SHORT
#undef INVALID_CONTINUATION_VALUE
#undef OVERLONG_ENCODING
}

int ON_DecodeUTF8(
    const char* sUTF8,
    int sUTF8_count,
    struct ON_UnicodeErrorParameters* e,
    ON__UINT32* unicode_code_point
    )
{
  ON__UINT32 u0, u1;
  int i0, i1;
  unsigned int error_status;
  ON__UINT16 sUTF16[2];
  char c;

  ON_UnicodeErrorParameters local_e = ON_UnicodeErrorParameters::MaskErrors;
  if (nullptr == e)
    e = &local_e;

  if (  0 == sUTF8 || sUTF8_count <= 0 || 0 == unicode_code_point )
  {
    if ( e )
      e->m_error_status |= 1;
    return 0;
  }

  // special cases for most common unicode values
  // If any error conditions exist, then ON_DecodeUTF8Helper()
  // is used.
  if ( 0 == (0x80 & sUTF8[0]) )
  {
    *unicode_code_point = sUTF8[0];
    return 1;
  }
  
  c = sUTF8[0];
  if ( 0xC0 == ( 0xE0 & c) && sUTF8_count >= 2 )
  {
    // 2 byte character encoding: 10xxxxxx, 10xxxxxx
    u0 = (0x1F & c);
    c = sUTF8[1];
    if (  0x80 == ( 0xC0 & c) )
    {
      u0 *= 64;
      u0 |= (0x3F & c);
      if ( u0 > 0x7F )
      {
        *unicode_code_point = u0;
        return 2;
      }
    }
  }
  else if ( 0xE0 == ( 0xF0 & c) && sUTF8_count >= 3 )
  {
    // 3 byte character encoding: 110xxxxx, 10xxxxxx, 10xxxxxx
    u0 = (0x0F & c);
    c = sUTF8[1];
    if (  0x80 == ( 0xC0 & c) )
    {
      u0 *= 64;
      u0 |= (0x3F & c);
      c = sUTF8[2];
      if (  0x80 == ( 0xC0 & c) )
      {
        u0 *= 64;
        u0 |= (0x3F & c);
        if ( u0 >= 0x0800 && (u0 <= 0xD800 || u0 >= 0xE000) )
        {
          *unicode_code_point = u0;
          return 3;
        }
      }
    }
  }
  else if ( 0xF0 == ( 0xF8 & c) && sUTF8_count >= 4 )
  {
    // 4 byte character encoding: 11110xxx, 10xxxxxx, 10xxxxxx, 10xxxxxx
    u0 = (0x07 & c);
    c = sUTF8[1];
    if (  0x80 == ( 0xC0 & c) )
    {
      u0 *= 64;
      u0 |= (0x3F & c);
      c = sUTF8[2];
      if (  0x80 == ( 0xC0 & c) )
      {
        u0 *= 64;
        u0 |= (0x3F & c);
        c = sUTF8[3];
        if (  0x80 == ( 0xC0 & c) )
        {
          u0 *= 64;
          u0 |= (0x3F & c);
          if ( u0 >= 0x010000 && u0 <= 0x10FFFF )
          {
            *unicode_code_point = u0;
            return 4;
          }
        }
      }
    }
  }


  error_status = 0;
  u0 = 0xFFFFFFFF;
  i0 = ON_DecodeUTF8Helper(sUTF8,sUTF8_count,&u0,&error_status);
  if ( i0 > 0 && 0 == error_status && (u0 < 0xD800 || (u0 >= 0xE000 && u0 <= 0x10FFFF) ) )
  {
    // valid UTF-8 multibyte encoding parsed
    *unicode_code_point = u0;
    return i0;
  }

  // handle errors
  if ( 0 == e )
  {
    // no errors are masked.
    return 0;
  }

  // report error condition
  e->m_error_status |= error_status;

  if ( error_status != (error_status & e->m_error_mask) )
  {
    // this error is not masked
    return 0;
  }

  if ( i0 <= 0 )
  {
    i0 = 1;
    if ( ON_IsValidUnicodeCodePoint(e->m_error_code_point) )
    {
      // skip to next UTF-8 start elemement
      for ( /*empty for initializer*/; i0 < sUTF8_count; i0++ )
      {
        // Search for the next element of sUTF8[] that is the 
        // start of a UTF-8 encoding sequence.
        c = sUTF8[i0];
        if (    0 == (0x80 & c)     // ASCII 0 - 127
              || 0xC0 == ( 0xE0 & c) // 2 byte encoding first character
              || 0xE0 == ( 0xF0 & c) // 3 byte encoding first character
              || 0xF0 == ( 0xF8 & c) // 4 byte encoding first character
              || 0xF8 == ( 0xFC & c) // 5 byte encoding first character
              || 0xFC == ( 0xFE & c) // 6 byte encoding first character
            )
        {
          // resume parsing at this character
          break;
        }
      }
      *unicode_code_point = e->m_error_code_point;
    }
    return i0;
  }

  if ( ON_IsValidUnicodeCodePoint(u0) && 8 == error_status )
  {
    // overlong UTF-8 multibyte encoding of valid unicode code point
    *unicode_code_point = u0;
    return i0;
  }
  
  if ( i0 < sUTF8_count 
       && u0 >= 0xD800 && u0 <= 0xDBFF 
       && (0 == error_status || 8 == error_status)
       && 0 != (4 & e->m_error_mask) 
     )
  {
    // See if a UFT-16 surrogate pair was incorrectly encoded 
    // as two consecutive UTF-8 sequences.
    u1 = 0xFFFFFFFF;
    i1 = ON_DecodeUTF8Helper(sUTF8+i0,sUTF8_count-i0,&u1,&error_status);
    if ( i1 > 0 && (0 == error_status || 8 == error_status) )
    {
      error_status = 0;
      sUTF16[0] = (ON__UINT16)u0;
      sUTF16[1] = (ON__UINT16)u1;
      u0 = 0xFFFFFFFF;
      if ( 2 == ON_ConvertUTF16ToUTF32(false,sUTF16,2,&u0,1,&error_status,0,0,0) 
           && 0 == error_status 
           && ON_IsValidUnicodeCodePoint(u0)
         )
      {
        *unicode_code_point = u0;
        e->m_error_status |= 4;
        return i0+i1;
      }
    }
  }

  if ( ON_IsValidUnicodeCodePoint(e->m_error_code_point) )
  {
    *unicode_code_point = e->m_error_code_point;
    return i0;
  }

  return 0;
}

int ON_EncodeUTF16( ON__UINT32 unicode_code_point, ON__UINT16 sUTF16[2] )
{
  // put the most comman case first
  if ( unicode_code_point < 0xD800 )
  {
    // code point values U+0000 ... U+D7FF
    // = UTF-16 values
    sUTF16[0] = (ON__UINT16)unicode_code_point;
    return 1;
  }

  if ( unicode_code_point < 0xE000 )
  {
    // 0xD800 ... 0xDFFF are invalid unicode code point values
    return 0;
  }

  if ( unicode_code_point <= 0xFFFF )
  {
    // code point values U+E000 ... U+FFFF
    // = UTF-16 values
    sUTF16[0] = (ON__UINT16)unicode_code_point;
    return 1;
  }

  if ( unicode_code_point <= 0x10FFFF )
  {
    // code point values U+10000 ... U+10FFFF
    // = surrogate pair UTF-16 values
    unicode_code_point -= 0x10000;
    sUTF16[0] = (ON__UINT16)(0xD800 + (unicode_code_point / 0x400)); // high surrogate value (0xD800 ... 0xDBFF)
    sUTF16[1] = (ON__UINT16)(0xDC00 + (unicode_code_point & 0x3FF)); // low surrogate value (0xDC00 ... 0xDFFF)
    return 2;
  }

  // 0x110000 ... 0xFFFFFFFF are invalid unicode code point values
  return 0;
}

int ON_DecodeUTF16(
    const ON__UINT16* sUTF16,
    int sUTF16_count,
    struct ON_UnicodeErrorParameters* e,
    ON__UINT32* unicode_code_point
    )
{
  ON__UINT32 uhi, ulo;

  ON_UnicodeErrorParameters local_e = ON_UnicodeErrorParameters::MaskErrors;
  if (nullptr == e)
    e = &local_e;

  if ( 0 == sUTF16 || sUTF16_count <= 0 || 0 == unicode_code_point )
  {
    if ( e )
      e->m_error_status |= 1;
    return 0;
  }

  // special case for most common UTF-16 single element values
  if ( ( sUTF16[0] < 0xD800 ) || ( sUTF16[0] >= 0xE000 ) )
  {
    *unicode_code_point = sUTF16[0];
    return 1;
  }

  if ( sUTF16_count >= 2 && sUTF16[0] < 0xDC00 && sUTF16[1] >=  0xDC00 && sUTF16[1] < 0xE000 )
  {
    // UTF-16 surrogate pair
    uhi = sUTF16[0];
    ulo = sUTF16[1];
    *unicode_code_point = (uhi-0xD800)*0x400 + (ulo-0xDC00) + 0x10000;
    return 2;
  }

  // handle errors
  if ( 0 == e )
  {
    // no errors are masked.
    return 0;
  }

  // report error condition
  e->m_error_status |= 16;

  if ( 16 != (16 & e->m_error_mask) || !ON_IsValidUnicodeCodePoint(e->m_error_code_point) )
  {
    // this error is not masked
    return 0;
  }

  // Search for the next element of sUTF16[] that is a 
  // valid UTF-16 encoding sequence.
  int i;
  for ( i = 1; i < sUTF16_count; i++ )
  {
    if ( ( sUTF16[i] < 0xD800 ) || ( sUTF16[i] >= 0xE000 ) )
    {
      // valid single UTF-16 code unit
      break;
    }
    if ( i+1 < sUTF16_count 
         && sUTF16[i] >= 0xD800 && sUTF16[i] < 0xDC00 
         && sUTF16[i+1] >= 0xDC00 && sUTF16[i+1] < 0xE000
       )
    {
      // valid UTF-16 surrogate pair
      break;
    }
  }

  *unicode_code_point = e->m_error_code_point;

  return i;
}

int ON_DecodeUTF16LE(
  const ON__UINT16* sUTF16,
  int sUTF16_count,
  struct ON_UnicodeErrorParameters* e,
  ON__UINT32* unicode_code_point
  )
{
#if defined(ON_LITTLE_ENDIAN)
  return ON_DecodeUTF16(sUTF16,sUTF16_count,e,unicode_code_point);
#else
  return ON_DecodeSwapByteUTF16(sUTF16,sUTF16_count,e,unicode_code_point);
#endif
}

int ON_DecodeUTF16BE(
  const ON__UINT16* sUTF16,
  int sUTF16_count,
  struct ON_UnicodeErrorParameters* e,
  ON__UINT32* unicode_code_point
  )
{
#if defined(ON_BIG_ENDIAN)
  return ON_DecodeUTF16(sUTF16,sUTF16_count,e,unicode_code_point);
#else
  return ON_DecodeSwapByteUTF16(sUTF16,sUTF16_count,e,unicode_code_point);
#endif
}

int ON_DecodeUTF32LE(
  const ON__UINT32* sUTF32,
  int sUTF32_count,
  struct ON_UnicodeErrorParameters* e,
  ON__UINT32* unicode_code_point
  )
{
#if defined(ON_LITTLE_ENDIAN)
  return ON_DecodeUTF32(sUTF32,sUTF32_count,e,unicode_code_point);
#else
  return ON_DecodeSwapByteUTF32(sUTF32,sUTF32_count,e,unicode_code_point);
#endif
}

int ON_DecodeUTF32BE(
  const ON__UINT32* sUTF32,
  int sUTF32_count,
  struct ON_UnicodeErrorParameters* e,
  ON__UINT32* unicode_code_point
  )
{
#if defined(ON_BIG_ENDIAN)
  return ON_DecodeUTF32(sUTF32,sUTF32_count,e,unicode_code_point);
#else
  return ON_DecodeSwapByteUTF32(sUTF32,sUTF32_count,e,unicode_code_point);
#endif
}


int ON_EncodeWideChar(
  ON__UINT32 code_point,
  size_t w_capacity,
  wchar_t* w
  )
{
  int rc = 0;
  if (nullptr != w && w_capacity > 0)
  {
    if (ON_IsValidUnicodeCodePoint(code_point))
    {
#if 1 == ON_SIZEOF_WCHAR_T
      char sUTF8[6];
      rc = ON_EncodeUTF8(code_point, sUTF8);
      if (rc > (int)w_capacity)
        rc = 0;
      switch (rc)
      {
      case 1:
        w[0] = (wchar_t)sUTF8[0];
        break;
      case 2:
        w[0] = (wchar_t)sUTF8[0];
        w[1] = (wchar_t)sUTF8[1];
        break;
      case 3:
        w[0] = (wchar_t)sUTF8[0];
        w[1] = (wchar_t)sUTF8[1];
        w[2] = (wchar_t)sUTF8[2];
        break;
      case 4:
        w[0] = (wchar_t)sUTF8[0];
        w[1] = (wchar_t)sUTF8[1];
        w[2] = (wchar_t)sUTF8[2];
        w[3] = (wchar_t)sUTF8[3];
        break;
      default:
        rc = 0; break;
      }
#elif 2 == ON_SIZEOF_WCHAR_T
      ON__UINT16 sUTF16[2];
      rc = ON_EncodeUTF16(code_point, sUTF16);
      if (rc > (int)w_capacity)
        rc = 0;
      switch (rc)
      {
      case 1:
        w[0] = (wchar_t)sUTF16[0];
        break;
      case 2:
        w[0] = (wchar_t)sUTF16[0];
        w[1] = (wchar_t)sUTF16[1];
        break;
      default:
        rc = 0; break;
      }
#elif 4 == ON_SIZEOF_WCHAR_T
      if (w_capacity > 0)
      {
        w[0] = (wchar_t)code_point;
        rc = 1;
      }
#endif
    }
    if (rc >= 0 && rc < (int)w_capacity)
      w[rc] = 0;
  }
  return rc;
}

int ON_DecodeWideChar(
  const wchar_t* sWideChar,
  int sWideChar_count,
  struct ON_UnicodeErrorParameters* e,
  ON__UINT32* unicode_code_point
  )
{
  const ON_UnicodeEncoding widechar_encoding = ON_WCHAR_T_ENCODING;
  int rc;

  switch (widechar_encoding)
  {
#if 1 == ON_SIZEOF_WCHAR_T
  case ON_UTF_8:
    rc = ON_DecodeUTF8((const char*)sWideChar,sWideChar_count,e,unicode_code_point);
    break;

#elif 2 == ON_SIZEOF_WCHAR_T
  case ON_UTF_16:
    return ON_DecodeUTF16((const ON__UINT16*)sWideChar,sWideChar_count,e,unicode_code_point);
    break;

  case ON_UTF_16BE:
    rc = ON_DecodeUTF16BE((const ON__UINT16*)sWideChar,sWideChar_count,e,unicode_code_point);
    break;

  case ON_UTF_16LE:
    rc = ON_DecodeUTF16LE((const ON__UINT16*)sWideChar,sWideChar_count,e,unicode_code_point);
    break;

#elif 4 == ON_SIZEOF_WCHAR_T
  case ON_UTF_32:
    rc = ON_DecodeUTF32((const ON__UINT32*)sWideChar,sWideChar_count,e,unicode_code_point);
    break;

  case ON_UTF_32BE:
    rc = ON_DecodeUTF32BE((const ON__UINT32*)sWideChar,sWideChar_count,e,unicode_code_point);
    break;

  case ON_UTF_32LE:
    rc = ON_DecodeUTF32LE((const ON__UINT32*)sWideChar,sWideChar_count,e,unicode_code_point);
    break;
#endif

  default:
    rc = 0;
    if ( e )
      e->m_error_status |= 1;
    break;
  }

  return rc;
}


int ON_DecodeSwapByteUTF16(
    const ON__UINT16* sUTF16,
    int sUTF16_count,
    struct ON_UnicodeErrorParameters* e,
    ON__UINT32* unicode_code_point
    )
{
  int i;
  ON__UINT32 uhi, ulo;
  ON__UINT16 w0, w1;
  const ON__UINT8* p;
  ON__UINT8* p0;
  ON__UINT8* p1;

  ON_UnicodeErrorParameters local_e = ON_UnicodeErrorParameters::MaskErrors;
  if (nullptr == e)
    e = &local_e;

  if ( 0 == sUTF16 || sUTF16_count <= 0 || 0 == unicode_code_point )
  {
    if ( e )
      e->m_error_status |= 1;
    return 0;
  }

  // special case for most common UTF-16 single element values
  // w0 = byte swapped sUTF16[0]
  p = (const ON__UINT8*)sUTF16;
  p0 = (ON__UINT8*)&w0;
  p0[1] = p[0];
  p0[0] = p[1];
  if ( ( w0 < 0xD800 ) || (w0 >= 0xE000 ) )
  {
    *unicode_code_point = w0;
    return 1;
  }

  if ( sUTF16_count >= 2 && w0 < 0xDC00 )
  {
    // w1 = byte swapped sUTF16[1]
    p1 = (ON__UINT8*)&w1;
    p1[1] = p[2];
    p1[0] = p[3];
    if ( w1 >=  0xDC00 && w1 < 0xE000 )
    {
      // UTF-16 surrogate pair
      uhi = w0;
      ulo = w1;
      *unicode_code_point = (uhi-0xD800)*0x400 + (ulo-0xDC00) + 0x10000;
      return 2;
    }
  }

  // handle errors
  if ( 0 == e )
  {
    // no errors are masked.
    return 0;
  }

  // report error condition
  e->m_error_status |= 16;

  if ( 16 != (16 & e->m_error_mask) || !ON_IsValidUnicodeCodePoint(e->m_error_code_point) )
  {
    // this error is not masked
    return 0;
  }

  // Search for the next element of sUTF16[] that is a 
  // valid UTF-16 encoding sequence.
  p1 = (ON__UINT8*)&w1;
  p += sizeof(sUTF16[0]);
  for ( i = 1; i < sUTF16_count; i++, p += sizeof(sUTF16[0]) )
  {
    // w0 = byte swapped sUTF16[i]
    p0[1] = p[0];
    p0[0] = p[1];
    if ( ( w0 < 0xD800 ) || ( w0 >= 0xE000 ) )
    {
      // valid single UTF-16 code unit
      break;
    }
    if ( i+1 < sUTF16_count && w0 >= 0xD800 && w0 < 0xDC00 )
    {
      // w1 = byte swapped sUTF16[i+1]
      p1[1] = p[sizeof(sUTF16[0])];
      p1[0] = p[sizeof(sUTF16[0])+1];
      if ( w1 >= 0xDC00 && w1 < 0xE000 )
      {
        // valid UTF-16 surrogate pair
        break;
      }
    }
  }

  *unicode_code_point = e->m_error_code_point;

  return i;
}

int ON_ConvertUTF8ToUTF8(
    int bTestByteOrder,
    const char* sInputUTF8,
    int sInputUTF8_count,
    char* sOutputUTF8,
    int sOutputUTF8_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const char** sNextInputUTF8
    )
{
  int i, j, k, output_count;
  ON__UINT32 u;
  char s[6];
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sInputUTF8_count && 0 != sInputUTF8 )
  {
    for ( sInputUTF8_count = 0; 0 != sInputUTF8[sInputUTF8_count]; sInputUTF8_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sInputUTF8 || sInputUTF8_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextInputUTF8 )
      *sNextInputUTF8 = sInputUTF8;
    return 0;
  }

  if ( 0 == sOutputUTF8_count )
  {
    sOutputUTF8 = 0;
    sOutputUTF8_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sOutputUTF8 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextInputUTF8 )
      *sNextInputUTF8 = sInputUTF8;
    return 0;
  }

  if ( bTestByteOrder && ON_IsUTF8ByteOrderMark(sInputUTF8,sInputUTF8_count) )
  {
    // skip UTF-8 byte order element
    sInputUTF8_count -= 3;
    sInputUTF8 += 3;
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  for ( i = 0; i < sInputUTF8_count; i += j )
  {
    j = ON_DecodeUTF8(sInputUTF8+i,sInputUTF8_count-i,&e,&u);
    if ( j <= 0 )
      break;
    k = ON_EncodeUTF8(u,s);
    if ( 0 != sOutputUTF8 )
    {
      if ( output_count + k > sOutputUTF8_count )
      {
        e.m_error_status |= 2;
        break;
      }
      memcpy(sOutputUTF8+output_count,s,k*sizeof(sOutputUTF8[0]));
    }
    output_count += k;
  }

  if ( 0 != sOutputUTF8 && output_count < sOutputUTF8_count)
    sOutputUTF8[output_count] = 0;
  if ( sNextInputUTF8 )
    *sNextInputUTF8 = sInputUTF8+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}


int ON_ConvertUTF8ToUTF16(
    int bTestByteOrder,
     const char* sUTF8,
    int sUTF8_count,
    ON__UINT16* sUTF16,
    int sUTF16_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const char** sNextUTF8
    )
{
  int i, j, k, output_count;
  ON__UINT32 u;
  ON__UINT16 w[2];
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sUTF8_count && 0 != sUTF8 )
  {
    for ( sUTF8_count = 0; 0 != sUTF8[sUTF8_count]; sUTF8_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sUTF8 || sUTF8_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF8 )
      *sNextUTF8 = sUTF8;
    return 0;
  }

  if ( bTestByteOrder && ON_IsUTF8ByteOrderMark(sUTF8,sUTF8_count) )
  {
    // skip UTF-8 byte order element
    sUTF8_count -= 3;
    sUTF8 += 3;
  }

  if ( 0 == sUTF16_count )
  {
    sUTF16 = 0;
    sUTF16_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sUTF16 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF8 )
      *sNextUTF8 = sUTF8;
    return 0;
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  for ( i = 0; i < sUTF8_count; i += j )
  {
    j = ON_DecodeUTF8(sUTF8+i,sUTF8_count-i,&e,&u);
    if ( j <= 0 )
      break;
    k = ON_EncodeUTF16(u,w);
    if ( 0 != sUTF16 )
    {
      if ( output_count + k > sUTF16_count )
      {
        e.m_error_status |= 2;
        break;
      }
      sUTF16[output_count] = w[0];
      if ( 2 == k )
        sUTF16[output_count+1] = w[1];
    }
    output_count += k;
  }

  if ( 0 != sUTF16 && output_count < sUTF16_count)
    sUTF16[output_count] = 0;
  if ( sNextUTF8 )
    *sNextUTF8 = sUTF8+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}

int ON_ConvertUTF8ToUTF32(
    int bTestByteOrder,
    const char* sUTF8,
    int sUTF8_count,
    ON__UINT32* sUTF32,
    int sUTF32_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const char** sNextUTF8
    )
{
  int i, j, output_count;
  ON__UINT32 u;
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sUTF8_count && 0 != sUTF8 )
  {
    for ( sUTF8_count = 0; 0 != sUTF8[sUTF8_count]; sUTF8_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sUTF8 || sUTF8_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF8 )
      *sNextUTF8 = sUTF8;
    return 0;
  }

  if ( bTestByteOrder && ON_IsUTF8ByteOrderMark(sUTF8,sUTF8_count) )
  {
    // skip UTF-8 byte order element
    sUTF8_count -= 3;
    sUTF8 += 3;
  }

  if ( 0 == sUTF32_count )
  {
    sUTF32 = 0;
    sUTF32_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sUTF32 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF8 )
      *sNextUTF8 = sUTF8;
    return 0;
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  for ( i = 0; i < sUTF8_count; i += j )
  {
    j = ON_DecodeUTF8(sUTF8+i,sUTF8_count-i,&e,&u);
    if ( j <= 0 )
      break;
    if ( 0 != sUTF32 )
    {
      if ( output_count >= sUTF32_count )
      {
        e.m_error_status |= 2;
        break;
      }
      sUTF32[output_count] = u;
    }
    output_count++;
  }

  if ( 0 != sUTF32 && output_count < sUTF32_count)
    sUTF32[output_count] = 0;
  if ( sNextUTF8 )
    *sNextUTF8 = sUTF8+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}

int ON_ConvertUTF16ToUTF8(
    int bTestByteOrder,
    const ON__UINT16* sUTF16,
    int sUTF16_count,
    char* sUTF8,
    int sUTF8_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const ON__UINT16** sNextUTF16
    )
{
  int i, j, k, output_count, bSwapBytes;
  ON__UINT32 u;
  char s[6];
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sUTF16_count && 0 != sUTF16 )
  {
    for ( sUTF16_count = 0; 0 != sUTF16[sUTF16_count]; sUTF16_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sUTF16 || sUTF16_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF16 )
      *sNextUTF16 = sUTF16;
    return 0;
  }

  if ( 0 == sUTF8_count )
  {
    sUTF8 = 0;
    sUTF8_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sUTF8 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF16 )
      *sNextUTF16 = sUTF16;
    return 0;
  }

  bSwapBytes = false;
  if ( bTestByteOrder && sUTF16_count > 0 )
  {
    if ( 0xFEFF == sUTF16[0] )
    {
      // skip BOM
      sUTF16_count--;
      sUTF16++;
    }
    else if ( 0xFFFE == sUTF16[0] )
    {
      // skip BOM and swap bytes in rest of sUTF16
      bSwapBytes = true;
      sUTF16_count--;
      sUTF16++;
    }
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  if ( bSwapBytes )
  {
    for ( i = 0; i < sUTF16_count; i += j )
    {
      j = ON_DecodeSwapByteUTF16(sUTF16+i,sUTF16_count-i,&e,&u);
      if ( j <= 0 )
        break;
      k = ON_EncodeUTF8(u,s);
      if ( 0 != sUTF8 )
      {
        if ( output_count + k > sUTF8_count )
        {
          e.m_error_status |= 2;
          break;
        }
        memcpy(sUTF8+output_count,s,k*sizeof(sUTF8[0]));
      }
      output_count += k;
    }
  }
  else
  {
    for ( i = 0; i < sUTF16_count; i += j )
    {
      j = ON_DecodeUTF16(sUTF16+i,sUTF16_count-i,&e,&u);
      if ( j <= 0 )
        break;
      k = ON_EncodeUTF8(u,s);
      if ( 0 != sUTF8 )
      {
        if ( output_count + k > sUTF8_count )
        {
          e.m_error_status |= 2;
          break;
        }
        memcpy(sUTF8+output_count,s,k*sizeof(sUTF8[0]));
      }
      output_count += k;
    }
  }
  if ( 0 != sUTF8 && output_count < sUTF8_count)
    sUTF8[output_count] = 0;
  if ( sNextUTF16 )
    *sNextUTF16 = sUTF16+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}

int ON_ConvertUTF16ToUTF16(
    int bTestByteOrder,
    const ON__UINT16* sInputUTF16,
    int sInputUTF16_count,
    ON__UINT16* sOutputUTF16,
    int sOutputUTF16_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const ON__UINT16** sNextInputUTF16
    )
{
  int i, j, k, output_count, bSwapBytes;
  ON__UINT32 u;
  ON__UINT16 s[2];
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sInputUTF16_count && 0 != sInputUTF16 )
  {
    for ( sInputUTF16_count = 0; 0 != sInputUTF16[sInputUTF16_count]; sInputUTF16_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sInputUTF16 || sInputUTF16_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextInputUTF16 )
      *sNextInputUTF16 = sInputUTF16;
    return 0;
  }

  if ( 0 == sOutputUTF16_count )
  {
    sOutputUTF16 = 0;
    sOutputUTF16_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sOutputUTF16 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextInputUTF16 )
      *sNextInputUTF16 = sInputUTF16;
    return 0;
  }

  bSwapBytes = false;
  if ( bTestByteOrder && sInputUTF16_count > 0 )
  {
    if ( 0xFEFF == sInputUTF16[0] )
    {
      // skip BOM
      sInputUTF16_count--;
      sInputUTF16++;
    }
    else if ( 0xFFFE == sInputUTF16[0] )
    {
      // skip BOM and swap bytes in rest of sInputUTF16
      bSwapBytes = true;
      sInputUTF16_count--;
      sInputUTF16++;
    }
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  if ( bSwapBytes )
  {
    for ( i = 0; i < sInputUTF16_count; i += j )
    {
      j = ON_DecodeSwapByteUTF16(sInputUTF16+i,sInputUTF16_count-i,&e,&u);
      if ( j <= 0 )
        break;
      k = ON_EncodeUTF16(u,s);
      if ( 0 != sOutputUTF16 )
      {
        if ( output_count + k > sOutputUTF16_count )
        {
          e.m_error_status |= 2;
          break;
        }
        memcpy(sOutputUTF16+output_count,s,k*sizeof(sOutputUTF16[0]));
      }
      output_count += k;
    }
  }
  else
  {
    for ( i = 0; i < sInputUTF16_count; i += j )
    {
      j = ON_DecodeUTF16(sInputUTF16+i,sInputUTF16_count-i,&e,&u);
      if ( j <= 0 )
        break;
      k = ON_EncodeUTF16(u,s);
      if ( 0 != sOutputUTF16 )
      {
        if ( output_count + k > sOutputUTF16_count )
        {
          e.m_error_status |= 2;
          break;
        }
        memcpy(sOutputUTF16+output_count,s,k*sizeof(sOutputUTF16[0]));
      }
      output_count += k;
    }
  }
  if ( 0 != sOutputUTF16 && output_count < sOutputUTF16_count)
    sOutputUTF16[output_count] = 0;
  if ( sNextInputUTF16 )
    *sNextInputUTF16 = sInputUTF16+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}

int ON_ConvertUTF16ToUTF32(
    int bTestByteOrder,
    const ON__UINT16* sUTF16,
    int sUTF16_count,
    unsigned int* sUTF32,
    int sUTF32_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const ON__UINT16** sNextUTF16
    )
{
  int i, j, output_count, bSwapBytes;
  ON__UINT32 u;
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sUTF16_count && 0 != sUTF16 )
  {
    for ( sUTF16_count = 0; 0 != sUTF16[sUTF16_count]; sUTF16_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sUTF16 || sUTF16_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF16 )
      *sNextUTF16 = sUTF16;
    return 0;
  }

  if ( 0 == sUTF32_count )
  {
    sUTF32 = 0;
    sUTF32_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sUTF32 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF16 )
      *sNextUTF16 = sUTF16;
    return 0;
  }

  bSwapBytes = false;
  if ( bTestByteOrder && sUTF16_count > 0 )
  {
    if ( 0xFEFF == sUTF16[0] )
    {
      // skip BOM
      sUTF16_count--;
      sUTF16++;
    }
    else if ( 0xFFFE == sUTF16[0] )
    {
      // skip BOM and swap bytes in rest of sUTF16
      bSwapBytes = true;
      sUTF16_count--;
      sUTF16++;
    }
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  if ( bSwapBytes )
  {
    for ( i = 0; i < sUTF16_count; i += j )
    {
      j = ON_DecodeSwapByteUTF16(sUTF16+i,sUTF16_count-i,&e,&u);
      if ( j <= 0 )
        break;
      if ( 0 != sUTF32 )
      {
        if ( output_count >= sUTF32_count )
        {
          e.m_error_status |= 2;
          break;
        }
        sUTF32[output_count] = u;
      }
      output_count++;
    }
  }
  else
  {
    for ( i = 0; i < sUTF16_count; i += j )
    {
      j = ON_DecodeUTF16(sUTF16+i,sUTF16_count-i,&e,&u);
      if ( j <= 0 )
        break;
      if ( 0 != sUTF32 )
      {
        if ( output_count >= sUTF32_count )
        {
          e.m_error_status |= 2;
          break;
        }
        sUTF32[output_count] = u;
      }
      output_count++;
    }
  }

  if ( 0 != sUTF32 && output_count < sUTF32_count)
    sUTF32[output_count] = 0;
  if ( sNextUTF16 )
    *sNextUTF16 = sUTF16+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}

static ON__UINT32 SwapBytes32(ON__UINT32 u)
{
  ON__UINT8 b;
  ON__UINT8* p = (ON__UINT8*)&u;
  b = p[0]; p[0] = p[3]; p[3] = b;
  b = p[1]; p[1] = p[2]; p[2] = b;
  return u;
}

int ON_DecodeUTF32(
    const ON__UINT32* sUTF32,
    int sUTF32_count,
    struct ON_UnicodeErrorParameters* e,
    ON__UINT32* unicode_code_point
    )
{
  ON__UINT32 uhi, ulo;

  ON_UnicodeErrorParameters local_e = ON_UnicodeErrorParameters::MaskErrors;
  if (nullptr == e)
    e = &local_e;

  if ( 0 == sUTF32 || sUTF32_count <= 0 || 0 == unicode_code_point )
  {
    e->m_error_status |= 1;
    return 0;
  }

  // special case for most common UTF-16 single element values
  if ( ( sUTF32[0] < 0xD800 ) || ( sUTF32[0] >= 0xE000 && sUTF32[0] <= 0x10FFFF) )
  {
    // valid UTF-32 encoding.
    *unicode_code_point = sUTF32[0];
    return 1;
  }

  // handle errors
  if ( 0 == e )
    return 0;

  if ( sUTF32_count >= 2 && sUTF32[0] < 0xDC00 && sUTF32[1] >=  0xDC00 && sUTF32[1] < 0xE000 )
  {
    // UTF-16 surrogate pair appears in UTF-32 array
    e->m_error_status |= 4;
    if ( 0 == (4 & e->m_error_mask) )
      return 0; // this error is not masked.

    uhi = sUTF32[0];
    ulo = sUTF32[1];
    *unicode_code_point = (uhi-0xD800)*0x400 + (ulo-0xDC00) + 0x10000;

    return 2; // error masked and reasonable value returned.
  }

  // bogus value
  e->m_error_status |= 16;
  if ( 16 != (16 & e->m_error_mask) || !ON_IsValidUnicodeCodePoint(e->m_error_code_point) )
  {
    // this error is not masked
    return 0;
  }

  *unicode_code_point = e->m_error_code_point;
  return 1; // error masked and e->m_error_code_point returnred.
}

int ON_DecodeSwapByteUTF32(
    const ON__UINT32* sUTF32,
    int sUTF32_count,
    struct ON_UnicodeErrorParameters* e,
    ON__UINT32* unicode_code_point
    )
{
  ON__UINT32 sUTF32swap[2];

  ON_UnicodeErrorParameters local_e = ON_UnicodeErrorParameters::MaskErrors;
  if (nullptr == e)
    e = &local_e;

  if ( 0 != sUTF32 && sUTF32_count > 0 )
  {
    sUTF32swap[0] = SwapBytes32(sUTF32[0]);
    if ( sUTF32_count > 1 )
    {
      // Get up to 2 elements to pass to the unswapped
      // decoder so that masked errors are uniformly
      // handled.
      sUTF32swap[1] = SwapBytes32(sUTF32[1]);
      sUTF32_count = 2;
    }
    sUTF32 = sUTF32swap;
  }

  return ON_DecodeUTF32(sUTF32,sUTF32_count,e,unicode_code_point);
}

int ON_ConvertUTF32ToUTF8(
    int bTestByteOrder,
    const ON__UINT32* sUTF32,
    int sUTF32_count,
    char* sUTF8,
    int sUTF8_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const ON__UINT32** sNextUTF32
    )
{
  int i, k, output_count, bSwapBytes;
  ON__UINT32 u;
  char s[6];
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sUTF32_count && 0 != sUTF32 )
  {
    for ( sUTF32_count = 0; 0 != sUTF32[sUTF32_count]; sUTF32_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sUTF32 || sUTF32_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF32 )
      *sNextUTF32 = sUTF32;
    return 0;
  }

  if ( 0 == sUTF8_count )
  {
    sUTF8 = 0;
    sUTF8_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sUTF8 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF32 )
      *sNextUTF32 = sUTF32;
    return 0;
  }

  bSwapBytes = false;
  if ( bTestByteOrder && sUTF32_count > 0 )
  {
    if ( 0x0000FEFF == sUTF32[0] )
    {
      // skip BOM
      sUTF32_count--;
      sUTF32++;
    }
    else if ( 0xFFFE0000 == sUTF32[0] )
    {
      // skip BOM and swap bytes in rest of sUTF32
      bSwapBytes = true;
      sUTF32_count--;
      sUTF32++;
    }
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  for ( i = 0; i < sUTF32_count; i++ )
  {
    u = bSwapBytes ? SwapBytes32(sUTF32[i]) : sUTF32[i];
    if ( !ON_IsValidUnicodeCodePoint(u) )
    {
      e.m_error_status |= 16;
      if ( 16 != (16 & e.m_error_mask) )
        break;
      if ( !ON_IsValidUnicodeCodePoint(e.m_error_code_point) )
        break;
      u = e.m_error_code_point;
    }
    k = ON_EncodeUTF8(u,s);
    if ( 0 != sUTF8 )
    {
      if ( output_count + k > sUTF8_count )
      {
        e.m_error_status |= 2;
        break;
      }
      memcpy(sUTF8+output_count,s,k*sizeof(sUTF8[0]));
    }
    output_count += k;
  }

  if ( 0 != sUTF8 && output_count < sUTF8_count)
    sUTF8[output_count] = 0;
  if ( sNextUTF32 )
    *sNextUTF32 = sUTF32+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}

int ON_ConvertUTF32ToUTF16(
    int bTestByteOrder,
    const ON__UINT32* sUTF32,
    int sUTF32_count,
    ON__UINT16* sUTF16,
    int sUTF16_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const ON__UINT32** sNextUTF32
    )
{
  int i, k, output_count, bSwapBytes;
  ON__UINT32 u;
  ON__UINT16 w[2];
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sUTF32_count && 0 != sUTF32 )
  {
    for ( sUTF32_count = 0; 0 != sUTF32[sUTF32_count]; sUTF32_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sUTF32 || sUTF32_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF32 )
      *sNextUTF32 = sUTF32;
    return 0;
  }

  if ( 0 == sUTF16_count )
  {
    sUTF16 = 0;
    sUTF16_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sUTF16 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF32 )
      *sNextUTF32 = sUTF32;
    return 0;
  }

  bSwapBytes = false;
  if ( bTestByteOrder && sUTF32_count > 0 )
  {
    if ( 0x0000FEFF == sUTF32[0] )
    {
      // skip BOM
      sUTF32_count--;
      sUTF32++;
    }
    else if ( 0xFFFE0000 == sUTF32[0] )
    {
      // skip BOM and swap bytes in rest of sUTF32
      bSwapBytes = true;
      sUTF32_count--;
      sUTF32++;
    }
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  for ( i = 0; i < sUTF32_count; i++ )
  {
    u = bSwapBytes ? SwapBytes32(sUTF32[i]) : sUTF32[i];
    if ( !ON_IsValidUnicodeCodePoint(u) )
    {
      e.m_error_status |= 16;
      if ( 16 != (16 & e.m_error_mask) )
        break;
      if ( !ON_IsValidUnicodeCodePoint(e.m_error_code_point) )
        break;
      u = e.m_error_code_point;
    }
    k = ON_EncodeUTF16(u,w);
    if ( 0 != sUTF16 )
    {
      if ( output_count + k > sUTF16_count )
      {
        e.m_error_status |= 2;
        break;
      }
      sUTF16[output_count] = w[0];
      if ( 2 == k )
        sUTF16[output_count+1] = w[1];
    }
    output_count += k;
  }

  if ( 0 != sUTF16 && output_count < sUTF16_count)
    sUTF16[output_count] = 0;
  if ( sNextUTF32 )
    *sNextUTF32 = sUTF32+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}


int ON_ConvertUTF32ToUTF32(
    int bTestByteOrder,
    const ON__UINT32* sUTF16,
    int sUTF16_count,
    unsigned int* sUTF32,
    int sUTF32_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const ON__UINT32** sNextUTF16
    )
{
  int i, j, output_count, bSwapBytes;
  ON__UINT32 u;
  struct ON_UnicodeErrorParameters e;

  if ( 0 != error_status )
    *error_status = 0;

  if ( -1 == sUTF16_count && 0 != sUTF16 )
  {
    for ( sUTF16_count = 0; 0 != sUTF16[sUTF16_count]; sUTF16_count++)
    {
      // empty for body
    }
  }

  if ( 0 == sUTF16 || sUTF16_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF16 )
      *sNextUTF16 = sUTF16;
    return 0;
  }

  if ( 0 == sUTF32_count )
  {
    sUTF32 = 0;
    sUTF32_count = 2147483647; // maximum value of a 32-bit signed int
  }
  else if ( 0 == sUTF32 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    if ( sNextUTF16 )
      *sNextUTF16 = sUTF16;
    return 0;
  }

  bSwapBytes = false;
  if ( bTestByteOrder && sUTF16_count > 0 )
  {
    if ( 0x0000FEFF == sUTF16[0] )
    {
      // skip BOM
      sUTF16_count--;
      sUTF16++;
    }
    else if ( 0xFFFE0000 == sUTF16[0])
    {
      // skip BOM and swap bytes in rest of sUTF16
      bSwapBytes = true;
      sUTF16_count--;
      sUTF16++;
    }
  }

  e.m_error_status = 0;
  e.m_error_mask = error_mask;
  e.m_error_code_point = error_code_point;

  output_count = 0;

  if ( bSwapBytes )
  {
    for ( i = 0; i < sUTF16_count; i += j )
    {
      j = ON_DecodeSwapByteUTF32(sUTF16+i,sUTF16_count-i,&e,&u);
      if ( j <= 0 )
        break;
      if ( 0 != sUTF32 )
      {
        if ( output_count >= sUTF32_count )
        {
          e.m_error_status |= 2;
          break;
        }
        sUTF32[output_count] = u;
      }
      output_count++;
    }
  }
  else
  {
    for ( i = 0; i < sUTF16_count; i += j )
    {
      j = ON_DecodeUTF32(sUTF16+i,sUTF16_count-i,&e,&u);
      if ( j <= 0 )
        break;
      if ( 0 != sUTF32 )
      {
        if ( output_count >= sUTF32_count )
        {
          e.m_error_status |= 2;
          break;
        }
        sUTF32[output_count] = u;
      }
      output_count++;
    }
  }

  if ( 0 != sUTF32 && output_count < sUTF32_count)
    sUTF32[output_count] = 0;
  if ( sNextUTF16 )
    *sNextUTF16 = sUTF16+i;
  if ( error_status )
    *error_status = e.m_error_status;
  
  return output_count;
}

int ON_ConvertWideCharToUTF8(
    int bTestByteOrder,
    const wchar_t* sWideChar,
    int sWideChar_count,
    char* sUTF8,
    int sUTF8_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const wchar_t** sNextWideChar
    )
{
  int rc;

  switch(sizeof(sWideChar[0]))
  {
  case sizeof(char):
    // assume wchar_t strings are UTF-8 encoded
    rc = ON_ConvertUTF8ToUTF8(
            bTestByteOrder,
            (const char*)sWideChar,sWideChar_count,
            sUTF8,sUTF8_count,
            error_status,error_mask,error_code_point,
            (const char**)sNextWideChar
            );
    break;

  case sizeof(ON__UINT16):
    // assume wchar_t strings are UTF-16 encoded
    rc = ON_ConvertUTF16ToUTF8(
            bTestByteOrder,
            (const ON__UINT16*)sWideChar,sWideChar_count,
            sUTF8,sUTF8_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT16**)sNextWideChar
            );
    break;

  case sizeof(ON__UINT32):
    // assume wchar_t strings are UTF-32 encoded
    rc = ON_ConvertUTF32ToUTF8(
            bTestByteOrder,
            (const ON__UINT32*)sWideChar,sWideChar_count,
            sUTF8,sUTF8_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT32**)sNextWideChar
            );
    break;

  default:
    rc = 0;
  }

  return rc;
}


int ON_ConvertWideCharToUTF16(
    int bTestByteOrder,
    const wchar_t* sWideChar,
    int sWideChar_count,
    char* sUTF16,
    int sUTF16_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const wchar_t** sNextWideChar
    )
{
  int rc;

  switch(sizeof(sWideChar[0]))
  {
  case sizeof(char):
    // assume wchar_t strings are UTF-8 encoded
    rc = ON_ConvertUTF8ToUTF16(
            bTestByteOrder,
            (const char*)sWideChar,sWideChar_count,
            (ON__UINT16*)sUTF16,sUTF16_count,
            error_status,error_mask,error_code_point,
            (const char**)sNextWideChar
            );
    break;

  case sizeof(ON__UINT16):
    // assume wchar_t strings are UTF-16 encoded
    rc = ON_ConvertUTF16ToUTF16(
            bTestByteOrder,
            (const ON__UINT16*)sWideChar,sWideChar_count,
            (ON__UINT16*)sUTF16,sUTF16_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT16**)sNextWideChar
            );
    break;

  case sizeof(ON__UINT32):
    // assume wchar_t strings are UTF-32 encoded
    rc = ON_ConvertUTF32ToUTF16(
            bTestByteOrder,
            (const ON__UINT32*)sWideChar,sWideChar_count,
            (ON__UINT16*)sUTF16,sUTF16_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT32**)sNextWideChar
            );
    break;

  default:
    rc = 0;
  }

  return rc;
}


int ON_ConvertWideCharToUTF32(
    int bTestByteOrder,
    const wchar_t* sWideChar,
    int sWideChar_count,
    ON__UINT32* sUTF32,
    int sUTF32_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const wchar_t** sNextWideChar
    )
{
  int rc;

  switch(sizeof(sWideChar[0]))
  {
  case sizeof(char):
    // assume wchar_t strings are UTF-8 encoded
    rc = ON_ConvertUTF8ToUTF32(
            bTestByteOrder,
            (const char*)sWideChar,sWideChar_count,
            sUTF32,sUTF32_count,
            error_status,error_mask,error_code_point,
            (const char**)sNextWideChar
            );
    break;

  case sizeof(ON__UINT16):
    // assume wchar_t strings are UTF-16 encoded
    rc = ON_ConvertUTF16ToUTF32(
            bTestByteOrder,
            (const ON__UINT16*)sWideChar,sWideChar_count,
            sUTF32,sUTF32_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT16**)sNextWideChar
            );
    break;

  case sizeof(ON__UINT32):
    // assume wchar_t strings are UTF-32 encoded
    rc = ON_ConvertUTF32ToUTF32(
            bTestByteOrder,
            (const ON__UINT32*)sWideChar,sWideChar_count,
            sUTF32,sUTF32_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT32**)sNextWideChar
            );
     break;

  default:
    rc = 0;
  }

  return rc;
}


int ON_ConvertUTF8ToWideChar(
    int bTestByteOrder,
    const char* sUTF8,
    int sUTF8_count,
    wchar_t* sWideChar,
    int sWideChar_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const char** sNextUTF8
    )
{
  int rc;

  switch(sizeof(sWideChar[0]))
  {
  case sizeof(char):
    // assume wchar_t strings are UTF-8 encoded
    rc = ON_ConvertUTF8ToUTF8(
            bTestByteOrder,
            sUTF8,sUTF8_count,
            (char*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            sNextUTF8
            );
    break;

  case sizeof(ON__UINT16):
    // assume wchar_t strings are UTF-16 encoded
    rc = ON_ConvertUTF8ToUTF16(
            bTestByteOrder,
            sUTF8,sUTF8_count,
            (ON__UINT16*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            sNextUTF8
            );
    break;

  case sizeof(ON__UINT32):
    // assume wchar_t strings are UTF-32 encoded
    rc = ON_ConvertUTF8ToUTF32(
            bTestByteOrder,
            sUTF8,sUTF8_count,
            (ON__UINT32*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            sNextUTF8
            );
    break;

  default:
    if (error_status)
      *error_status = 1;
    if (sNextUTF8)
      *sNextUTF8 = sUTF8;
    rc = 0;
  }

  return rc;
}


int ON_ConvertUTF16ToWideChar(
    int bTestByteOrder,
    const ON__UINT16* sUTF16,
    int sUTF16_count,
    wchar_t* sWideChar,
    int sWideChar_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const ON__UINT16** sNextUTF16
    )
{
  int rc;

  switch(sizeof(sWideChar[0]))
  {
  case sizeof(char):
    // assume wchar_t strings are UTF-8 encoded
    rc = ON_ConvertUTF16ToUTF8(
            bTestByteOrder,
            (const ON__UINT16*)sUTF16,sUTF16_count,
            (char*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT16**)sNextUTF16
            );
    break;

  case sizeof(ON__UINT16):
    // assume wchar_t strings are UTF-16 encoded
    rc = ON_ConvertUTF16ToUTF16(
            bTestByteOrder,
            (const ON__UINT16*)sUTF16,sUTF16_count,
            (ON__UINT16*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT16**)sNextUTF16
            );
    break;

  case sizeof(ON__UINT32):
    // assume wchar_t strings are UTF-32 encoded
    rc = ON_ConvertUTF16ToUTF32(
            bTestByteOrder,
            (const ON__UINT16*)sUTF16,sUTF16_count,
            (ON__UINT32*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT16**)sNextUTF16
            );
    break;

  default:
    if (error_status)
      *error_status = 1;
    if (sNextUTF16)
      *sNextUTF16 = sUTF16;
    rc = 0;
  }

  return rc;
}

int ON_ConvertUTF32ToWideChar(
    int bTestByteOrder,
    const ON__UINT32* sUTF32,
    int sUTF32_count,
    wchar_t* sWideChar,
    int sWideChar_count,
    unsigned int* error_status,
    unsigned int error_mask,
    ON__UINT32 error_code_point,
    const ON__UINT32** sNextUTF32
    )
{
  int rc;

  switch(sizeof(sWideChar[0]))
  {
  case sizeof(char):
    // assume wchar_t strings are UTF-8 encoded
    rc = ON_ConvertUTF32ToUTF8(
            bTestByteOrder,
            (const ON__UINT32*)sUTF32,sUTF32_count,
            (char*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT32**)sNextUTF32
            );
    break;

  case sizeof(ON__UINT16):
    // assume wchar_t strings are UTF-16 encoded
    rc = ON_ConvertUTF32ToUTF16(
            bTestByteOrder,
            (const ON__UINT32*)sUTF32,sUTF32_count,
            (ON__UINT16*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT32**)sNextUTF32
            );
    break;

  case sizeof(ON__UINT32):
    // assume wchar_t strings are UTF-32 encoded
    rc = ON_ConvertUTF32ToUTF32(
            bTestByteOrder,
            (const ON__UINT32*)sUTF32,sUTF32_count,
            (ON__UINT32*)sWideChar,sWideChar_count,
            error_status,error_mask,error_code_point,
            (const ON__UINT32**)sNextUTF32
            );
    break;

  default:
    if (error_status)
      *error_status = 1;
    if (sNextUTF32)
      *sNextUTF32 = sUTF32;
    rc = 0;
  }

  return rc;
}

const ON_wString ON_wString::FromUnicodeCodePoints(
  const ON__UINT32* code_points,
  int code_point_count,
  ON__UINT32 error_code_point
)
{
  const bool bErrorCodePointIsValid = ON_IsValidUnicodeCodePoint(error_code_point);
  
  if (nullptr == code_points)
    return ON_wString::EmptyString;

  if (-1 == code_point_count)
  {
    code_point_count = 0;
    while (0 != code_points[code_point_count])
    {
      if (
        false == bErrorCodePointIsValid
        && false == ON_IsValidUnicodeCodePoint(code_points[code_point_count])
        )
      {
        break;
      }
      code_point_count++;
    }
  }

  if ( code_point_count <= 0 )
    return ON_wString::EmptyString;

  const int bTestByteOrder = false;
  unsigned int error_status = 0;
  const unsigned int error_mask = bErrorCodePointIsValid ? 0xFFFFFFFF : 0;
  int wchar_count = ON_ConvertUTF32ToWideChar(
    bTestByteOrder,
    code_points,
    code_point_count,
    nullptr,
    0,
    &error_status,
    error_mask,
    error_code_point,
    nullptr
  );

  if (wchar_count <= 0)
    return ON_wString::EmptyString;

  ON_wString s;
  const int s_capacity = (wchar_count + 1);
  wchar_t* a = s.ReserveArray((size_t)s_capacity);
  error_status = 0;
  wchar_count = ON_ConvertUTF32ToWideChar(
    bTestByteOrder,
    code_points,
    code_point_count,
    a,
    s_capacity,
    &error_status,
    error_mask,
    error_code_point,
    nullptr
  );
  
  if (wchar_count <= 0)
    return ON_wString::EmptyString;

  s.SetLength(wchar_count);
  return s;
}


////int ON_ConvertWindowsCodePageValueToWideChar(
////  int windows_code_page,
////  ON__UINT32 code_page_character_value,
////  size_t w_capacity,
////  wchar_t* w
////)
////{
////  ON__UINT32 unicode_code_point = ON_UnicodeCodePoint::ON_ReplacementCharacter;
////  ON_UnicodeErrorParameters e;
////  memset(&e, 0, sizeof(e));
////  e.m_error_mask = 0xFF;
////  e.m_error_code_point = ON_UnicodeCodePoint::ON_ReplacementCharacter;
////  ON_DecodeWindowsCodePageValue( windows_code_page, code_page_character_value,  &e,  &unicode_code_point);
////  return ON_EncodeWideChar(unicode_code_point, w_capacity, w);
////}

ON__UINT32 ON_MapRTFcharsetToWindowsCodePage(
  ON__UINT32 rtf_charset,
  ON__UINT32 default_code_page
)
{
  //  From the Microsoft version of the RTF ver 1.9 spec available on MSDN
  //
  //  \fcharsetN:  	Specifies the character set of a font in the font table.If this appears, it implies that bytes in runs 
  //    tagged with the associated \fN are character codes in the codepage corresponding to the charset N.
  //    Use this codepage to convert the codes to Unicode using a function like the Windows MultiByteToWideChar().
  //    See also the \cpgN control word, which, if it appears, supersedes the codepage given by \fcharsetN.Values for N are defined, 
  //    for example, in the Windows header file wingdi.h(e.g., see ANSI_CHARSET) and are repeated here together with the corresponding 
  //    Windows or Mac codepages for convenience:charset	codepage	Windows / Mac name

  //  A font may have a different character set from the character set of the document. For example, the Symbol font has the 
  //    same characters in the same code positions both on the Macintosh and in Windows. Typically, RTF fonts use the code page 
  //    corresponding to the \fcharsetN control word in their \fonttbl description. If the charset doesn’t exist, the codepage 
  //    may be given by the \cpgN control word, for which the code page is N. If the \cpgN does appear, it supersedes the code 
  //    page corresponding to the \fcharsetN. 
  //    For such cases, codepage conversions can be avoided altogether by using the Unicode \uN notation for characters. 
  //    In addition, file names (used in field instructions and in embedded fonts) may not necessarily be the same as the character 
  //    set of the document; the \cpgN control word can change the character set for these file names as well. 
  //  

  ON__UINT32 cp;
  switch (rtf_charset)
  {
  case 0:   cp = 1252; break;         // ANSI
  case 1:   cp = 0; break;            // Default
  case 2:   cp = 42; break;           // Symbol
  case 77:  cp = 10000; break;        // Mac Roman
  case 78:  cp = 10001; break;        // Mac Shift Jis
  case 79:  cp = 10003; break;        // Mac Hangul
  case 80:  cp = 10008; break;        // Mac GB2312
  case 81:  cp = 10002; break;        // Mac Big5
  case 82:  cp = default_code_page; break; // Mac Johab (old)
  case 83:  cp = 10005; break;        // Mac Hebrew
  case 84:  cp = 10004; break;        // Mac Arabic
  case 85:  cp = 10006; break;        // Mac Greek
  case 86:  cp = 10081; break;        // Mac Turkish
  case 87:  cp = 10021; break;        // Mac Thai
  case 88:  cp = 10029; break;        // Mac East Europe
  case 89:  cp = 10007; break;        // Mac Russian
  case 128: cp = 932; break;          // Shift JIS
  case 129: cp = 949; break;          // Hangul (Korean)
  case 130: cp = 1361; break;         // Johab
  case 134: cp = 936; break;          // GB2312
  case 136: cp = 950; break;          // Big5
  case 161: cp = 1253; break;         // Greek
  case 162: cp = 1254; break;         // Turkish
  case 163: cp = 1258; break;         // Vietnamese
  case 177: cp = 1255; break;         // Hebrew
  case 178: cp = 1256; break;         // Arabic 
  case 179: cp = default_code_page; break; // Arabic Traditional (old)
  case 180: cp = default_code_page; break; // Arabic user (old)
  case 181: cp = default_code_page; break; // Hebrew user (old)
  case 186: cp = 1257; break;         // Baltic
  case 204: cp = 1251; break;         // Russian
  case 222: cp = 874; break;          // Thai
  case 238: cp = 1250; break;         // Eastern European
  case 254: cp = 437; break;          // PC 437
  case 255: cp = 850; break;          // OEM
  default: cp = default_code_page; break;
  }
  return cp;
}

static int ON_Internal_ConvertMSSBCPToWideChar(
  const ON__UINT32* sb_code_page_0x80_to_0xFF_to_unicode,
  const char* sMBCS,
  int sMBCS_count,
  wchar_t* sWideChar,
  int sWideChar_capacity,
  unsigned int* error_status
)
{
  wchar_t* sWideCharMax
    = (sWideChar_capacity > 0 && nullptr != sWideChar)
    ? sWideChar + sWideChar_capacity
    : nullptr;
  if (nullptr == sWideCharMax)
  {
    sWideChar = nullptr;
    sWideChar_capacity = 0;
  }
  else
  {
    sWideChar[0] = 0;
  }
  if (nullptr != error_status)
    *error_status = 0;

  unsigned int e = 0;
  if (nullptr == sMBCS || sMBCS_count < 0)
    sMBCS_count = 0;
  wchar_t* s = sWideChar;
  wchar_t w_buffer[8];
  int rc = 0;

  for (int i = 0; i < sMBCS_count; i++)
  {
    const ON__UINT32 c = (unsigned char)sMBCS[i];
    ON__UINT32 unicode_code_point;
    if (c < 0x80)
      unicode_code_point = c;
    else 
    {
      if (c <= 0xFF && nullptr != sb_code_page_0x80_to_0xFF_to_unicode )
      {
        unicode_code_point = sb_code_page_0x80_to_0xFF_to_unicode[c - 0x80];
        if (0 == ON_IsValidUnicodeCodePoint(unicode_code_point))
          unicode_code_point = ON_UnicodeCodePoint::ON_ReplacementCharacter;
      }
      else
        unicode_code_point = ON_UnicodeCodePoint::ON_ReplacementCharacter;
      if ( ON_UnicodeCodePoint::ON_ReplacementCharacter == unicode_code_point )
        e |= 16;
    }
    const int w_count = ON_EncodeWideChar(unicode_code_point, sizeof(w_buffer)/sizeof(w_buffer[0]), w_buffer);
    if (w_count <= 0)
    {
      e |= 16;
      continue;
    }
    rc += w_count;
    if (s == nullptr)
      continue;
    wchar_t* s1 = s + w_count;
    if (s1 > sWideCharMax)
    {
      e |= 2;
      continue;
    }
    const wchar_t* w = w_buffer;
    while (s < s1)
      *s++ = *w++;
  }
  
  while (s < sWideCharMax)
  {
    *s++ = 0;
  }

  if (nullptr != error_status)
    *error_status = e;

  return rc;
}

int ON_ConvertMSMBCPToWideChar(
    ON__UINT32 windows_code_page,
    const char* sMBCS,
    int sMBCS_count,
    wchar_t* sWideChar,
    int sWideChar_capacity,
    unsigned int* error_status
    )
{
  if ( 0 != error_status )
    *error_status = 0;

  bool bNullTerminated = false;
  if ( -1 == sMBCS_count && nullptr != sMBCS )
  {
    for ( sMBCS_count = 0; true; sMBCS_count++)
    {
      if (0 == sMBCS[sMBCS_count])
      {
        bNullTerminated = true;
        break;
      }
    }
  }

  if ( nullptr == sMBCS || sMBCS_count < 0 )
  {
    if ( 0 != error_status )
      *error_status |= 1;
    return 0;
  }

  if ( 0 == sMBCS_count )
  {
    return 0;
  }

  if (sWideChar_capacity <= 0)
  {
    sWideChar_capacity = 0;
    sWideChar = nullptr;
  }
  else if (nullptr == sWideChar)
  {
    sWideChar_capacity = 0;
  }
  else
  {
    sWideChar[0] = 0;
  }

  const char* c = sMBCS;
  const char* c1 = c + sMBCS_count;
  wchar_t* w = sWideChar;
  wchar_t* w1 = w + sWideChar_capacity;
  while (c < c1 && *c >= 0 && *c <= 127)
  {
    if (nullptr != w)
    {
      if (w >= w1)
        break;
      *w++ = (wchar_t)*c;
    }
    c++;
  }
  if (c == c1)
  {
    if (w < w1)
      *w = 0;
    return sMBCS_count;
  }

  const ON__UINT32* sb_code_page_0x80_to_0xFF_to_unicode = ON_MSSBCP_0x80_0xFF_Unicode(windows_code_page);
  if (nullptr != sb_code_page_0x80_to_0xFF_to_unicode)
  {
    // fast platform independent single byte code page conversion built into opennurbs
    return ON_Internal_ConvertMSSBCPToWideChar(
      sb_code_page_0x80_to_0xFF_to_unicode,
      sMBCS,
      sMBCS_count,
      sWideChar,
      sWideChar_capacity,
      error_status
    );
  }

#if defined(ON_RUNTIME_WIN)
  // Starting with Windows Vista, the function does not drop illegal code points when dwFlags=0.
  // It replaces illegal sequences with U+FFFD (encoded as appropriate for the specified codepage).
  DWORD dwFlags = 0; 
  int sWideChar_count = ::MultiByteToWideChar(windows_code_page, dwFlags, sMBCS, sMBCS_count, sWideChar, sWideChar_capacity);
  if (sWideChar_count < 0)
    sWideChar_count = 0;
  if (nullptr == sWideChar)
    return sWideChar_count;

  for (int i = 0; i < sWideChar_count; i++)
  {
    if (0 == sWideChar[i])
    {
      sWideChar_count = i;
      break;
    }
    if ( ON_wString::ReplacementCharacter == sWideChar[i] )
    {
      if ( nullptr != error_status)
        *error_status |= 16;
    }
  }
  if (sWideChar_count < sWideChar_capacity)
    sWideChar[sWideChar_count] = 0;
  return sWideChar_count;

#elif defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE)
  CFStringEncoding cfEncoding = CFStringConvertWindowsCodepageToEncoding(windows_code_page);
  if (cfEncoding == kCFStringEncodingInvalidId)
  {
    ON_ERROR("No Apple CFStringEncoding support for this value of windows_code_page");
    return 0;
  }

  char* szMBCS = nullptr;
  if (false == bNullTerminated)
  {
    szMBCS = (char*)onmalloc((sMBCS_count + 1) * sizeof(szMBCS[0]));
    memcpy(szMBCS, sMBCS, sMBCS_count * sizeof(szMBCS[0]));
    szMBCS[sMBCS_count] = 0;
    sMBCS = szMBCS;
  }

  int sWideChar_count = 0;

  for (;;)
  {
    NSStringEncoding nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
    NSString* str = [NSString stringWithCString : sMBCS encoding : nsEncoding];
    if (nullptr == str)
    {
      ON_ERROR("[NSString stringWithCString: sMBCS encoding: nsEncoding] failed.");
      break;
    }
    const int len = (int)(str.length);
    if (len <= 0)
    {
      break;
    }

    for (int i = 0; i < len; i++)
    {
      ON__UINT32 unicode_code_point = 0;
      const int u1 = [str characterAtIndex : i];
      if (u1 >= 0xD800U && u1 < 0xDC00 && i+1 < len)
      {
        const int u2 = [str characterAtIndex : (i+1)];
        unicode_code_point = ON_DecodeUTF16SurrogatePair((unsigned int)u1, (unsigned int)u2, ON_wString::ReplacementCharacter);
        if (ON_wString::ReplacementCharacter != unicode_code_point)
          i++;
      }
      else
      {
        unicode_code_point = (unsigned int)u1;
      }
      if (
        false == ON_IsValidUnicodeCodePoint(unicode_code_point)
        || ON_wString::ReplacementCharacter == unicode_code_point
        )
      {
        unicode_code_point = ON_wString::ReplacementCharacter;
        if (nullptr != error_status)
          *error_status |= 16;
      }
      if (nullptr != sWideChar && sWideChar_capacity > 0)
      {
        if (sWideChar_count < sWideChar_capacity)
          sWideChar[sWideChar_count] = (wchar_t)unicode_code_point;
        else
        {
          // continue counting but no more output to sWideChar[]
          sWideChar[sWideChar_capacity-1] = 0;
          sWideChar = nullptr;
          sWideChar_capacity = 0;
          if (nullptr != error_status)
            *error_status |= 2;
        }
      }
      sWideChar_count++;
    }

    break;
  }

  if (nullptr != szMBCS)
    onfree(szMBCS);

  if (nullptr != sWideChar && sWideChar_count < sWideChar_capacity)
  {
    sWideChar[sWideChar_count] = 0;
    sWideChar[sWideChar_capacity-1] = 0;
  }

  return sWideChar_count;

#else
  // Add support for other platforms as needed.
  return 0;
#endif

}
