qtcountlinemeasureqtextdocument

Qt - How to count and measure the lines in a QTextDocument?


In one of my project, I created a QTextDocument which owns a text I need to draw. The text is a word wrapped HTML formatted text, and it should be drawn in a rectangle area, for which I know the width. Its content will also never exceed a paragraph.

The text document is created as follow:

// create and configure the text document to measure
QTextDocument textDoc;
textDoc.setHtml(text);
textDoc.setDocumentMargin(m_TextMargin);
textDoc.setDefaultFont(m_Font);
textDoc.setDefaultTextOption(m_TextOption);
textDoc.setTextWidth(m_Background.GetMessageWidth(size().width()));

and here is a sample text I want to draw:

Ceci est un texte <img src=\"Resources/1f601.svg\" width=\"24\" height=\"24\"> avec <img src=\"Resources/1f970.svg\" width=\"24\" height=\"24\"> une <img src=\"Resources/1f914.svg\" width=\"24\" height=\"24\"> dizaine <img src=\"Resources/1f469-1f3fe.svg\" width=\"24\" height=\"24\"> de <img src=\"Resources/1f3a8.svg\" width=\"24\" height=\"24\"> mots. Pour voir comment la vue réagit.

The images are SVG images get from the qml resources.

In order to perform several operations while the text is drawn, I need to know how many lines will be drawn, after the word wrapping is applied, and the height of any line in the word wrapped text.

I tried to search in the functions provided by the text document, as well as those provided in QTextBLock, QTextFragment and QTextCursor. I tried several approaches like iterate through the chars with a cursor and count the lines, or count each fragments in a block. Unfortunately none of them worked: All the functions always count 1 line, or just fail.

Here are some code sample I already tried, without success:

// FAILS - always return 1
int lineCount = textDoc.lineCount()

// FAILS - always return 1
int lineCount = textDoc.blockCount()

// FAILS - return the whole text height, not a particular line height at index
int lineHeight = int(textDoc.documentLayout()->blockBoundingRect(textDoc.findBlockByNumber(lineNb)).height());
// get the paragraph (there is only 1 paragraph in the item text document
QTextBlock textBlock = textDoc.findBlockByLineNumber(lineNb);

int blockCount = 0;

for (QTextBlock::iterator it = textBlock.begin(); it != textBlock.end(); ++it)
{
    // FAILS - fragments aren't divided by line, e.g an image will generate a fragment
    QString blockText = it.fragment().text();
    ++blockCount;
}

return blockCount;
QTextCursor cursor(&textDoc);

int lineCount = 0;

cursor.movePosition(QTextCursor::Start);

// FAILS - movePosition() always return false
while (cursor.movePosition(QTextCursor::Down))
    ++lineCount;

I cannot figure out what I'm doing wrong, and why all my approaches fail.

So my questions are:

  1. How can I count the lines contained in my word wrapped document
  2. How can I measure the height of a line in my word wrapped document
  3. Are the text document function failing because of the html format? If yes, how should I do to reach my objectives in a such context?

NOTE I know how to measure the whole text height. However, as each line height may be different, I cannot just divide the whole text height by the lines, so this is not an acceptable solution for me.


Solution

  • I finally found a way to resolve my issue. The text document cannot be used directly to measure individual lines, the layout should be used for this purpose instead, as explained in the following post: https://forum.qt.io/topic/113275/how-to-count-and-measure-the-lines-in-a-qtextdocument

    So the following code is the solution:

    //---------------------------------------------------------------------------
    int getLineCount(const QString& text) const
    {
        // create and configure the text document to measure
        QTextDocument textDoc;
        textDoc.setHtml(text);
        textDoc.setDocumentMargin(m_TextMargin);
        textDoc.setDefaultFont(m_Font);
        textDoc.setDefaultTextOption(m_TextOption);
        textDoc.setTextWidth(m_Background.GetMessageWidth(size().width()));
    
        // this line is required to force the document to create the layout, which will then be used
        //to count the lines
        textDoc.documentLayout();
    
        // the document should at least contain one block
        if (textDoc.blockCount() < 1)
            return -1;
    
        int lineCount = 0;
    
        // iterate through document paragraphs (NOTE normally the message item should contain only 1 paragraph
        for (QTextBlock it = textDoc.begin(); it != textDoc.end(); it = it.next())
        {
            // get the block layout
            QTextLayout* pBlockLayout = it.layout();
    
            // should always exist, otherwise error
            if (!pBlockLayout)
                return -1;
    
            // count the block lines
            lineCount += pBlockLayout->lineCount();
        }
    
        return lineCount;
    }
    //---------------------------------------------------------------------------
    int measureLineHeight(const QString& text, int lineNb, int blockNb) const
    {
        // create and configure the text document to measure
        QTextDocument textDoc;
        textDoc.setHtml(text);
        textDoc.setDocumentMargin(m_TextMargin);
        textDoc.setDefaultFont(m_Font);
        textDoc.setDefaultTextOption(m_TextOption);
        textDoc.setTextWidth(m_Background.GetMessageWidth(size().width()));
    
        // this line is required to force the document to create the layout, which will then be used
        //to count the lines
        textDoc.documentLayout();
    
        // check if block number is out of bounds
        if (blockNb >= textDoc.blockCount())
            return -1;
    
        // get text block and its layout
        QTextBlock   textBlock = textDoc.findBlockByNumber(blockNb);
        QTextLayout* pLayout   = textBlock.layout();
    
        if (!pLayout)
            return -1;
    
        // check if line number is out of bounds
        if (lineNb >= pLayout->lineCount())
            return -1;
    
        // get the line to measure
        QTextLine textLine = pLayout->lineAt(lineNb);
    
        return textLine.height();
    }
    //---------------------------------------------------------------------------