繁体   English   中英

优化自动换行算法

[英]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 ++中, vectordeque都可以。 虽然从理论上讲, 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;
};

使用这种结构,实现应该简单明了,并且在内容增长时不应减慢太多。

对算法的一般更改-

  1. 算出是否需要尽可能便宜的滚动条,即。 计算文本中\\ n的数量,如果大于\\ n,则打开滚动条的vheight,检查长度,依此类推。
  2. 现在您知道是否需要滚动条,即可将文本准备好以适合控件使用。

这使您可以删除/减少测试if(!isVscroll && isVScrollNeeded())因为它几乎在每个字符上运行-isVScroll可能不是很便宜,示例代码似乎并未将行的知识传递给该函数,因此可以'看不到它如何指示是否需要它。

假设textRowsvector<string> textrows.back() +=有点贵,在字符串上查找后沿的方式不如+ =,对字符串而言效率不高。 我将改为使用ostrstream收集行,并在完成时将其推入。

getFont()。getWidth()可能很昂贵-字体是否正在更改? 最小和最大固定宽度字体的快捷方式之间的宽度有多大差异。

尽可能使用本机方法来获取单词的大小,因为您不想破坏它们-GetTextExtentPoint32

通常,当您在两者之间进行切换时,它们将有足够的空间容纳VScroll。 从头开始进行测量可能会花费您多达两倍的时间。 存储每行的线宽,以便您可以跳过仍然适合的宽度。 或者不要直接构建线串,而是使单词与大小分开。

确实需要多精确? 应用一些实用主义...
只是假设将需要VScroll,即使不需要,多数情况下换行也不会有太大变化(行尾/行首有1个字母)

尝试用单词而不是字母来工作-检查每个字母的剩余空间会浪费时间。 假设字符串中的每个字母都是最长的字母,字母x最长<空格,然后将其放入。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM