这几天想把一个 dotNET WinForm 程序移植到 Linux 平台,界面上某些标签最后一个字老是不显示。经过分析定位到了 libgdiplus 并完成修复。鉴于 libgdiplus 已经凉了上游不太可能修复,发出来给大家闭坑。
环境
银河麒麟 V10 SP1 ( glibc 2.31 ,x86_64 )、libgdiplus 6.0.4 、Mono 6.8 。
复现
#include <stdio.h>
#include <ctype.h>
#include <locale.h>
int main(void) {
setlocale(LC_ALL, "");
printf("isspace(般) = %d\n", isspace(0x822C));
}
$ gcc demo.c -o a.out
$ LC_ALL=zh_CN.UTF-8 ./a.out
isspace(般) = 8192 ← 8192 = 0x2000 = _ISspace ,被判成空格了
$ LC_ALL=C ./a.out
isspace(般) = 0
值和库相同,仅仅 locale 不同结果就不一致。在 zh_CN.UTF-8 / en_US.UTF-8 + glibc 2.31 上实测,这 32 个常用字作为末字时会被吞:
也 自 而 第 美 从 但 前 行 动 位 名 使 力 公 做
十 像 加 表 克 员 稜 先 住 色 候 变 呢 低 般 健
bug 位置
拉取 libgdiplus 6.0.4 源码后一路测试,定位到了 src/text-cairo.c 的
MeasureString():
/* 除非指定要量尾随空格,否则裁掉( GDI+ 的老行为,issue #80680 ) */
if ((format->formatFlags & StringFormatFlagsMeasureTrailingSpaces) == 0) {
while ((StringLen > 0) && (isspace ((int) ( *(Src + StringLen - 1)))))
StringLen--;
if (StringLen == 0)
StringLen = 1;
}
Src 是 GDIPCONST WCHAR *,也就是 16 位宽的 UTF-16 码元;而 isspace() 是 ctype.h
里的窄字符函数,只对 EOF 和 0–255 有定义。把 般( U+822C = 33324 )传进去,是标准的
未定义行为。
修复
- while ((StringLen > 0) && (isspace ((int) ( *(Src + StringLen - 1)))))
+ while ((StringLen > 0) && (g_unichar_isspace ((gunichar)(*(Src + StringLen - 1)))))
StringLen--;