• 邓望舒

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

    主楼
  • MountainHike

    这个修复方案会不会影响其他对空白字符有正确处理的场景?

    #1
  • 陈皓

    其实可以考虑用 iswspace 替代 isspace,专门处理宽字符判断。

    #2
  • moioooo

    我们之前也遇到过类似的中文渲染问题,把 isspace 换成宽字符函数就解决了。

    #3

发表回复

登录后回复