我开发了一些面向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;
};
答案 0 :(得分:3)
我想知道在测量单词后跟空格的大小时,文本指标是否能提供可靠的结果。例如,width( "aaa " ) + width( "bbb" ) = width( "aaa bbb" )
?如果是这样,你可以测量文本中的每个单词,在它之后有空格和没有空格,并从那里计算出其余的单词。计划B(假设单词后跟空格的文本指标不能给出精确结果)是测量没有空格的每个单词,并使用固定值来估计单词之间的空间。
正如我所看到的,当前算法效率低下的原因是您正在调用measureText
方法O( n ^ 2 )次,并且您正在使用#{1}}方法。重新测量长弦的宽度。通过将文本分解为单词并测量每个单词,您只需要调用measureText
O( n )次,并且您将在相对较短的字符串上调用它。
然后,建议的算法从每行的开头开始并添加单词,直到达到包裹限制。这种添加方法可以减少必须测量的字符串数量,并减少必须测量的字符串长度。