text: // This file is part of Blend2D project <https://blend2d.com>
  //
  // See blend2d.h or LICENSE.md for license and copyright information
  // SPDX-License-Identifier: Zlib
  
  #ifndef BLEND2D_UNICODE_P_H_INCLUDED
  #define BLEND2D_UNICODE_P_H_INCLUDED
  
  #include "support/intops_p.h"
  #include "support/memops_p.h"
  
  //! \cond INTERNAL
  //! \addtogroup blend2d_internal
  //! \{
  
  // ============================================================================
  // [Unicode Data]
  // ============================================================================
  
  BL_HIDDEN extern const uint8_t blUtf8SizeData[256];
  
  // ============================================================================
  // [Unicode Constants]
  // ============================================================================
  
  //! Special unicode characters.
  enum BLCharCode : uint32_t {
    BL_CHAR_BOM                 = 0x00FEFFu,   //!< Native Byte-Order-Mark.
    BL_CHAR_MAX                 = 0x10FFFFu,   //!< Last code-point.
  
    BL_CHAR_REPLACEMENT         = 0x00FFFDu,   //!< Replacement character.
  
    BL_CHAR_FVS1                = 0x00180Bu,   //!< First char in Mongolian 'free variation selectors' FVS1..FVS3.
    BL_CHAR_FVS3                = 0x00180Du,   //!< Last char in Mongolian 'free variation selectors' FVS1..FVS3.
  
    BL_CHAR_VS1                 = 0x00FE00u,   //!< First char in 'variation selectors' VS1..VS16.
    BL_CHAR_VS16                = 0x00FE0Fu,   //!< Last char in 'variation selectors' VS1..VS16.
  
    BL_CHAR_VS17                = 0x0E0100u,   //!< First char in 'variation selectors supplement' VS17..VS256.
    BL_CHAR_VS256               = 0x0E01EFu,   //!< Last char in 'variation selectors supplement' VS17..VS256.
  
    BL_CHAR_SURROGATE_FIRST     = 0x00D800u,   //!< First surrogate code-point.
    BL_CHAR_SURROGATE_LAST      = 0x00DFFFu,   //!< Last surrogate code-point.
  
    BL_CHAR_HI_SURROGATE_FIRST  = 0x00D800u,   //!< First high-surrogate code-point
    BL_CHAR_HI_SURROGATE_LAST   = 0x00DBFFu,   //!< Last high-surrogate code-point
  
    BL_CHAR_LO_SURROGATE_FIRST  = 0x00DC00u,   //!< First low-surrogate code-point
    BL_CHAR_LO_SURROGATE_LAST   = 0x00DFFFu    //!< Last low-surrogate code-point
  };
  
  //! Flags that can be used to parametrize unicode I/O iterators.
  enum BLUnicodeIOFlags : uint32_t {
    BL_UNICODE_IO_UNALIGNED     = 0x00000001u,
    BL_UNICODE_IO_BYTE_SWAP     = 0x00000002u,
    BL_UNICODE_IO_STRICT        = 0x00000004u,
    BL_UNICODE_IO_CALC_INDEX    = 0x00000008u,
  
    BL_UNICODE_IO_BYTE_ORDER_LE = BL_BYTE_ORDER_NATIVE == BL_BYTE_ORDER_LE ? 0 : BL_UNICODE_IO_BYTE_SWAP,
    BL_UNICODE_IO_BYTE_ORDER_BE = BL_BYTE_ORDER_NATIVE == BL_BYTE_ORDER_BE ? 0 : BL_UNICODE_IO_BYTE_SWAP
  };
  
  // ============================================================================
  // [Unicode Utilities]
  // ============================================================================
  
  namespace {
  
  template<typename T>
  BL_NODISCARD
  BL_INLINE uint32_t blUtf8CharSize(const T& c) noexcept {
    typedef typename std::make_unsigned<T>::type U;
    return blUtf8SizeData[U(c)];
  }
  
  template<typename T>
  BL_NODISCARD
  BL_INLINE bool blIsValidUtf8(const T& c) noexcept {
    typedef typename std::make_unsigned<T>::type U;
    return U(c) < 128 || (U(c) - U(194) < U(245 - 194));
  }
  
  template<typename T>
  BL_NODISCARD
  constexpr bool blIsAsciiAlpha(const T& x) noexcept { return T(x | 0x20) >= T('a') && T(x | 0x20) <= T('z'); }
  
  template<typename T>
  BL_NODISCARD
  constexpr bool blIsAsciiDigit(const T& x) noexcept { return x >= T('0') && x <= T('9'); }
  
  template<typename T>
  BL_NODISCARD
  constexpr bool blIsAsciiAlnum(const T& x) noexcept { return blIsAsciiAlpha(x) || (x >= T('0') && x <= T('9')); }
  
  template<typename T>
  BL_NODISCARD
  constexpr T blAsciiToLower(const T& x) noexcept { return x >= T('A') && x <= T('Z') ? T(x |  T(0x20)) : x; }
  
  template<typename T>
  BL_NODISCARD
  constexpr T blAsciiToUpper(const T& x) noexcept { return x >= T('a') && x <= T('z') ? T(x & ~T(0x20)) : x; }
  
  //! Tests whether the unicode character `uc` is high or low surrogate.
  template<typename T>
  BL_NODISCARD
  constexpr bool blIsSurrogate(const T& uc) noexcept { return uc >= BL_CHAR_SURROGATE_FIRST && uc <= BL_CHAR_SURROGATE_LAST; }
  
  //! Tests whether the unicode character `uc` is a high (leading) surrogate.
  template<typename T>
  BL_NODISCARD
  constexpr bool blIsHiSurrogate(const T& uc) noexcept { return uc >= BL_CHAR_HI_SURROGATE_FIRST && uc <= BL_CHAR_HI_SURROGATE_LAST; }
  
  //! Tests whether the unicode character `uc` is a low (trailing) surrogate.
  template<typename T>
  BL_NODISCARD
  constexpr bool blIsLoSurrogate(const T& uc) noexcept { return uc >= BL_CHAR_LO_SURROGATE_FIRST && uc <= BL_CHAR_LO_SURROGATE_LAST; }
  
  //! Composes `hi` and `lo` surrogates into a unicode code-point.
  template<typename T>
  BL_NODISCARD
  constexpr uint32_t blCharFromSurrogate(const T& hi, const T& lo) noexcept {
    return (uint32_t(hi) << 10) + uint32_t(lo) - uint32_t((BL_CHAR_SURROGATE_FIRST << 10) + BL_CHAR_LO_SURROGATE_FIRST - 0x10000u);
  }
  
  //! Decomposes a unicode code-point into `hi` and `lo` surrogates.
  template<typename T>
  BL_INLINE void blCharToSurrogate(uint32_t uc, T& hi, T& lo) noexcept {
    uc -= 0x10000u;
    hi = T(BL_CHAR_HI_SURROGATE_FIRST | (uc >> 10));
    lo = T(BL_CHAR_LO_SURROGATE_FIRST | (uc & 0x3FFu));
  }
  
  } // {anonymous}
  
  // ============================================================================
  // [Unicode Validation]
  // ============================================================================
  
  struct BLUnicodeValidationState {
    size_t utf8Index;
    size_t utf16Index;
    size_t utf32Index;
  
    BL_INLINE void reset() noexcept {
      utf8Index = 0;
      utf16Index = 0;
      utf32Index = 0;
    }
  
    BL_INLINE bool hasSMP() const noexcept { return utf16Index != utf32Index; }
  };
  
  BL_HIDDEN BLResult blValidateUnicode(const void* data, size_t sizeInBytes, BLTextEncoding encoding, BLUnicodeValidationState& state) noexcept;
  
  static BL_INLINE BLResult blValidateUtf8(const char* data, size_t size, BLUnicodeValidationState& state) noexcept {
    return blValidateUnicode(data, size, BL_TEXT_ENCODING_UTF8, state);
  }
  
  static BL_INLINE BLResult blValidateUtf16(const uint16_t* data, size_t size, BLUnicodeValidationState& state) noexcept {
    return blValidateUnicode(data, size * 2u, BL_TEXT_ENCODING_UTF16, state);
  }
  
  static BL_INLINE BLResult blValidateUtf32(const uint32_t* data, size_t size, BLUnicodeValidationState& state) noexcept {
    return blValidateUnicode(data, size * 4u, BL_TEXT_ENCODING_UTF32, state);
  }
  
  // ============================================================================
  // [Conversion]
  // ============================================================================
  
  struct BLUnicodeConversionState {
    size_t dstIndex;
    size_t srcIndex;
  
    BL_INLINE void reset() noexcept {
      this->dstIndex = 0;
      this->srcIndex = 0;
    }
  };
  
  //! Converts a string from one encoding to another.
  //!
  //! Convert function works at a byte level. All sizes here are including those
  //! stored in a `BLUnicodeConversionState` are byte entities. So for example to convert
  //! a single UTF-16 BMP character the source size must be 2, etc...
  BL_HIDDEN BLResult blConvertUnicode(
    void* dst, size_t dstSizeInBytes, uint32_t dstEncoding,
    const void* src, size_t srcSizeInBytes, uint32_t srcEncoding, BLUnicodeConversionState& state) noexcept;
  
  // ============================================================================
  // [BLUtf8Reader]
  // ============================================================================
  
  //! UTF-8 reader.
  class BLUtf8Reader {
  public:
    enum : uint32_t { kCharSize = 1 };
  
    //! Current pointer.
    const char* _ptr;
    //! End of input.
    const char* _end;
    //! `index() - _utf32IndexSubtract` yields the current `utf32Index`.
    size_t _utf32IndexSubtract;
    //! Number of surrogates is required to calculate `utf16Index`.
    size_t _utf16SurrogateCount;
  
    BL_INLINE BLUtf8Reader(const void* data, size_t byteSize) noexcept {
      reset(data, byteSize);
    }
  
    BL_INLINE void reset(const void* data, size_t byteSize) noexcept {
      _ptr = static_cast<const char*>(data);
      _end = static_cast<const char*>(data) + byteSize;
      _utf32IndexSubtract = 0;
      _utf16SurrogateCount = 0;
    }
  
    BL_NODISCARD
    BL_INLINE bool hasNext() const noexcept { return _ptr != _end; }
  
    BL_NODISCARD
    BL_INLINE size_t remainingByteSize() const noexcept { return (size_t)(_end - _ptr); }
  
    BL_NODISCARD
    BL_INLINE size_t byteIndex(const void* start) const noexcept { return (size_t)(_ptr - static_cast<const char*>(start)); }
  
    BL_NODISCARD
    BL_INLINE size_t utf8Index(const void* start) const noexcept { return byteIndex(start); }
  
    BL_NODISCARD
    BL_INLINE size_t utf16Index(const void* start) const noexcept { return utf32Index(start) + _utf16SurrogateCount; }
  
    BL_NODISCARD
    BL_INLINE size_t utf32Index(const void* start) const noexcept { return byteIndex(start) - _utf32IndexSubtract; }
  
    BL_NODISCARD
    BL_INLINE size_t nativeIndex(const void* start) const noexcept { return utf8Index(start); }
  
    template<uint32_t Flags = 0>
    BL_INLINE BLResult next(uint32_t& uc) noexcept {
      size_t ucSizeInBytes;
      return next<Flags>(uc, ucSizeInBytes);
    }
  
    template<uint32_t Flags = 0>
    BL_INLINE BLResult next(uint32_t& uc, size_t& ucSizeInBytes) noexcept {
      BL_ASSERT(hasNext());
  
      uc = BLMemOps::readU8(_ptr);
      ucSizeInBytes = 1;
  
      _ptr++;
      if (uc < 0x80u) {
        // 1-Byte UTF-8 Sequence -> [0x00..0x7F].
        // ...nothing to do...
      }
      else {
        // Start of MultiByte.
        const uint32_t kMultiByte = 0xC2u;
  
        uc -= kMultiByte;
        if (uc < 0xE0u - kMultiByte) {
          // 2-Byte UTF-8 Sequence -> [0x80-0x7FF].
          _ptr++;
          ucSizeInBytes = 2;
  
          // Truncated input.
          if (BL_UNLIKELY(_ptr > _end))
            goto TruncatedString;
  
          // All consecutive bytes must be '10xxxxxx'.
          uint32_t b1 = BLMemOps::readU8(_ptr - 1) ^ 0x80u;
          uc = ((uc + kMultiByte - 0xC0u) << 6) + b1;
  
          if (BL_UNLIKELY(b1 > 0x3Fu))
            goto InvalidString;
  
          // 2-Byte UTF-8 maps to one UTF-16 or UTF-32 code-point, so subtract 1.
          if (Flags & BL_UNICODE_IO_CALC_INDEX) _utf32IndexSubtract += 1;
        }
        else if (uc < 0xF0u - kMultiByte) {
          // 3-Byte UTF-8 Sequence -> [0x800-0xFFFF].
          _ptr += 2;
          ucSizeInBytes = 3;
  
          // Truncated input.
          if (BL_UNLIKELY(_ptr > _end))
            goto TruncatedString;
  
          uint32_t b1 = BLMemOps::readU8(_ptr - 2) ^ 0x80u;
          uint32_t b2 = BLMemOps::readU8(_ptr - 1) ^ 0x80u;
          uc = ((uc + kMultiByte - 0xE0u) << 12) + (b1 << 6) + b2;
  
          // 1. All consecutive bytes must be '10xxxxxx'.
          // 2. Refuse overlong UTF-8.
          if (BL_UNLIKELY((b1 | b2) > 0x3Fu || uc < 0x800u))
            goto InvalidString;
  
          // 3-Byte UTF-8 maps to one UTF-16 or UTF-32 code-point, so subtract 2.
          if (Flags & BL_UNICODE_IO_CALC_INDEX) _utf32IndexSubtract += 2;
        }
        else {
          // 4-Byte UTF-8 Sequence -> [0x010000-0x10FFFF].
          _ptr += 3;
          ucSizeInBytes = 4;
  
          // Truncated input.
          if (BL_UNLIKELY(_ptr > _end)) {
            // If this happens we want to report a correct error, bytes 0xF5
            // and above are always invalid and normally caught later.
            if (uc >= 0xF5u - kMultiByte)
              goto InvalidString;
            else
              goto TruncatedString;
          }
  
          uint32_t b1 = BLMemOps::readU8(_ptr - 3) ^ 0x80u;
          uint32_t b2 = BLMemOps::readU8(_ptr - 2) ^ 0x80u;
          uint32_t b3 = BLMemOps::readU8(_ptr - 1) ^ 0x80u;
          uc = ((uc + kMultiByte - 0xF0u) << 18) + (b1 << 12) + (b2 << 6) + b3;
  
          // 1. All consecutive bytes must be '10xxxxxx'.
          // 2. Refuse overlong UTF-8.
          // 3. Make sure the final character is <= U+10FFFF.
          if (BL_UNLIKELY((b1 | b2 | b3) > 0x3Fu || uc < 0x010000u || uc > BL_CHAR_MAX))
            goto InvalidString;
  
          // 4-Byte UTF-8 maps to one UTF-16 or UTF-32 code-point, so subtract 3.
          if (Flags & BL_UNICODE_IO_CALC_INDEX) _utf32IndexSubtract += 3;
          if (Flags & BL_UNICODE_IO_CALC_INDEX) _utf16SurrogateCount += 1;
        }
      }
      return BL_SUCCESS;
  
  InvalidString:
      _ptr -= ucSizeInBytes;
      return blSetError(BL_ERROR_INVALID_STRING);
  
  TruncatedString:
      _ptr -= ucSizeInBytes;
      return blSetError(BL_ERROR_DATA_TRUNCATED);
    }
  
    BL_INLINE void skipOneUnit() noexcept {
      BL_ASSERT(hasNext());
      _ptr++;
    }
  
    template<uint32_t Flags = 0>
    BL_NODISCARD
    BL_INLINE BLResult validate() noexcept {
      BLResult result = BL_SUCCESS;
      while (hasNext()) {
        uint32_t uc;
        result = next<Flags>(uc);
        if (result)
          break;
      }
      return result;
    }
  };
  
  // ============================================================================
  // [BLUtf16Reader]
  // ============================================================================
  
  //! UTF-16 reader.
  class BLUtf16Reader {
  public:
    enum : uint32_t { kCharSize = 2 };
  
    const char* _ptr;
    const char* _end;
  
    size_t _utf8IndexAdd;
    size_t _utf16SurrogateCount;
  
    BL_INLINE BLUtf16Reader(const void* data, size_t byteSize) noexcept {
      reset(data, byteSize);
    }
  
    // --------------------------------------------------------------------------
    // [Reset]
    // --------------------------------------------------------------------------
  
    BL_INLINE void reset(const void* data, size_t byteSize) noexcept {
      _ptr = static_cast<const char*>(data);
      _end = static_cast<const char*>(data) + BLIntOps::alignDown(byteSize, 2);
      _utf8IndexAdd = 0;
      _utf16SurrogateCount = 0;
    }
  
    // --------------------------------------------------------------------------
    // [Accessors]
    // --------------------------------------------------------------------------
  
    BL_NODISCARD
    BL_INLINE bool hasNext() const noexcept { return _ptr != _end; }
  
    BL_NODISCARD
    BL_INLINE size_t remainingByteSize() const noexcept { return (size_t)(_end - _ptr); }
  
    BL_NODISCARD
    BL_INLINE size_t byteIndex(const void* start) const noexcept { return (size_t)(_ptr - static_cast<const char*>(start)); }
  
    BL_NODISCARD
    BL_INLINE size_t utf8Index(const void* start) const noexcept { return utf16Index(start) + _utf8IndexAdd; }
  
    BL_NODISCARD
    BL_INLINE size_t utf16Index(const void* start) const noexcept { return byteIndex(start) / 2u; }
  
    BL_NODISCARD
    BL_INLINE size_t utf32Index(const void* start) const noexcept { return utf16Index(start) - _utf16SurrogateCount; }
  
    BL_NODISCARD
    BL_INLINE size_t nativeIndex(const void* start) const noexcept { return utf16Index(start); }
  
    // --------------------------------------------------------------------------
    // [Iterator]
    // --------------------------------------------------------------------------
  
    template<uint32_t Flags = 0>
    BL_INLINE BLResult next(uint32_t& uc) noexcept {
      size_t ucSizeInBytes;
      return next<Flags>(uc, ucSizeInBytes);
    }
  
    template<uint32_t Flags = 0>
    BL_INLINE BLResult next(uint32_t& uc, size_t& ucSizeInBytes) noexcept {
      BL_ASSERT(hasNext());
  
      uc = readU16<Flags>(_ptr);
      _ptr += 2;
  
      if (blIsSurrogate(uc)) {
        if (BL_LIKELY(blIsHiSurrogate(uc))) {
          if (BL_LIKELY(_ptr != _end)) {
            uint32_t lo = readU16<Flags>(_ptr);
            if (BL_LIKELY(blIsLoSurrogate(lo))) {
              uc = blCharFromSurrogate(uc, lo);
              _ptr += 2;
  
              // Add two to `_utf8IndexAdd` as two surrogates count as 2, so we
              // have to add 2 more to have UTF-8 length of a valid surrogate.
              if (Flags & BL_UNICODE_IO_CALC_INDEX) _utf8IndexAdd += 2;
              if (Flags & BL_UNICODE_IO_CALC_INDEX) _utf16SurrogateCount += 1;
  
              ucSizeInBytes = 4;
              return BL_SUCCESS;
            }
            else {
              if (Flags & BL_UNICODE_IO_STRICT)
                goto InvalidString;
            }
          }
          else {
            if (Flags & BL_UNICODE_IO_STRICT)
              goto TruncatedString;
          }
        }
        else {
          if (Flags & BL_UNICODE_IO_STRICT)
            goto InvalidString;
        }
      }
  
      // Either not surrogate or fallback in non-strict mode.
      if (Flags & BL_UNICODE_IO_CALC_INDEX)
        _utf8IndexAdd += size_t(uc >= 0x0080u) + size_t(uc >= 0x0800u);
  
      ucSizeInBytes = 2;
      return BL_SUCCESS;
  
  InvalidString:
      _ptr -= 2;
      return blSetError(BL_ERROR_INVALID_STRING);
  
  TruncatedString:
      _ptr -= 2;
      return blSetError(BL_ERROR_DATA_TRUNCATED);
    }
  
    BL_INLINE void skipOneUnit() noexcept {
      BL_ASSERT(hasNext());
      _ptr += 2;
    }
  
    // --------------------------------------------------------------------------
    // [Validator]
    // --------------------------------------------------------------------------
  
    template<uint32_t Flags = 0>
    BL_NODISCARD
    BL_INLINE BLResult validate() noexcept {
      BLResult result = BL_SUCCESS;
      while (hasNext()) {
        uint32_t uc;
        result = next<Flags>(uc);
        if (result)
          break;
      }
      return result;
    }
  
    // --------------------------------------------------------------------------
    // [Utilities]
    // --------------------------------------------------------------------------
  
    template<uint32_t Flags = 0>
    BL_NODISCARD
    static BL_INLINE uint32_t readU16(const char* ptr) noexcept {
      constexpr uint32_t kByteOrder = Flags & BL_UNICODE_IO_BYTE_SWAP ? BL_BYTE_ORDER_SWAPPED : BL_BYTE_ORDER_NATIVE;
      constexpr uint32_t kAlignment = Flags & BL_UNICODE_IO_UNALIGNED ? 1 : 2;
      return BLMemOps::readU16<kByteOrder, kAlignment>(ptr);
    }
  };
  
  // ============================================================================
  // [BLUtf32Reader]
  // ============================================================================
  
  //! UTF-32 reader.
  class BLUtf32Reader {
  public:
    enum : uint32_t { kCharSize = 4 };
  
    const char* _ptr;
    const char* _end;
  
    size_t _utf8IndexAdd;
    size_t _utf16SurrogateCount;
  
    BL_INLINE BLUtf32Reader(const void* data, size_t byteSize) noexcept {
      reset(data, byteSize);
    }
  
    BL_INLINE void reset(const void* data, size_t byteSize) noexcept {
      _ptr = static_cast<const char*>(data);
      _end = static_cast<const char*>(data) + BLIntOps::alignDown(byteSize, 4);
      _utf8IndexAdd = 0;
      _utf16SurrogateCount = 0;
    }
  
    BL_NODISCARD
    BL_INLINE bool hasNext() const noexcept { return _ptr != _end; }
  
    BL_NODISCARD
    BL_INLINE size_t remainingByteSize() const noexcept { return (size_t)(_end - _ptr); }
  
    BL_NODISCARD
    BL_INLINE size_t byteIndex(const void* start) const noexcept { return (size_t)(_ptr - static_cast<const char*>(start)); }
  
    BL_NODISCARD
    BL_INLINE size_t utf8Index(const void* start) const noexcept { return utf32Index(start) + _utf16SurrogateCount + _utf8IndexAdd; }
  
    BL_NODISCARD
    BL_INLINE size_t utf16Index(const void* start) const noexcept { return utf32Index(start) + _utf16SurrogateCount; }
  
    BL_NODISCARD
    BL_INLINE size_t utf32Index(const void* start) const noexcept { return byteIndex(start) / 4u; }
  
    BL_NODISCARD
    BL_INLINE size_t nativeIndex(const void* start) const noexcept { return utf32Index(start); }
  
    template<uint32_t Flags = 0>
    BL_INLINE BLResult next(uint32_t& uc) noexcept {
      size_t ucSizeInBytes;
      return next<Flags>(uc, ucSizeInBytes);
    }
  
    template<uint32_t Flags = 0>
    BL_INLINE BLResult next(uint32_t& uc, size_t& ucSizeInBytes) noexcept {
      BL_ASSERT(hasNext());
  
      uc = readU32<Flags>(_ptr);
      if (BL_UNLIKELY(uc > BL_CHAR_MAX))
        return blSetError(BL_ERROR_INVALID_STRING);
  
      if (Flags & BL_UNICODE_IO_STRICT) {
        if (BL_UNLIKELY(blIsSurrogate(uc)))
          return blSetError(BL_ERROR_INVALID_STRING);
      }
  
      if (Flags & BL_UNICODE_IO_CALC_INDEX) _utf8IndexAdd += size_t(uc >= 0x800u) + size_t(uc >= 0x80u);
      if (Flags & BL_UNICODE_IO_CALC_INDEX) _utf16SurrogateCount += size_t(uc >= 0x10000u);
  
      _ptr += 4;
      ucSizeInBytes = 4;
      return BL_SUCCESS;
    }
  
    BL_INLINE void skipOneUnit() noexcept {
      BL_ASSERT(hasNext());
      _ptr += 4;
    }
  
    template<uint32_t Flags = 0>
    BL_NODISCARD
    BL_INLINE BLResult validate() noexcept {
      BLResult result = BL_SUCCESS;
      while (hasNext()) {
        uint32_t uc;
        result = next<Flags>(uc);
        if (result)
          break;
      }
      return result;
    }
  
    template<uint32_t Flags = 0>
    BL_NODISCARD
    static BL_INLINE uint32_t readU32(const char* ptr) noexcept {
      constexpr uint32_t kByteOrder = Flags & BL_UNICODE_IO_BYTE_SWAP ? BL_BYTE_ORDER_SWAPPED : BL_BYTE_ORDER_NATIVE;
      constexpr uint32_t kAlignment = Flags & BL_UNICODE_IO_UNALIGNED ? 1 : 4;
      return BLMemOps::readU32<kByteOrder, kAlignment>(ptr);
    }
  };
  
  // ============================================================================
  // [BLUtf8Writer]
  // ============================================================================
  
  //! UTF8 writer.
  class BLUtf8Writer {
  public:
    typedef char CharType;
  
    char* _ptr;
    char* _end;
  
    BL_INLINE BLUtf8Writer(char* dst, size_t size) noexcept {
      reset(dst, size);
    }
  
    BL_INLINE void reset(char* dst, size_t size) noexcept {
      _ptr = dst;
      _end = dst + size;
    }
  
    BL_NODISCARD
    BL_INLINE size_t index(const char* start) const noexcept { return (size_t)(_ptr - start); }
  
    BL_NODISCARD
    BL_INLINE bool atEnd() const noexcept { return _ptr == _end; }
  
    BL_NODISCARD
    BL_INLINE size_t remainingSize() const noexcept { return (size_t)(_end - _ptr); }
  
    BL_INLINE BLResult write(uint32_t uc) noexcept {
      if (uc <= 0x7F)
        return writeByte(uc);
      else if (uc <= 0x7FFu)
        return write2Bytes(uc);
      else if (uc <= 0xFFFFu)
        return write3Bytes(uc);
      else
        return write4Bytes(uc);
    }
  
    BL_INLINE BLResult writeUnsafe(uint32_t uc) noexcept {
      if (uc <= 0x7F)
        return writeByteUnsafe(uc);
      else if (uc <= 0x7FFu)
        return write2BytesUnsafe(uc);
      else if (uc <= 0xFFFFu)
        return write3BytesUnsafe(uc);
      else
        return write4BytesUnsafe(uc);
    }
  
    BL_INLINE BLResult writeByte(uint32_t uc) noexcept {
      BL_ASSERT(uc <= 0x7Fu);
      if (BL_UNLIKELY(atEnd()))
        return blSetError(BL_ERROR_NO_SPACE_LEFT);
  
      _ptr[0] = char(uint8_t(uc));
      _ptr++;
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult writeByteUnsafe(uint32_t uc) noexcept {
      BL_ASSERT(remainingSize() >= 1);
      _ptr[0] = char(uint8_t(uc));
      _ptr++;
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult write2Bytes(uint32_t uc) noexcept {
      BL_ASSERT(uc >= 0x80u && uc <= 0x7FFu);
  
      _ptr += 2;
      if (BL_UNLIKELY(_ptr > _end)) {
        _ptr -= 2;
        return blSetError(BL_ERROR_NO_SPACE_LEFT);
      }
  
      _ptr[-2] = char(uint8_t(0xC0u | (uc >> 6)));
      _ptr[-1] = char(uint8_t(0x80u | (uc & 63)));
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult write2BytesUnsafe(uint32_t uc) noexcept {
      BL_ASSERT(remainingSize() >= 2);
      BL_ASSERT(uc >= 0x80u && uc <= 0x7FFu);
  
      _ptr[0] = char(uint8_t(0xC0u | (uc >> 6)));
      _ptr[1] = char(uint8_t(0x80u | (uc & 63)));
  
      _ptr += 2;
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult write3Bytes(uint32_t uc) noexcept {
      BL_ASSERT(uc >= 0x800u && uc <= 0xFFFFu);
  
      _ptr += 3;
      if (BL_UNLIKELY(_ptr > _end)) {
        _ptr -= 3;
        return blSetError(BL_ERROR_NO_SPACE_LEFT);
      }
  
      _ptr[-3] = char(uint8_t(0xE0u | ((uc >> 12)     )));
      _ptr[-2] = char(uint8_t(0x80u | ((uc >>  6) & 63)));
      _ptr[-1] = char(uint8_t(0x80u | ((uc      ) & 63)));
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult write3BytesUnsafe(uint32_t uc) noexcept {
      BL_ASSERT(remainingSize() >= 3);
      BL_ASSERT(uc >= 0x800u && uc <= 0xFFFFu);
  
      _ptr[0] = char(uint8_t(0xE0u | ((uc >> 12)     )));
      _ptr[1] = char(uint8_t(0x80u | ((uc >>  6) & 63)));
      _ptr[2] = char(uint8_t(0x80u | ((uc      ) & 63)));
  
      _ptr += 3;
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult write4Bytes(uint32_t uc) noexcept {
      BL_ASSERT(uc >= 0x10000u && uc <= 0x10FFFFu);
  
      _ptr += 4;
      if (BL_UNLIKELY(_ptr > _end)) {
        _ptr -= 4;
        return blSetError(BL_ERROR_NO_SPACE_LEFT);
      }
  
      _ptr[-4] = char(uint8_t(0xF0u | ((uc >> 18)     )));
      _ptr[-3] = char(uint8_t(0x80u | ((uc >> 12) & 63)));
      _ptr[-2] = char(uint8_t(0x80u | ((uc >>  6) & 63)));
      _ptr[-1] = char(uint8_t(0x80u | ((uc      ) & 63)));
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult write4BytesUnsafe(uint32_t uc) noexcept {
      BL_ASSERT(remainingSize() >= 4);
      BL_ASSERT(uc >= 0x10000u && uc <= 0x10FFFFu);
  
      _ptr[0] = char(uint8_t(0xF0u | ((uc >> 18)     )));
      _ptr[1] = char(uint8_t(0x80u | ((uc >> 12) & 63)));
      _ptr[2] = char(uint8_t(0x80u | ((uc >>  6) & 63)));
      _ptr[3] = char(uint8_t(0x80u | ((uc      ) & 63)));
  
      _ptr += 4;
      return BL_SUCCESS;
    }
  };
  
  // ============================================================================
  // [BLUtf16Writer]
  // ============================================================================
  
  //! UTF16 writer that can be parametrized by `ByteOrder` and `Alignment`.
  template<uint32_t ByteOrder = BL_BYTE_ORDER_NATIVE, uint32_t Alignment = 2>
  class BLUtf16Writer {
  public:
    typedef uint16_t CharType;
  
    uint16_t* _ptr;
    uint16_t* _end;
  
    BL_INLINE BLUtf16Writer(uint16_t* dst, size_t size) noexcept {
      reset(dst, size);
    }
  
    BL_INLINE void reset(uint16_t* dst, size_t size) noexcept {
      _ptr = dst;
      _end = dst + size;
    }
  
    BL_NODISCARD
    BL_INLINE size_t index(const uint16_t* start) const noexcept { return (size_t)(_ptr - start); }
  
    BL_NODISCARD
    BL_INLINE bool atEnd() const noexcept { return _ptr == _end; }
  
    BL_NODISCARD
    BL_INLINE size_t remainingSize() const noexcept { return (size_t)(_end - _ptr); }
  
    BL_INLINE BLResult write(uint32_t uc) noexcept {
      if (uc <= 0xFFFFu)
        return writeBMP(uc);
      else
        return writeSMP(uc);
    }
  
    BL_INLINE BLResult writeBMP(uint32_t uc) noexcept {
      BL_ASSERT(uc <= 0xFFFFu);
  
      if (BL_UNLIKELY(atEnd()))
        return blSetError(BL_ERROR_NO_SPACE_LEFT);
  
      _writeMemU16(_ptr, uc);
      _ptr++;
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult writeBMPUnsafe(uint32_t uc) noexcept {
      BL_ASSERT(remainingSize() >= 1);
  
      _writeMemU16(_ptr, uc);
      _ptr++;
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult writeSMP(uint32_t uc) noexcept {
      BL_ASSERT(uc >= 0x10000u && uc <= 0x10FFFFu);
  
      _ptr += 2;
      if (BL_UNLIKELY(_ptr > _end)) {
        _ptr -= 2;
        return blSetError(BL_ERROR_NO_SPACE_LEFT);
      }
  
      uint32_t hi, lo;
      blCharToSurrogate(uc, hi, lo);
  
      _writeMemU16(_ptr - 2, hi);
      _writeMemU16(_ptr - 1, lo);
      return BL_SUCCESS;
    }
  
    BL_INLINE BLResult writeSMPUnsafe(uint32_t uc) noexcept {
      BL_ASSERT(remainingSize() >= 2);
      BL_ASSERT(uc >= 0x10000u && uc <= 0x10FFFFu);
  
      uint32_t hi, lo;
      blCharToSurrogate(uc, hi, lo);
  
      _writeMemU16(_ptr + 0, hi);
      _writeMemU16(_ptr + 1, lo);
  
      _ptr += 2;
      return BL_SUCCESS;
    }
  
    // --------------------------------------------------------------------------
    // [Utilities]
    // --------------------------------------------------------------------------
  
    static BL_INLINE void _writeMemU16(void* dst, uint32_t value) noexcept {
      BLMemOps::writeU16<ByteOrder, Alignment>(dst, value);
    }
  };
  
  // ============================================================================
  // [BLUtf32Writer]
  // ============================================================================
  
  //! UTF32 writer that can be parametrized by `ByteOrder` and `Alignment`.
  template<uint32_t ByteOrder = BL_BYTE_ORDER_NATIVE, uint32_t Alignment = 4>
  class BLUtf32Writer {
  public:
    typedef uint32_t CharType;
  
    uint32_t* _ptr;
    uint32_t* _end;
  
    BL_INLINE BLUtf32Writer(uint32_t* dst, size_t size) noexcept {
      reset(dst, size);
    }
  
    BL_INLINE void reset(uint32_t* dst, size_t size) noexcept {
      _ptr = dst;
      _end = dst + size;
    }
  
    BL_NODISCARD
    BL_INLINE size_t index(const uint32_t* start) const noexcept { return (size_t)(_ptr - start); }
  
    BL_NODISCARD
    BL_INLINE bool atEnd() const noexcept { return _ptr == _end; }
  
    BL_NODISCARD
    BL_INLINE size_t remainingSize() const noexcept { return (size_t)(_end - _ptr); }
  
    BL_INLINE BLResult write(uint32_t uc) noexcept {
      if (BL_UNLIKELY(atEnd()))
        return blSetError(BL_ERROR_NO_SPACE_LEFT);
  
      _writeMemU32(_ptr, uc);
      _ptr++;
      return BL_SUCCESS;
    }
  
    static BL_INLINE void _writeMemU32(void* dst, uint32_t value) noexcept {
      BLMemOps::writeU32<ByteOrder, Alignment>(dst, value);
    }
  };
  
  //! \}
  //! \endcond
  
  #endif // BLEND2D_UNICODE_P_H_INCLUDED
  