这有点乱,我很犹豫是否将其发布到互联网上而不将其清理到适当的库中...但我不太可能有时间做到这一点图书馆,所以在这里以防万一它有用。它很大程度上源自Shawn's contribution,但它没有使用可能增长到数百万个条目的每个代码点“缓存”,而是首先使用 Unicode::UCD 数据构建代码点范围及其相关宽度的“invmap”称呼;查询该地图的工作方式类似于(并且成本与单个 charprop 调用相同或略低)。
map_im 采用 prop_invmap 返回的 invmap,并通过哈希映射属性值。在散列中找不到的任何值都将成为undef,Unicode::UCD 不使用它,但我们的代码将其视为“无关紧要”。 merge_im 采用两个这样的 invmap 并将它们合并,以便“右侧”invmap 中的值覆盖“左侧”invmap 中的值,但右侧的 undef 范围允许左侧值“闪耀”。 charwidth的状态初始化根据Shawn自己charwidth的逻辑映射和合并三个invmap(East_Asian_Width、Category和特殊情况覆盖列表),该函数只是使用Unicode::UCD自己的查询search_invlist 例行公事。
在我的笔记本电脑上初始化需要
use Unicode::UCD;
use open qw/:std :locale/;
use charnames qw/:full/;
use feature 'state';
use List::Util 'reduce';
sub map_im {
my ($im, $h) = @_;
die unless $im->[2] eq 's';
my $out;
for my $i (0 .. $#{ $im->[0] }) {
my $val = $h->{ $im->[1][$i] };
my $different = @{ $out->[0] } ? ($val ne $out->[1][-1]) : defined($val);
if ($different) {
push @{ $out->[0] }, $im->[0][$i];
push @{ $out->[1] }, $val;
}
}
return $out;
}
sub merge_im {
my ($l, $r) = @_;
die unless $l->[0][0] == 0;
my $out;
my $idx_l = my $idx_r = 0;
my $val_l = $l->[1][0];
my $val_r;
while ($idx_r < @{ $r->[0] } || $idx_l < @{ $l->[0] }) {
my $newcp;
# Take from the list with the lower next entry. Or the one with entries left.
# This could probably be simplified.
if ($idx_r >= @{ $r->[0] } || ($idx_l < @{ $l->[0] }
&& $l->[0][$idx_l] <= $r->[0][$idx_r])) {
$newcp = $l->[0][$idx_l];
$val_l = $l->[1][$idx_l];
$idx_l ++;
} else {
$newcp = $r->[0][$idx_r];
$val_r = $r->[1][$idx_r];
$idx_r ++;
}
# But if they both have a transition at the same codepoint, take both so there's
# not a duplicate.
if ($idx_r < @{ $r->[0] } && $r->[0][$idx_r] == $newcp) {
$val_r = $r->[1][$idx_r];
$idx_r ++;
}
my $newval = defined($val_r) ? $val_r : $val_l;
# This gets skipped if we updated $val_l but $val_r is overriding, or
# $val_r went from undef to equaling $val_l.
if ($newval ne $out->[1][-1]) {
push @{ $out->[0] }, $newcp;
push @{ $out->[1] }, $newval;
}
}
return $out;
}
sub charwidth {
state $width_eaw = map_im([Unicode::UCD::prop_invmap('East_Asian_Width')],
{ F => 2, W => 2, H => 1, Na => 1, Neutral => 1, A => 1 }
);
state $width_cat = map_im([Unicode::UCD::prop_invmap('Category')],
{ Cc => 0, Mn => 0, Me => 0, Cf => 0 }
);
state $width_override = [
[ 0x0000, 0x0001, # NUL
0x00AD, 0x00AE, # Soft Hyphen
0x1160, 0x1200, # Hangul Jamo vowels and final consonants
0x200B, 0x200C, # ZWSP
],
[ 0, undef,
1, undef,
0, undef,
0, undef,
],
];
state $merged = reduce { merge_im($a, $b) } $width_eaw, $width_cat, $width_override;
my $cp = shift;
my $idx = Unicode::UCD::search_invlist($merged->[0], $cp);
return $merged->[1][$idx];
}
sub testwidth($) {
my $char = shift;
my $cp = ord $char;
printf "Width of %c (U+%04X %s) is %d\n", $cp, $cp,
charnames::viacode($cp), charwidth($cp);
}
testwidth "\x04";
testwidth "a";
testwidth "\N{MEDIUM BLACK CIRCLE}";
testwidth "\N{LARGE RED CIRCLE}";
testwidth "\N{U+20A9}";
testwidth "\N{U+1F637}";