UTF-8 有一些简洁的属性,允许我们在处理 UTF-8 而不是字符时做你想做的事情。所以首先,你需要 UTF-8。
use Encode qw( encode_utf8 );
my $bytes = encode_utf8($str);
现在,在代码点之间进行拆分。0b0xxxxxxx
每个代码点的 UTF-8 编码都以匹配的字节或开头0b11xxxxxx
,您永远不会在代码点的中间找到这些字节。这意味着你想在之前截断
[\x00-\x7F\xC0-\xFF]
我们一起得到:
use Encode qw( encode_utf8 );
my $max_bytes = 8;
my $str = "\x{263a}\x{263b}\x{263c}"; # ☺☻☼
my $bytes = encode_utf8($str);
$bytes =~ s/^.{0,$max_bytes}(?![^\x00-\x7F\xC0-\xFF])\K.*//s;
# $bytes contains encode_utf8("\x{263a}\x{263b}")
# instead of encode_utf8("\x{263a}\x{263b}") . "\xE2\x98"
太好了,是吗?没有。以上可以在字素中间截断。字素(特别是“扩展的字素簇”)是人们将其视为单个视觉单元的东西。例如,“é”是一个字形,但可以使用两个代码点 ( ) 对其进行编码"\x{0065}\x{0301}"
。如果您在两个代码点之间进行切换,它将是有效的 UTF-8,但“é”将变成“e”!如果这不可接受,则上述解决方案也不是。(奥列格的解决方案也存在同样的问题。)
不幸的是,UTF-8 的属性不再足以帮助我们。我们需要一次抓取一个字形,并将其添加到输出中,直到我们无法容纳一个。
my $max_bytes = 6;
my $str = "abcd\x{0065}\x{0301}fg"; # abcdéfg
my $bytes = '';
my $bytes_left = $max_bytes;
while ($str =~ /(\X)/g) {
my $grapheme = $1;
my $grapheme_bytes = encode_utf8($grapheme);
$bytes_left -= length($grapheme_bytes);
last if $bytes_left < 0;
$bytes .= $grapheme_bytes;
}
# $bytes contains encode_utf8("abcd")
# instead of encode_utf8("abcde")
# or encode_utf8("abcde") . "\xCC"