读取UTF8(24位)并输出



我有一个包含8位和24位UTF8编码(ASCII和日语(的UTF8文本文件。

我的目标是一个接一个地读入字符,然后输出这样的东西:

{'V', 0x02},

这是一个C++语言初始化条目,其中第一个参数是UTF8编码,第二个参数是文件中的索引或位置。

我的第一步是成功读取UTF8文本文件并输出到控制台。

以下是输入文件的示例:

()+-0123456789Vanoty˄˅いおがきくさしすせただてでなにのびまみむめりるれをん  

以下是我的程序的输出示例:

$ ./main.exe
Size of wchar_t: 2
32
40
41
43
45
48
49
50
51
52
53
54
55
56
57
86
97
110
111
116
121
4294953604
4294953605
4293034116
4293034122
4293034124
4293034125
4293034127
4293034133
4293034135
4293034137
4293034139
4293034143
4293034144

这是我的代码:

#include <fstream>
#include <iostream>
int main()
{
std::cout << "Size of wchar_t: " << sizeof(wchar_t) << "n";
std::ifstream japanese_file("japanese_font_glyphs_all_112_qty_horizontal_layout.txt", std::ios::binary);
char c = '';
char32_t    utf8_char = 0;
while (japanese_file.read(&c, 1))
{
unsigned int bytes_in_encoding = 1u;
if ((c & 0x80u) == 0U)
{
bytes_in_encoding = 1u;
}
else
{
if ((c & 0xF0u) == 0xE0u)
{
bytes_in_encoding = 3u;
}
else
{
if ((c & 0xE0u) == 0xC0u)
{
bytes_in_encoding = 2u;
}
}
}
char32_t    utf8_encoding = 0u;
switch (bytes_in_encoding)
{
case 1:
utf8_char = c;
break;
case 2:
{
char c2 = 0;
japanese_file.read(&c2, 1);
utf8_char = (c * 0x100ul) + c2;
}
break;
case 3:
{
char c2 = 0;
japanese_file.read(&c2, 1);
char c3 = 0;
japanese_file.read(&c3, 1);
utf8_char = (c * 0x10000ul) + (c2 * 0x100ul) + c3;
}
break;
default:
break;
}
std::cout << utf8_char << "n";
}

japanese_file.close();
return EXIT_SUCCESS;
}

输出显示wchar_t的大小为2,不足以容纳日语字形的24位编码。

那么,我该用什么代码将24位UTF8编码(作为单个字形(输出到控制台呢?

设置
g++(GCC(10.2.0--Cygwin
Windows 10
Visual Studio 2017

我正在编写的应用程序将作为控制台应用程序在Windows10上运行。

编辑1--背景
我的应用程序将生成C++数据语句,用于创建显示芯片位图寄存器的索引。

以下是结构定义和一些示例条目:

struct UTF8_To_Bitmap_Index_t
{
char32_t    encoded_character;      //!< UTF8 encoding.
uint8_t     bitmap_index;           //!< Index of glyph within the font.
uint8_t     padding_alignment;      //!< For alignment purposes, not used.
};
static const
UTF8_To_Bitmap_Index_t default_conversion_table[] =
{
{'¡', 0x01, 0u}, 
{'À', 0x02, 0u}, 
{'Á', 0x03, 0u}, 
{'Ã', 0x04, 0u}, 
{'Ä', 0x05, 0u}, 
{'Å', 0x06, 0u}, 
};
编辑→以下假设您希望将UTF-8解码为Unicode代码点值,这对于查找表和查找字体中的字形非常有用。

(免责声明:我没有安装cygwin;下面使用MinGW-w64,并假设有一个与bash兼容的命令行。您可能需要对cygwin的怪异之处进行调整。(

要找到并使用Windows的ICU,您需要采取一些步骤。打开一个shell提示符(bash/zsh/cygwin给你的任何东西(并键入:

cd /mnt/c/Program Files (x86)/Windows Kits/

→记住,你可以随时点击TAB来帮助你。

查找ICU标题

find ~+ -name 'icu*.h'

您将获得至少一个目录中的文件列表。选择版本号最大的目录。对我来说,它是"10.0.17763.0"。这是你将添加到CPATH的路径(请确保使用正确的字符大写并转义那些空格和括号(:

(Makefile) CPATH += /mnt/c/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/um
(Terminal) export CPATH=/mnt/c/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/um/

请参阅下面的示例,了解如何处理源代码中实际包含的标头。

查找ICU库文件

find ~+ -name 'icu*.lib'

选择与上面相同的版本号,然后选择正确的体系结构。对于大多数现代电脑来说,它是"x64"。这就是GCC令人讨厌的地方:你不能把它添加到你的LIBRARY_PATH中。相反,您必须在命令行中指定程序源中的完整路径。

/mnt/c/Program Files (x86)/Windows Kits/10/Lib/10.0.17763.0/um/x64/icuin.Lib
/mnt/c/Program Files (x86)/Windows Kits/10/Lib/10.0.17763.0/um/x64/icuuc.lib

作弊,你可以直接将它们复制到工作目录中(这就是我假设你用下面的例子所做的;-(

编译

g++ -Wall -Wextra -pedantic-errors -O3 -std=c++17 -o example example.cpp icuin.Lib icuuc.lib
strip example.exe

MSVC对应的命令行是:

cl /EHsc /W4 /Ox /std:c++17 example.cpp

示例程序

请注意,在旧的Windows控制台中,使用纯C或C++工具将UTF-8打印到控制台有点棘手且不一致。新的Windows终端使一切正常工作。下面的代码假定新的Windows终端——如果没有帮助,它将无法在旧的Windows控制台上正确显示。

(如果你必须在旧的Windows控制台上正确显示,我发现wprintf((往往最常用,但我并不介意,因为让它在编译器之间表现是不可能的。我只是在附加到输出流IFF的修改后的rdbuf中使用WriteConsoleW,它们附加到控制台。这样,C++代码就可以用std::cout以通常的方式编写。(

"example.txt"文件与您在问题中提供的文本相同,但任何UTF-8编码的文件都可以。

第47行的BUFFER_SIZE值故意变小,以突出ICU的ucnv_toUnicode功能是如何工作的(按照您的要求执行(。我个人会使用更大的缓冲区,从100到1024个元素,这取决于你想要占用多少堆栈空间。

另一种选择是对输入和输出缓冲区使用std::vector——您甚至可以提供默认的参数缓冲区大小来选择它。只要注意你的要求,不要过度设计。

如上所述,包含哪个ICU文件取决于奇怪的Windows版本控制疯狂。如果您的系统上没有可用的"icu.h",则必须使用较旧的"icucommon.h"one_answers"icui18n.h"文件。通过将-DSUPPRESS_LEGACY_ICU_HEADER_WARNINGS添加到命令行中来完成此操作。

以下代码使用一个函数来构造std::map。请注意文件偏移量必须是键值,否则会发生冲突。当然,你可以打印结果,然后忘记返回地图。这只是如何使用ICU解决问题的一个例子。

#include <ciso646>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <string>
using namespace std::string_literals;

#ifdef SUPPRESS_LEGACY_ICU_HEADER_WARNINGS
#include <icucommon.h>
#include <icui18n.h>
#else
#include <icu.h>
#endif
#ifdef _MSC_VER
#pragma comment(lib, "icuuc")
#pragma comment(lib, "icuin")
#endif

// Function to convert bytes from file --> map of {file offset, Unicode code point}
std::map <size_t, char32_t>
map_utf8_file_offsets( const std::filesystem::path & filename )
{
std::map <size_t, char32_t> result;
// This is how we get {offset,uchar} pairs -- using a UTF-8 UConverter
UErrorCode err;
std::shared_ptr <UConverter> ucnv8
(
ucnv_open( "UTF-8", &(err=U_ZERO_ERROR) ),
[]( UConverter * ucnv8 ) { if(ucnv8) ucnv_close( ucnv8 ); }
);
if (!ucnv8.get()) throw u_errorName( err ) + " (Could not create UTF-8 converter)"s;
// We'll process the file in small chunks
std::ifstream f( filename, std::ios::binary );
if (!f) throw "Failure to open file: " + filename.string();
constexpr int BUFFER_SIZE = 10;  // adjust to your liking
char          octets    [BUFFER_SIZE];
UChar         codepoints[BUFFER_SIZE];
int32_t       offsets   [BUFFER_SIZE];
UChar       * p_codepoint;
const char  * p_octet;
size_t        offset_base = 0;
// For each chunk
while (f.read( octets, BUFFER_SIZE ) or f.gcount())
{
// Convert it
ucnv_toUnicode
(
ucnv8.get(),
&(p_codepoint=codepoints), codepoints+BUFFER_SIZE,
&(p_octet    =octets),     octets    +f.gcount(),  // true: gcount ≤ BUFFER_SIZE
offsets,
f.gcount()!=BUFFER_SIZE,
&(err=U_ZERO_ERROR)
);
if (err) throw u_errorName( err ) + " (UTF-8 conversion error)"s;
// Store results
auto n_codepoints = p_codepoint - codepoints;
for (int n = 0;  n < n_codepoints;  n++)
{
result[ offset_base+offsets[n] ] = codepoints[n];
}
offset_base += BUFFER_SIZE;
}
ucnv_resetToUnicode( ucnv8.get() );
return result;
}

// Helper to print Unicode characters to the console since cout/wcout can't do it directly
std::string to_utf8( char32_t c )
{
UErrorCode err = U_ZERO_ERROR;
char s[5];
int32_t n = 0;
UChar uc = (UChar)c;
return u_strToUTF8( s, 5, &n, &uc, 1, &err );
}

int main()
try
{
for (const auto & [offset, c] : map_utf8_file_offsets( "example.txt" ))
{
std::cout << "{'" << to_utf8( c ) << "', " << offset << "},n";
}
}
catch (const std::string& s)
{
std::cerr << s << "n";
return 1;
}

好吧,就是这样。

最新更新