大阪市中央区 システムソフトウェア開発会社

営業時間:平日09:15〜18:15
MENU

C++でC風ライブラリを作る(UTF-16またはUTF-32からUTF-8への変換編)

著者:高木信尚
公開日:2019/04/14
最終更新日:2019/04/13
カテゴリー:技術情報

高木です。こんばんは。

伊関が入社して以来、このブログもにわかに活気づいた感があります。
ひとつひとつの記事のクオリティを上げるには限界があるので、とにかくアウトプットを増やせばそのうちのいくらかは世の中の人たちのお役に立てるものが出てくるだろうと考えています。
いろんな人が多くの投稿をしてくれるのはありがたいことです。

さて、最近の私は週1投稿のペースを守っています。
ところが、他のメンバーが頑張っているのであっという間に私の投稿は流れていってしまいます。
今回は前回に引き続き、UTF-16からUTF-8への変換、そしてUTF-32からUTF-8への変換もいっしょにやってしまおうと思います。

多重定義する両方のctrtomb関数に共通する部分をutf32_to_utf8関数に切り出し、それぞれから呼び出すようにしました。
また、mbstate_t型は前回まではstd::uint32_t型そのものでしたが、誤使用を避けるため構造体にしました。
さらに、不正なサロゲートペアに対するエラー処理を強化しました。

以下にコードを掲載します。

namespace cloverfield
{
  namespace detail
  {
    inline int utf32_to_utf8(char* s, char32_t c32)
    {
      int result = -1;

      if (c32 == 0)
      {
        s[0] = '\0';
        result = 1;
      }
      else if (c32 < 0x80)
      {
        *s = static_cast<char>(c32);
        result = 1;
      }
      else if (c32 < 0x800)
      {
        s[0] = static_cast<char>(0xc0 | (c32 >> 6));
        s[1] = static_cast<char>(0x80 | (c32 & 0x3f));
        result = 2;
      }
      else if (c32 < 0x10000)
      {
        s[0] = static_cast<char>(0xe0 | (c32 >> 12));
        s[1] = static_cast<char>(0x80 | ((c32 >> 6) & 0x3f));
        s[2] = static_cast<char>(0x80 | (c32 & 0x3f));
        result = 3;
      }
      else if (c32 < 0x110000)
      {
        s[0] = static_cast<char>(0xf0 | (c32 >> 18));
        s[1] = static_cast<char>(0x80 | ((c32 >> 12) & 0x3f));
        s[2] = static_cast<char>(0x80 | ((c32 >> 6) & 0x3f));
        s[3] = static_cast<char>(0x80 | (c32 & 0x3f));
        result = 4;
      }
      return result;
    }
  }

  constexpr int mb_len_max = 4;
  constexpr int mb_cur_max = 4;

  struct mbstate_t
  {
    std::uint32_t state;
  };

  inline std::size_t ctrtomb(char* s, char32_t c32, mbstate_t* ps)
  {
    if (ps == nullptr)
    {
      thread_local mbstate_t ts{};
      ps = &ts;
    }

    if (s == nullptr)
    {
      static char buf[mb_len_max];
      s = buf;
      c32 = 0;
    }

    int t;
    std::size_t result = -1;
    if (c32 != 0 && (ps->state & 0xfc00) == 0xd800)
      goto illegal_sequence;

    t = detail::utf32_to_utf8(s, c32);
    if (t < 0)
      goto illegal_sequence;
    result = t;
    ps->state = c32;
    return result;

illegal_sequence:
    ps->state = -1;
    throw std::invalid_argument("cloverfield::ctrtomb");
  }

  inline std::size_t ctrtomb(char* s, char16_t c16, mbstate_t* ps)
  {
    if (ps == nullptr)
    {
      thread_local mbstate_t ts{};
      ps = &ts;
    }

    if (s == nullptr)
    {
      static char buf[mb_len_max];
      s = buf;
      c16 = 0;
    }

    int t;
    std::size_t result = -1;
    char32_t c32 = c16;
    if (c32 != 0)
    {
      if ((ps->state & 0xfc00) == 0xd800)
      {
        if ((c16 & 0xfc00) == 0xdc00)
          c32 = ((ps->state & 0x3ff) << 10 | c16 & 0x3ff) + 0x10000;
        else
        goto illegal_sequence;
      }
      else if ((c16 & 0xfc00) == 0xd800)
      {
        ps->state = c16;
        return 0;
      }
    }

    t = detail::utf32_to_utf8(s, c32);
    if (t < 0)
      goto illegal_sequence;
    result = t;
    ps->state = c32;
    return result;

illegal_sequence:
    ps->state = -1;
    throw std::invalid_argument("cloverfield::ctrtomb");
  }
}

前回に引き続き、ほとんどコードを掲載するだけの手抜きな内容になってしまいましたね。
次回は引数にmbstate_t*を取らないcttomb関数に再度取り組みことにします。

    前の記事 :
    上に戻る