我有一个包含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},
};
(免责声明:我没有安装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;
}
好吧,就是这样。