关于富文本换行问题

:bug: 富文本换行大作战:当文字决定"越狱"的时候

:clipboard: 案发现场

引擎版本: Cocos Creator 2.4.11

举例文本: <outline width=2>Valid t t000000000000000000op-up of<color=#ffec4e>xxxx</color> --------</outline>

案发经过: 某天风和日丽,我正在愉快地写代码,突然发现文字们集体"造反"了!它们拒绝乖乖换行,非要挤在一起开party,结果…

    xxxx --xx-- 都显示到 Node 框外了 😂😂
    (文字:我们要自由!框框什么的最讨厌了!)

复现率: 100%

:male_detective:‍♂ 福尔摩斯式破案过程

经过一番"深入浅出"的调查(其实就是疯狂打断点),我发现了这个狡猾的bug藏身之处:

行文本宽度 > richText.MaxWidth 时,换行逻辑开始"摆烂"。

真相大白时刻:

  • cc.textUtils.fragmentText 这个方法在处理临界值时,表现异常:

  • measureText('') 居然返回 4!(空字符串你凭什么占4个像素?:face_with_raised_eyebrow:

  • allWidth > maxWidth && allWidth - maxWidth <= 4 时,while 会循环执行 10 次,结果就是数值偏移,文字"越狱"成功

具体的换行逻辑… …查看源码

:bulb: 拯救世界的解决方案

既然找到了这个"捣蛋鬼",那就好办了! :v:

核心问题: measureText('') = 4

至于为什么空字符串要占4个像素?估计是有什么深刻含义!!!

我的解决方案:

重写了相关逻辑,顺便把空格字符 ' ' 的临界值问题也一并解决了(一石二鸟,效率max!)


温馨提示:修复bug就像打地鼠,解决一个,可能又冒出三个。但这次我很有信心,这个bug已经被我"物理说服"了! :hammer:

4赞

有理有据令人幸福

粘贴下代码,方便大家搬砖 :robot:

    // 重写 fragmentText 方法
    const originalFragmentText = cc.textUtils.fragmentText;
    cc.textUtils.fragmentText = function (stringToken, allWidth, maxWidth, measureText) {
        if (allWidth > maxWidth) {
            stringToken = stringToken.trimEnd();
            if (allWidth - maxWidth <= 4) {
                allWidth = maxWidth + 4.1;
            }
        }
        // 直接调用原始方法,避免通过 cc.textUtils 调用
        return originalFragmentText.call(this, stringToken, allWidth, maxWidth, measureText);
    };
1赞

最新的 3.8.x 的换行逻辑,已经优化了 “” 计算带来的问题,避免循环(fragmentText 方法)

        // Find the truncation point
        // if the 'tempText' which is truncated from the next line content equals to '',
        // we should break this loop because there is no available character in the next line.
        //  tmpText 为 '' 不在循环
        //    2.4.x 的判断  while (width <= maxWidth && checkWhile++ < checkCount) {
        while (tmpText && width <= maxWidth && checkWhile++ < checkCount) {
            const exec = WORD_REG.exec(tmpText);
            pushNum = exec ? exec[0].length : 1;
            sLine = tmpText;

            fuzzyLen += pushNum;
            tmpText = _safeSubstring(text, fuzzyLen);
            width = allWidth - measureText(tmpText);
        }

       ... code

        if (wrappedWords.length === 0) {
            wrappedWords.push(text);
        } else {
            text = text.trim();  // 2.x 使用的是 text.trimLeft();
            if (text.length > 0) {
                wrappedWords.push(text);
            }
        }
1赞