【问题标题】:Converting an "HTML entity" emoticon code in UTF16 (in c++)在 UTF16 中转换“HTML 实体”表情符号代码(在 c++ 中)
【发布时间】:2016-09-20 21:45:18
【问题描述】:

我目前正在编写自己的支持表情符号的 DrawTextEx() 函数。使用此函数,每次在文本中找到表情符号时都会调用回调,从而使调用者有机会将包含表情符号的文本段替换为图像。例如,在文本中找到的 Unicode 字符 0x3DD8 0x00DE 将在绘制文本时被笑脸图像替换。其实这个功能很好用。

现在我想在调用方实现一个图像库。我在回调函数中收到一个像 0x3DD8 0x00DE 这样的文本段,我的想法是将此代码用作包含所有 Unicode 组合的映射中的键,每个组合都与包含要绘制的图像的结构链接。我在http://emojione.com/developers/ 网站上找到了一个不错的包。该站点上可用的所有软件包都包含多个文件名,即十六进制代码。所以我可以遍历包中包含的文件,并以自动方式创建我的地图。

但我发现这些代码是另一个标准的一部分,实际上是一组名为“HTML 实体”的项目,显然用于 Web 开发,如 http://graphemica.com/%F0%9F%98%80 网站上所见。因此,为了能够使用这些文件,我需要一个解决方案,将其名称中包含的 HTML 实体值转换为 UTF16 代码。比如上面提到的笑脸,我需要把0x1f600 HTML实体码转换成0x3DD8 0x00DE UTF16码。

蛮力方法可能包括编写一个转换这些代码的映射,方法是将它们中的每一个一个一个地添加到我的代码中。但由于 Unicode 标准在最乐观的情况下包含超过 1800 种表情符号组合,我想知道是否有一个现有的解决方案,例如已知的 API 或函数,我可以使用它来完成这项工作。还是有一个已知的技巧可以做到这一点? (例如“字符+('a' - 'A')”将大写字符转换为小写)

问候

【问题讨论】:

    标签: html entity utf-16 emoticons chars


    【解决方案1】:

    例如,文本中的 Unicode 字符 0x3DD8 0x00DE 将被替换为笑脸图像

    字符 U+1F600 Grinning Face ? 由 UTF-16 代码单元序列 0xD83D, 0xDE00 表示。

    (Graphemica 为每个代码单元交换字节顺序是超级误导;忽略这一点。)

    我发现这些代码是另一个标准的一部分,实际上是一组名为“HTML实体”的项目,显然是在Web开发中使用的

    HTML 与它无关。它们是普通的 Unicode 字符——只是基本多语言平面之外的字符,高于 U+FFFF,这就是为什么需要多个 UTF-16 代码单元来表示它们的原因。

    HTML 数字字符引用,如 😀(通常被错误地称为实体)是一种通过代码点编号引用字符的方式,但转义字符串仅在 HTML(或 XML)文档中有效,我们'不在其中之一。

    所以:

    我需要将 0x1f600 HTML 实体代码转换为 0x3DD8 0x00DE UTF16 代码。

    听起来更像:

    我需要将 U+1F600 笑脸的表示:从代码点编号 0x1F600 转换为 UTF-16 代码单元序列 0xD83D, 0xDE00

    在 C# 中是:

    string face = Char.ConvertFromUtf32(0x1F619); // "?" aka "\uD83D\uDE00"
    

    或在另一个方向:

    int codepoint = Char.ConvertToUtf32("\uD83D\uDE00", 0); // 0x1F619
    

    (这里选择的名称“UTF-32”很糟糕;我们谈论的是整数代码点编号,而不是每个字符四个字节的序列。)

    或者有什么已知的技巧可以做到这一点? (例如“字符+('a' - 'A')”将大写字符转换为小写)

    在 C++ 中,事情更烦人;没有(我能想到的)任何直接在代码点和 UTF-16 代码单元之间转换的东西。您可以使用各种编码函数/库在 UTF-32 编码的字节序列和 UTF-16 代码单元之间进行转换,但这最终可能比自己编写 conversion logic 更麻烦。例如,单个字符的最基本形式:

    std::wstring fromCodePoint(int codePoint) {
        if (codePoint < 0x10000) {
            return std::wstring(1, (wchar_t)codePoint);
        }
        wchar_t codeUnits[2] = {
            0xD800 + ((codePoint - 0x10000) >> 10),
            0xDC00 + ((codePoint - 0x10000) & 0x3FF)
        };
        return std::wstring(codeUnits, 2);
    }
    

    这是假设 wchar_t 类型基于 UTF-16 代码单元,与 C# 的 string 类型相同。在 Windows 上,这可能是真的。在其他地方可能不是,但在 wchar_t 基于代码点的平台上,您可以将每个代码点作为字符从字符串中提取出来,无需进一步处理。

    (优化和错误处理留给读者练习。)

    【讨论】:

      【解决方案2】:

      我正在使用 RAD Studio 编译器,幸运的是它提供了 bobince 提到的 ConvertFromUtf32 和 ConvertToUtf32 函数的实现。我对它们进行了测试,它们完全符合我的需要。

      对于那些不使用 Embarcadero 产品的用户,bobince 提供的 fromCodePoint() 实现也很有效。有关信息,这里也是 RAD Studio 中实现的 ConvertFromUtf32() 函数,并翻译成 C++

      std::wstring ConvertFromUtf32(unsigned c)
      {
          const unsigned unicodeLastChar  = 1114111;
          const wchar_t  minHighSurrogate = 0xD800;
          const wchar_t  minLowSurrogate  = 0xDC00;
          const wchar_t  maxLowSurrogate  = 0xDFFF;
      
          // is UTF32 value out of bounds?
          if (c > unicodeLastChar || (c >= minHighSurrogate && c <= maxLowSurrogate))
              throw "Argument out of range - invalid UTF32 value";
      
          std::wstring result;
      
          // is UTF32 value a 16 bit value that can fit inside a wchar_t?
          if (c < 0x10000)
              result = wchar_t(c);
          else
          {
              // do divide in 2 chars
              c -= 0x10000;
      
              // convert code point value to UTF16 string
              result  = wchar_t((c / 0x400) + minHighSurrogate);
              result += wchar_t((c % 0x400) + minLowSurrogate);
          }
      
          return result;
      }
      

      感谢 bobince 的回复,为我指明了正确的方向并帮助我解决了这个问题。

      问候

      【讨论】:

        猜你喜欢
        • 2012-07-14
        • 2014-07-21
        • 2014-09-05
        • 1970-01-01
        • 1970-01-01
        • 2020-08-23
        • 1970-01-01
        • 1970-01-01
        • 2017-06-28
        相关资源
        最近更新 更多