libgdiplus 吞字 bug

这几天想把一个 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--;