换行文本的算法(换行文本以适应'page')

时间:2014-07-02 05:52:23

标签: algorithm text graphics

我开发了一些面向GUI的应用程序,它们实现了自己的文本换行算法。例如,考虑我的应用程序包含可以在屏幕上布局的典型GUI“小部件”。复选框,文本字段,简单标签等小部件相当容易绘制。然而,诸如“段落”之类的小部件(任意数量的多行文本,其应当适合于指定的盒子,并且必要时发生断行)由于良好的断行部分而更加困难。

每次我实现这样的算法时,我都使用了一种方法,但效率很低。我的一般方法(将字符串放入宽度 w 的框中)一直迭代地取一个字符串 s ,使用字体指标来衡量其像素长度 l ,然后将其缩小直至l <= w。然后将剩余部分分配给 s ,然后重复此过程,直到我留下值为 s 小于或等于 w

在这个底部是一个Javascript示例(诚然,这可能不是做这种事情的最佳环境)。此代码将是前面提到的“段落”小部件的一部分,并且是为HTML5 Canvas API编写的( ctx 是Canvas'图形上下文)。显然,这种方法的Big-O分析非常差。但是......有没有更好的方法来做这种事情?我假设它在某种程度上取决于我们工作的环境。但我也假设,鉴于存在的文本编辑工具的数量,存在一种有效的解决方案。

    // the paragraph widgets' main drawing function
    this.drawText = function(ctx) {
        ... 

        var lines = this.text.split("\n"); // here we account for user-entered line breaks
        var y = this.y;

        for (var i=0; i<lines.length; i++) {
            var currTxt = lines[i]; // a chunk of text in between user-entered line breaks
            var miniLines = this.breakToLines(currTxt, this.textWidth(), ctx);
            for (var j = 0; j < miniLines.length; j++) {
                var miniTxt = miniLines[j];

                var x = this.x + ( (this.round) ? this.cornerRadius : 0 );

                x += this.textOffset();
                y += this.fontSize;

                ctx.save();
                ctx.rect(this.x, this.y, this.width, this.height);
                ctx.clip();

                ctx.fillText(miniTxt, x, y);
                ctx.restore();
            }

        };
    };

    // take a chunk of text and break it into lines that fit within width 'w'
    this.breakToLines = function(txt, w, ctx) {
        var arr = [];
        while (true) {
            var txt2 = this.popLine(txt, w, ctx);
            if (txt2 == null)
                break;
            arr.push(txt2);
            if (txt.length <= txt2.length)
                break;
            txt = txt.substring(txt2.length);
        }
        return arr;
    };

    this.popLine = function(txt, w, ctx) {
        var m = ctx.measureText(txt); // 'm' represents the size of the text
        if (m.length == 0)
            return null;  // 'm' is empty, so we're done
        while (m.width > w) {
            // remove a word from txt and re-measure it
            txt = txt.substring(0, txt.lastIndexOf(' '));
            m = ctx.measureText(txt);
        }
        return txt;
    };

1 个答案:

答案 0 :(得分:3)

我想知道在测量单词后跟空格的大小时,文本指标是否能提供可靠的结果。例如,width( "aaa " ) + width( "bbb" ) = width( "aaa bbb" )?如果是这样,你可以测量文本中的每个单词,在它之后有空格和没有空格,并从那里计算出其余的单词。计划B(假设单词后跟空格的文本指标不能给出精确结果)是测量没有空格的每个单词,并使用固定值来估计单词之间的空间。

正如我所看到的,当前算法效率低下的原因是您正在调用measureText方法O( n ^ 2 )次,并且您正在使用#{1}}方法。重新测量长弦的宽度。通过将文本分解为单词并测量每个单词,您只需要调用measureText O( n )次,并且您将在相对较短的字符串上调用它。

然后,建议的算法从每行的开头开始并添加单词,直到达到包裹限制。这种添加方法可以减少必须测量的字符串数量,并减少必须测量的字符串长度。