[英]Optimizing WordWrap Algorithm
我有一个自动换行算法,该算法基本上会生成适合文本宽度的文本行。 不幸的是,当我添加太多文本时,它变得很慢。
我想知道我是否监督了可以进行的任何重大优化。 此外,如果有人设计的结果仍然允许行字符串或行字符串指针更好,那么我愿意公开重写算法。
谢谢
void AguiTextBox::makeLinesFromWordWrap()
{
textRows.clear();
textRows.push_back("");
std::string curStr;
std::string curWord;
int curWordWidth = 0;
int curLetterWidth = 0;
int curLineWidth = 0;
bool isVscroll = isVScrollNeeded();
int voffset = 0;
if(isVscroll)
{
voffset = pChildVScroll->getWidth();
}
int AdjWidthMinusVoffset = getAdjustedWidth() - voffset;
int len = getTextLength();
int bytesSkipped = 0;
int letterLength = 0;
size_t ind = 0;
for(int i = 0; i < len; ++i)
{
//get the unicode character
letterLength = _unicodeFunctions.bringToNextUnichar(ind,getText());
curStr = getText().substr(bytesSkipped,letterLength);
bytesSkipped += letterLength;
curLetterWidth = getFont().getTextWidth(curStr);
//push a new line
if(curStr[0] == '\n')
{
textRows.back() += curWord;
curWord = "";
curLetterWidth = 0;
curWordWidth = 0;
curLineWidth = 0;
textRows.push_back("");
continue;
}
//ensure word is not longer than the width
if(curWordWidth + curLetterWidth >= AdjWidthMinusVoffset &&
curWord.length() >= 1)
{
textRows.back() += curWord;
textRows.push_back("");
curWord = "";
curWordWidth = 0;
curLineWidth = 0;
}
//add letter to word
curWord += curStr;
curWordWidth += curLetterWidth;
//if we need a Vscroll bar start over
if(!isVscroll && isVScrollNeeded())
{
isVscroll = true;
voffset = pChildVScroll->getWidth();
AdjWidthMinusVoffset = getAdjustedWidth() - voffset;
i = -1;
curWord = "";
curStr = "";
textRows.clear();
textRows.push_back("");
ind = 0;
curWordWidth = 0;
curLetterWidth = 0;
curLineWidth = 0;
bytesSkipped = 0;
continue;
}
if(curLineWidth + curWordWidth >=
AdjWidthMinusVoffset && textRows.back().length() >= 1)
{
textRows.push_back("");
curLineWidth = 0;
}
if(curStr[0] == ' ' || curStr[0] == '-')
{
textRows.back() += curWord;
curLineWidth += curWordWidth;
curWord = "";
curWordWidth = 0;
}
}
if(curWord != "")
{
textRows.back() += curWord;
}
updateWidestLine();
}
我认为有两个主要因素使速度变慢。
第一个,可能不太重要:在构建每行时,您要在该行后附加单词。 每个此类操作都可能需要重新分配该行并复制其旧内容。 对于长行,这是低效的。 但是,我猜想在实际使用中您的行很短(例如60-100个字符),在这种情况下,代价不太可能很大。 不过,在那里可能仍会获得一些效率。
第二个,也许更重要:您显然在某种GUI中将它用于文本区域,而且我猜想它正在被键入。 如果您要重新计算每个键入的字符,那么一旦文本变长,那真的会很受伤。
只要用户仅在末尾添加字符(这肯定是最常见的情况),您就可以有效利用以下事实:“贪心”换行算法的更改永远不会影响早期的行:只需从最后一行的开头重新计算。
如果即使在用户在文本中间的某个位置键入(或删除或其他内容)时也想使其快速运行,则您的代码将需要做更多的工作并存储更多的信息。 例如:当你建立一个行,记住“如果你开始一个符合这个词,它与字结束, 这也是导致线路全”。 当该行中发生任何更改时,使此信息无效。 现在,稍加编辑后,大多数更改将不需要太多的重新计算。 您应该自己解决这个问题,因为(1)这是一个很好的锻炼,并且(2)我现在需要上床睡觉。
(为了节省内存,您可能不希望根本不存储整行-不管您是否实现了我刚刚描述的那种技巧。相反,只需存储此处的下一个换行符信息并以您的UI需要呈现它们。)
它可能比您现在想承担的要复杂得多,但是您还应该查阅Donald Knuth的基于动态编程的换行算法。 它比您的要复杂得多,但仍然可以很快完成,并产生明显更好的结果。 参见例如http://defoe.sourceforge.net/folio/knuth-plass.html 。
算法问题经常伴随数据结构问题。
首先,让我们进行一些观察:
段
我将首先介绍段落的概念,该概念由用户引入的换行符确定。 进行版本编辑时,您需要找到相关的段落,这需要查找结构。
对于一个小的文本框,这里的“理想”结构将是一棵Fenwick树,但这似乎过分了。 我们只需要让每个段落存储组成其表示的显示行数,您就可以从头算起。 请注意,对最后显示的行的访问就是对最后一段的访问。
因此,这些段落以C ++的形式存储为连续的序列,很可能会受到间接调用的影响(即,存储指针),以免中间部分的段落被删除时将它们移动。
每个段落将存储:
std::string
表示它。 每个段落将缓存其显示,每当进行编辑时, 该段落缓存将失效。
一次只能对几个段落(以及更好的是显示的几行)进行实际渲染:那些可见。
显示行
一段可能显示至少一行,但没有最大值。 我们需要以可编辑的形式存储“显示”,这是一种适合版本的形式。
抛出\\n
单个字符不适合。 更改意味着要移动许多字符,并且用户应该更改文本,因此我们需要更好的。
使用长度而不是字符,我们实际上可能仅存储4个字节(如果字符串占用的空间超过3GB,则我对该算法不做太多保证)。
我的第一个想法是使用字符索引,但是在版本的情况下,所有后续索引都会更改,并且传播容易出错。 长度是偏移量,因此我们有一个相对于前一个单词的位置的索引。 它确实提出了一个单词(或令牌)是什么的问题。 值得注意的是,您是否折叠多个空间? 您如何处理它们? 在这里,我假设单词被单个空格隔开。
对于“快速”检索,我还将存储整个显示行的长度。 当在段落的字符503进行编辑时,这允许快速跳过显示的第一行。
因此,显示的行将包含:
这个序列在两端都应该是可有效编辑的(因为要进行换行,我们将在两端推送/弹出单词,具体取决于编辑是添加还是删除单词)。 如果中间的效率不是那么重要,那不是很重要,因为一次只能编辑一行。
在C ++中, vector
或deque
都可以。 虽然从理论上讲, list
是“完美的”,但实际上,它的较差的内存位置和较高的内存开销将抵消其渐近保证。 一条线由很少的单词组成,因此渐近行为无关紧要,而高常数则重要。
渲染
为了进行渲染,请选择一个已经足够长的缓冲区(一个带有std::string
并调用reserve
的缓冲区)。 通常,您每次都会clear
并重写缓冲区,因此不会发生内存分配。
您不需要显示看不到的内容,而是需要知道有多少行才能选择正确的段落。
一旦获得该段:
offset
设置为0
offset
(其后的空格为+ 1) _content
的子字符串访问,可以在buffer
上使用insert
方法: buffer.insert(buffer.end(), _content[offset], _content[offset+length])
困难在于保持offset
,但这就是使算法高效的原因。
结构
struct LineDisplay: private boost::noncopyable
{
Paragraph& _paragraph;
uint32_t _length;
std::vector<uint16_t> _words; // copying around can be done with memmove
};
struct Paragraph:
{
std::string _content;
boost::ptr_vector<LineDisplay> _lines;
};
使用这种结构,实现应该简单明了,并且在内容增长时不应减慢太多。
对算法的一般更改-
这使您可以删除/减少测试if(!isVscroll && isVScrollNeeded())
因为它几乎在每个字符上运行-isVScroll可能不是很便宜,示例代码似乎并未将行的知识传递给该函数,因此可以'看不到它如何指示是否需要它。
假设textRows
是vector<string>
textrows.back() +=
有点贵,在字符串上查找后沿的方式不如+ =,对字符串而言效率不高。 我将改为使用ostrstream
收集行,并在完成时将其推入。
getFont()。getWidth()可能很昂贵-字体是否正在更改? 最小和最大固定宽度字体的快捷方式之间的宽度有多大差异。
尽可能使用本机方法来获取单词的大小,因为您不想破坏它们-GetTextExtentPoint32
通常,当您在两者之间进行切换时,它们将有足够的空间容纳VScroll。 从头开始进行测量可能会花费您多达两倍的时间。 存储每行的线宽,以便您可以跳过仍然适合的宽度。 或者不要直接构建线串,而是使单词与大小分开。
确实需要多精确? 应用一些实用主义...
只是假设将需要VScroll,即使不需要,多数情况下换行也不会有太大变化(行尾/行首有1个字母)
尝试用单词而不是字母来工作-检查每个字母的剩余空间会浪费时间。 假设字符串中的每个字母都是最长的字母,字母x最长<空格,然后将其放入。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.