【问题标题】:How to convert between a Unicode/UCS codepoint and a UTF16 surrogate pair?如何在 Unicode/UCS 代码点和 UTF16 代理对之间进行转换?
【发布时间】:2017-08-05 13:27:55
【问题描述】:

如何在 C++14 及更高版本中的 Unicode/UCS 代码点和 UTF16 代理对之间来回转换?

编辑:删除了对 UCS-2 代理的提及,因为没有这样的事情。谢谢@remy-lebeau

【问题讨论】:

    标签: c++ unicode c++14 surrogate-pairs ucs


    【解决方案1】:

    标记 info page 解释(比第 3.9 节表 3-5 中的 Unicode Standard 9.0 指定的更好。)从代码点转换为代理对的算法如下:

    基本多语言平面之外的 Unicode 字符,即代码高于 0xFFFF 的字符,由称为代理对的 16 位代码单元对以 UTF-16 编码,采用以下方案:

    • 从代码点中减去 0x010000,留下一个 0..0x0FFFFF 范围内的 20 位数字;
    • 前十位(0..0x03FF 范围内的数字)被添加到 0xD800 以给出第一个代码单元或高代理,它将在 0xD800..0xDBFF 范围内;
    • 低十位(也在 0..0x03FF 范围内)被添加到 0xDC00 以提供第二个代码单元或低代理,它将在 0xDC00..0xDFFF 范围内。

    在 C++14 及更高版本中,这可以写成:

    #include <cstdint>
    
    using codepoint = std::uint32_t;
    using utf16 = std::uint16_t;
    
    struct surrogate {
        utf16 high; // Leading
        utf16 low;  // Trailing
    };
    
    constexpr surrogate split(codepoint const in) noexcept {
        auto const inMinus0x10000 = (in - 0x10000);
        surrogate const r{
                static_cast<utf16>((inMinus0x10000 / 0x400) + 0xd800), // High
                static_cast<utf16>((inMinus0x10000 % 0x400) + 0xdc00)}; // Low
        return r;
    }
    

    在相反的方向上,只需将高代理的最后 10 位和低代理的最后 10 位组合起来,并添加0x10000

    constexpr codepoint combine(surrogate const s) noexcept {
        return static_cast<codepoint>(
                ((s.high - 0xd800) * 0x400) + (s.low - 0xdc00) + 0x10000);
    }
    

    以下是对这些转化的测试:

    #include <cassert>
    
    constexpr bool isValidUtf16Surrogate(utf16 v) noexcept
    { return (v & 0xf800) == 0xd800; }
    
    constexpr bool isValidCodePoint(codepoint v) noexcept {
        return (v <= 0x10ffff)
            && ((v >= 0x10000) || !isValidUtf16Surrogate(static_cast<utf16>(v)));
    }
    
    constexpr bool isValidUtf16HighSurrogate(utf16 v) noexcept
    { return (v & 0xfc00) == 0xd800; }
    
    constexpr bool isValidUtf16LowSurrogate(utf16 v) noexcept
    { return (v & 0xfc00) == 0xdc00; }
    
    constexpr bool codePointNeedsUtf16Surrogates(codepoint v) noexcept
    { return (v >= 0x10000) && (v <= 0x10ffff); }
    
    void test(codepoint const in) {
        assert(isValidCodePoint(in));
        assert(codePointNeedsUtf16Surrogates(in));
        auto const s = split(in);
        assert(isValidUtf16HighSurrogate(s.high));
        assert(isValidUtf16LowSurrogate(s.low));
        auto const out = combine(s);
        assert(isValidCodePoint(out));
        assert(in == out);
    }
    
    int main() {
        for (codepoint c = 0x10000; c <= 0x10ffff; ++c)
            test(c);
    }
    

    【讨论】:

      【解决方案2】:

      在 C++11 及更高版本中,您可以使用 std::wstring_convert 在各种 UTF/UCS 编码之间进行转换,使用以下 std::codecvt 类型:

      您不需要手动处理代理。

      您可以使用 std::u32string 保存您的代码点,并使用 std::u16string 保存您的 UTF-16/UCS-2 代码单元。

      例如:

      using convert_utf16_uf32 = std::wstring_convert<std::codecvt_utf16<char32_t>, char16_t>;
      
      std::u16string CodepointToUTF16(const char32_t codepoint)
      {
          const char32_t *p = &codepoint;
          return convert_utf16_uf32{}.from_bytes(
              reinterpret_cast<const char*>(p),
              reinterpret_cast<const char*>(p+1)
          );
      }
      
      std::u16string UTF32toUTF16(const std::u32string &str)
      {
          return convert_utf16_uf32{}.from_bytes(
              reinterpret_cast<const char*>(str.data()),
              reinterpret_cast<const char*>(str.data()+str.size())
          );
      }
      
      char32_t UTF16toCodepoint(const std::u16string &str)
      {
          std::string bytes = convert_utf16_uf32{}.to_bytes(str);
          return *(reinterpret_cast<const char32_t*>(bytes.data()));
      }
      
      std::u32string UTF16toUTF32(const std::u16string &str)
      {
          std::string bytes = convert_utf16_uf32{}.to_bytes(str);
          return std::u32string(
             reinterpret_cast<const char32_t*>(bytes.data()),
             bytes.size() / sizeof(char32_t)
          );
      }
      

      【讨论】:

      • 转换字符串的示例很有帮助,但也请提供转换单个代码点的基本示例。
      • 此外,根据 cppreference.com 的说法,std::codecvt_-prefixed 类似乎在 C++17 中已被弃用。对此有何评论?
      • @jotik from std::wstring_convert and std::codecvt_utf8 deprecated: "在 cppreference.com 页面的历史中,有一条注释指出 "p0618r0 deprecated codecvt"。这篇论文不是公开的,所以我们不'不知道它说什么
      • 现已上市
      • @Cubbi:我现在看到了 (open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0618r0.html),但它仍然没有描述 &lt;codecvt&gt;std::wstring_convertstd:::wbuffer_convert 被替换为(如果有的话)只是那个它们将被弃用(显然是 committee did adopt p0618r0: "Adopted 2017-03")。
      猜你喜欢
      • 1970-01-01
      • 2018-12-11
      • 2017-01-06
      • 1970-01-01
      • 2020-07-29
      • 2021-09-19
      • 1970-01-01
      • 1970-01-01
      • 2019-09-08
      相关资源
      最近更新 更多