From 9619737a4c951d7f2abc80b59e287896b735e01d Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Mon, 19 Sep 2011 16:46:38 +0200 Subject: [PATCH] Support rich text elements in QSGTextNode In order to have the same text rendering mechanism regardless of the contents of the document, we need to implement rich text layout elements in the scene graph. Currently we support the following: - Text objects, floating and inline (including images) - Tables - Lists - Only solid borders - Frame background colors - All the HTML text/text layout/font manipulation Not supported yet: - Other border types - Border radius -
Task-number: QTBUG-20917 Change-Id: I4112940e6bc4ad74ae749a727acdd7e6fec3f871 Reviewed-on: http://codereview.qt-project.org/5803 Reviewed-by: Qt Sanity Bot Reviewed-by: Jiang Jiang --- src/declarative/items/qsgtext.cpp | 6 +- src/declarative/items/qsgtextedit.cpp | 17 +- src/declarative/items/qsgtextedit_p_p.h | 4 +- src/declarative/items/qsgtextnode.cpp | 648 ++++++++++++++++++++------------ src/declarative/items/qsgtextnode_p.h | 3 + 5 files changed, 423 insertions(+), 255 deletions(-) diff --git a/src/declarative/items/qsgtext.cpp b/src/declarative/items/qsgtext.cpp index 3cad9b3..a1b1260 100644 --- a/src/declarative/items/qsgtext.cpp +++ b/src/declarative/items/qsgtext.cpp @@ -958,7 +958,7 @@ void QSGText::setText(const QString &n) d->ensureDoc(); d->doc->setText(n); d->rightToLeftText = d->doc->toPlainText().isRightToLeft(); - d->richTextAsImage = QSGTextNode::isComplexRichText(d->doc); + d->richTextAsImage = enableImageCache(); } else { d->rightToLeftText = d->text.isRightToLeft(); } @@ -1384,7 +1384,7 @@ void QSGText::setTextFormat(TextFormat format) if (!wasRich && d->richText && isComponentComplete()) { d->ensureDoc(); d->doc->setText(d->text); - d->richTextAsImage = QSGTextNode::isComplexRichText(d->doc); + d->richTextAsImage = enableImageCache(); } d->updateLayout(); @@ -1674,7 +1674,7 @@ void QSGText::componentComplete() d->ensureDoc(); d->doc->setText(d->text); d->rightToLeftText = d->doc->toPlainText().isRightToLeft(); - d->richTextAsImage = QSGTextNode::isComplexRichText(d->doc); + d->richTextAsImage = enableImageCache(); } else { d->rightToLeftText = d->text.isRightToLeft(); } diff --git a/src/declarative/items/qsgtextedit.cpp b/src/declarative/items/qsgtextedit.cpp index 5cb061b..6dd3908 100644 --- a/src/declarative/items/qsgtextedit.cpp +++ b/src/declarative/items/qsgtextedit.cpp @@ -63,6 +63,7 @@ QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) +DEFINE_BOOL_CONFIG_OPTION(qmlEnableImageCache, QML_ENABLE_TEXT_IMAGE_CACHE) /*! \qmlclass TextEdit QSGTextEdit @@ -260,7 +261,7 @@ void QSGTextEdit::setText(const QString &text) #else d->control->setPlainText(text); #endif - d->isComplexRichText = QSGTextNode::isComplexRichText(d->document); + d->useImageFallback = qmlEnableImageCache(); } else { d->control->setPlainText(text); } @@ -330,7 +331,7 @@ void QSGTextEdit::setTextFormat(TextFormat format) d->control->setPlainText(d->text); #endif updateSize(); - d->isComplexRichText = QSGTextNode::isComplexRichText(d->document); + d->useImageFallback = qmlEnableImageCache(); } d->format = format; d->control->setAcceptRichText(d->format != PlainText); @@ -1035,9 +1036,8 @@ void QSGTextEdit::componentComplete() Q_D(QSGTextEdit); QSGImplicitSizeItem::componentComplete(); - if (d->richText) { - d->isComplexRichText = QSGTextNode::isComplexRichText(d->document); - } + if (d->richText) + d->useImageFallback = qmlEnableImageCache(); if (d->dirty) { d->determineHorizontalAlignment(); @@ -1429,7 +1429,7 @@ void QSGTextEdit::updateImageCache(const QRectF &) Q_D(QSGTextEdit); // Do we really need the image cache? - if (!d->richText || !d->isComplexRichText) { + if (!d->richText || !d->useImageFallback) { if (!d->pixmapCache.isNull()) d->pixmapCache = QPixmap(); return; @@ -1461,7 +1461,7 @@ QSGNode *QSGTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *upd Q_D(QSGTextEdit); QSGNode *currentNode = oldNode; - if (d->richText && d->isComplexRichText) { + if (d->richText && d->useImageFallback) { QSGImageNode *node = 0; if (oldNode == 0 || d->nodeType != QSGTextEditPrivate::NodeIsTexture) { delete oldNode; @@ -1513,7 +1513,8 @@ QSGNode *QSGTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *upd QColor selectedTextColor = d->control->palette().color(QPalette::HighlightedText); node->addTextDocument(bounds.topLeft(), d->document, d->color, QSGText::Normal, QColor(), selectionColor, selectedTextColor, selectionStart(), - selectionEnd() - 1); + selectionEnd() - 1); // selectionEnd() returns first char after + // selection #if defined(Q_WS_MAC) // We also need to make sure the document layout is redone when diff --git a/src/declarative/items/qsgtextedit_p_p.h b/src/declarative/items/qsgtextedit_p_p.h index 7561977..51904d1 100644 --- a/src/declarative/items/qsgtextedit_p_p.h +++ b/src/declarative/items/qsgtextedit_p_p.h @@ -73,7 +73,7 @@ public: : color("black"), hAlign(QSGTextEdit::AlignLeft), vAlign(QSGTextEdit::AlignTop), documentDirty(true), dirty(false), richText(false), cursorVisible(false), focusOnPress(true), persistentSelection(true), requireImplicitWidth(false), selectByMouse(false), canPaste(false), - hAlignImplicit(true), rightToLeftText(false), isComplexRichText(false), + hAlignImplicit(true), rightToLeftText(false), useImageFallback(false), textMargin(0.0), lastSelectionStart(0), lastSelectionEnd(0), cursorComponent(0), cursor(0), format(QSGTextEdit::AutoText), document(0), wrapMode(QSGTextEdit::NoWrap), mouseSelectionMode(QSGTextEdit::SelectCharacters), @@ -113,7 +113,7 @@ public: bool canPaste:1; bool hAlignImplicit:1; bool rightToLeftText:1; - bool isComplexRichText:1; + bool useImageFallback:1; qreal textMargin; int lastSelectionStart; diff --git a/src/declarative/items/qsgtextnode.cpp b/src/declarative/items/qsgtextnode.cpp index 3af7e54..39fa033 100644 --- a/src/declarative/items/qsgtextnode.cpp +++ b/src/declarative/items/qsgtextnode.cpp @@ -54,10 +54,14 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include #include QT_BEGIN_NAMESPACE @@ -75,6 +79,7 @@ QSGTextNode::QSGTextNode(QSGContext *context) QSGTextNode::~QSGTextNode() { + qDeleteAll(m_textures); } #if 0 @@ -169,16 +174,28 @@ namespace { : selectionState(Unselected) , clipNode(0) , decorations(QSGTextNode::NoDecoration) + , ascent(0.0) , leftChildIndex(-1) , rightChildIndex(-1) { } + BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a) + : boundingRect(brect) + , selectionState(selState) + , clipNode(0) + , decorations(QSGTextNode::NoDecoration) + , image(i) + , ascent(a) + , leftChildIndex(-1) + , rightChildIndex(-1) + { + } + BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect, - const QSGTextNode::Decorations &decs, - const QColor &c, const QColor &bc, - const QPointF &pos) + const QSGTextNode::Decorations &decs, const QColor &c, const QColor &bc, + const QPointF &pos, qreal a) : glyphRun(g) , boundingRect(brect) , selectionState(selState) @@ -187,6 +204,7 @@ namespace { , color(c) , backgroundColor(bc) , position(pos) + , ascent(a) , leftChildIndex(-1) , rightChildIndex(-1) { @@ -200,18 +218,28 @@ namespace { QColor color; QColor backgroundColor; QPointF position; + QImage image; + qreal ascent; int leftChildIndex; int rightChildIndex; static void insert(QVarLengthArray *binaryTree, + const QRectF &rect, + const QImage &image, + qreal ascent, + SelectionState selectionState) + { + insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent)); + } + + static void insert(QVarLengthArray *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState, const QColor &textColor, const QColor &backgroundColor, const QPointF &position) { - int newIndex = binaryTree->size(); QRectF searchRect = glyphRun.boundingRect(); searchRect.translate(position); @@ -224,15 +252,23 @@ namespace { decorations |= (glyphRun.strikeOut() ? QSGTextNode::StrikeOut : QSGTextNode::NoDecoration); decorations |= (backgroundColor.isValid() ? QSGTextNode::Background : QSGTextNode::NoDecoration); - binaryTree->append(BinaryTreeNode(glyphRun, selectionState, searchRect, decorations, - textColor, backgroundColor, position)); + qreal ascent = glyphRun.rawFont().ascent(); + insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations, + textColor, backgroundColor, position, ascent)); + } + + static void insert(QVarLengthArray *binaryTree, + const BinaryTreeNode &binaryTreeNode) + { + int newIndex = binaryTree->size(); + binaryTree->append(binaryTreeNode); if (newIndex == 0) return; int searchIndex = 0; forever { BinaryTreeNode *node = binaryTree->data() + searchIndex; - if (searchRect.left() < node->boundingRect.left()) { + if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) { if (node->leftChildIndex < 0) { node->leftChildIndex = newIndex; break; @@ -285,6 +321,14 @@ namespace { m_currentLine = currentLine; } + void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle, + const QBrush &borderBrush); + void addFrameDecorations(QTextDocument *document, QTextFrame *frame); + void addImage(const QRectF &rect, const QImage &image, qreal ascent, + BinaryTreeNode::SelectionState selectionState); + void addTextObject(const QPointF &position, const QTextCharFormat &format, + BinaryTreeNode::SelectionState selectionState, + QTextDocument *textDocument, int pos); void addSelectedGlyphs(const QGlyphRun &glyphRun); void addUnselectedGlyphs(const QGlyphRun &glyphRun); void addGlyphsInRange(int rangeStart, int rangeEnd, @@ -349,11 +393,14 @@ namespace { QTextLine m_currentLine; bool m_hasSelection; + QList > m_backgrounds; QList m_selectionRects; QVarLengthArray m_currentLineTree; QList m_lines; QVector m_processedNodes; + + QList > m_images; }; void SelectionEngine::addTextDecorations(const QVarLengthArray &textDecorations, @@ -410,7 +457,6 @@ namespace { QVarLengthArray pendingUnderlines; QVarLengthArray pendingOverlines; QVarLengthArray pendingStrikeOuts; - QVarLengthArray pendingBackgrounds; if (!sortedIndexes.isEmpty()) { QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0; bool currentClipNodeUsed = false; @@ -444,11 +490,8 @@ namespace { if (currentDecorations & QSGTextNode::StrikeOut) pendingStrikeOuts.append(textDecoration); - if (currentDecorations & QSGTextNode::Background) { - pendingBackgrounds.append(TextDecoration(BinaryTreeNode::Unselected, - decorationRect, - lastBackgroundColor)); - } + if (currentDecorations & QSGTextNode::Background) + m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor)); } // If we've reached an unselected node from a selected node, we add the @@ -500,13 +543,6 @@ namespace { decorationRect = node->boundingRect; - if (!pendingBackgrounds.isEmpty()) { - addTextDecorations(pendingBackgrounds, -m_currentLine.ascent(), - m_currentLine.height()); - - pendingBackgrounds.clear(); - } - // If previous item(s) had underline and current does not, then we add the // pending lines to the lists and likewise for overlines and strikeouts if (!pendingUnderlines.isEmpty() @@ -567,12 +603,6 @@ namespace { } } - // If there are pending decorations, we need to add them - if (!pendingBackgrounds.isEmpty()) { - addTextDecorations(pendingBackgrounds, -m_currentLine.ascent(), - m_currentLine.height()); - } - if (!pendingUnderlines.isEmpty()) addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness); @@ -588,6 +618,70 @@ namespace { m_hasSelection = false; } + void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent, + BinaryTreeNode::SelectionState selectionState) + { + QRectF searchRect = rect; + if (searchRect.topLeft().isNull()) { + if (m_currentLineTree.isEmpty()) { + searchRect.moveTopLeft(m_position); + } else { + const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1; + if (lastNode->glyphRun.isRightToLeft()) { + QPointF lastPos = lastNode->boundingRect.topLeft(); + searchRect.moveTopRight(lastPos - QPointF(0, ascent)); + } else { + QPointF lastPos = lastNode->boundingRect.topRight(); + searchRect.moveTopLeft(lastPos - QPointF(0, ascent)); + } + } + } + + BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState); + } + + void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format, + BinaryTreeNode::SelectionState selectionState, + QTextDocument *textDocument, int pos) + { + QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType()); + if (handler != 0) { + QImage image; + QSizeF size = handler->intrinsicSize(textDocument, pos, format); + + if (format.objectType() == QTextFormat::ImageObject) { + QTextImageFormat imageFormat = format.toImageFormat(); + QTextImageHandler *imageHandler = static_cast(handler); + image = imageHandler->image(textDocument, imageFormat); + } + + if (image.isNull()) { + image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + { + QPainter painter(&image); + handler->drawObject(&painter, image.rect(), textDocument, pos, format); + } + } + + qreal ascent; + QFontMetrics m(format.font()); + switch (format.verticalAlignment()) + { + case QTextCharFormat::AlignMiddle: + ascent = size.height() / 2 - 1; + break; + case QTextCharFormat::AlignBaseline: + ascent = size.height() - m.descent() - 1; + break; + default: + ascent = size.height() - 1; + } + + addImage(QRectF(position, size), image, ascent, selectionState); + } + } + void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun) { BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected, @@ -661,37 +755,46 @@ namespace { m_backgroundColor = backgroundColor; } - bool hasSelection = selectionStart >= 0 - && selectionEnd >= 0 + bool hasSelection = selectionEnd >= 0 && selectionStart <= selectionEnd; QTextLine &line = m_currentLine; int rangeEnd = rangeStart + rangeLength; if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) { QList glyphRuns = line.glyphRuns(rangeStart, rangeLength); - for (int j=0; j glyphRuns = line.glyphRuns(rangeStart, qMin(selectionStart - rangeStart, rangeLength)); - for (int j=0; j= selectionStart && selectionStart >= rangeStart) { - QList glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1); + if (rangeEnd > selectionStart) { + int start = qMax(selectionStart, rangeStart); + int length = qMin(selectionEnd - start + 1, rangeEnd - start); + QList glyphRuns = line.glyphRuns(start, length); - for (int j=0; j= rangeStart && selectionEnd < rangeEnd) { - QList glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd); - for (int j=0; j glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1); + for (int j=0; j(document->documentLayout()); + QTextFrameFormat frameFormat = frame->format().toFrameFormat(); + + QTextTable *table = qobject_cast(frame); + QRectF boundingRect = table == 0 + ? documentLayout->frameBoundingRect(frame) + : documentLayout->tableBoundingRect(table); + + QBrush bg = frame->frameFormat().background(); + if (bg.style() != Qt::NoBrush) + m_backgrounds.append(qMakePair(boundingRect, bg.color())); + + if (!frameFormat.hasProperty(QTextFormat::FrameBorder)) + return; + + qreal borderWidth = frameFormat.border(); + if (qFuzzyIsNull(borderWidth)) + return; + + QBrush borderBrush = frameFormat.borderBrush(); + QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle(); + if (borderStyle == QTextFrameFormat::BorderStyle_None) + return; + + addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(), + -frameFormat.rightMargin(), -frameFormat.bottomMargin()), + borderWidth, borderStyle, borderBrush); + if (table != 0) { + int rows = table->rows(); + int columns = table->columns(); + + for (int row=0; rowcellAt(row, column); + + QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell); + addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth, + borderStyle, borderBrush); + } + } + } + } + void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode, QSGText::TextStyle style, const QColor &styleColor) @@ -709,6 +872,14 @@ namespace { if (m_currentLine.isValid()) processCurrentLine(); + + for (int i=0; iappendChildNode(new QSGSimpleRectNode(rect, color)); + } + // First, prepend all selection rectangles to the tree for (int i=0; iglyphRun; - QRawFont rawFont = glyphRun.rawFont(); - QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont); + if (node->image.isNull()) { + QGlyphRun glyphRun = node->glyphRun; + QRawFont rawFont = glyphRun.rawFont(); + QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont); - QFontEngine *fontEngine = rawFontD->fontEngine; + QFontEngine *fontEngine = rawFontD->fontEngine; - KeyType key(qMakePair(fontEngine, - qMakePair(node->clipNode, - qMakePair(node->color.rgba(), int(node->selectionState))))); + KeyType key(qMakePair(fontEngine, + qMakePair(node->clipNode, + qMakePair(node->color.rgba(), int(node->selectionState))))); - BinaryTreeNode *otherNode = map.value(key, 0); - if (otherNode != 0) { - QGlyphRun &otherGlyphRun = otherNode->glyphRun; + BinaryTreeNode *otherNode = map.value(key, 0); + if (otherNode != 0) { + QGlyphRun &otherGlyphRun = otherNode->glyphRun; - QVector otherGlyphIndexes = otherGlyphRun.glyphIndexes(); - QVector otherGlyphPositions = otherGlyphRun.positions(); + QVector otherGlyphIndexes = otherGlyphRun.glyphIndexes(); + QVector otherGlyphPositions = otherGlyphRun.positions(); - otherGlyphIndexes += glyphRun.glyphIndexes(); + otherGlyphIndexes += glyphRun.glyphIndexes(); - QVector glyphPositions = glyphRun.positions(); - for (int j=0; jposition - otherNode->position); - } + QVector glyphPositions = glyphRun.positions(); + for (int j=0; jposition - otherNode->position); + } - otherGlyphRun.setGlyphIndexes(otherGlyphIndexes); - otherGlyphRun.setPositions(otherGlyphPositions); + otherGlyphRun.setGlyphIndexes(otherGlyphIndexes); + otherGlyphRun.setPositions(otherGlyphPositions); + } else { + map.insert(key, node); + } } else { - map.insert(key, node); + parentNode->addImage(node->boundingRect, node->image); + if (node->selectionState == BinaryTreeNode::Selected) { + QColor color = m_selectionColor; + color.setAlpha(128); + parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, + color)); + } } } @@ -840,77 +1021,197 @@ void QSGTextNode::mergeFormats(QTextLayout *textLayout, } +namespace { + + class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout + { + public: + inline QTextCharFormat formatAccessor(int pos) + { + return format(pos); + } + }; + +} + +void QSGTextNode::addImage(const QRectF &rect, const QImage &image) +{ + QSGImageNode *node = m_context->createImageNode(); + QSGTexture *texture = m_context->createTexture(image); + m_textures.append(texture); + node->setTargetRect(rect); + node->setTexture(texture); + appendChildNode(node); + node->update(); +} + void QSGTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument, const QColor &textColor, QSGText::TextStyle style, const QColor &styleColor, const QColor &selectionColor, const QColor &selectedTextColor, int selectionStart, int selectionEnd) { - QTextFrame *textFrame = textDocument->rootFrame(); - QPointF position = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft(); - SelectionEngine engine; engine.setTextColor(textColor); engine.setSelectedTextColor(selectedTextColor); engine.setSelectionColor(selectionColor); - QTextFrame::iterator it = textFrame->begin(); - while (!it.atEnd()) { - Q_ASSERT(!engine.currentLine().isValid()); + QList frames; + frames.append(textDocument->rootFrame()); + while (!frames.isEmpty()) { + QTextFrame *textFrame = frames.takeFirst(); + frames.append(textFrame->childFrames()); - QTextBlock block = it.currentBlock(); - int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0; - int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1; + engine.addFrameDecorations(textDocument, textFrame); - QVarLengthArray colorChanges; - mergeFormats(block.layout(), &colorChanges); + if (textFrame->firstPosition() > textFrame->lastPosition() + && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) { + const int pos = textFrame->firstPosition() - 1; + ProtectedLayoutAccessor *a = static_cast(textDocument->documentLayout()); + QTextCharFormat format = a->formatAccessor(pos); + QRectF rect = a->frameBoundingRect(textFrame); - QTextBlock::iterator blockIterator = block.begin(); - int textPos = block.position(); - while (!blockIterator.atEnd()) { - QTextFragment fragment = blockIterator.fragment(); - if (fragment.text().isEmpty()) - continue; + engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument, + pos); + } else { + QTextFrame::iterator it = textFrame->begin(); + + while (!it.atEnd()) { + Q_ASSERT(!engine.currentLine().isValid()); + + QTextBlock block = it.currentBlock(); + int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0; + int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1; + + QVarLengthArray colorChanges; + mergeFormats(block.layout(), &colorChanges); + + QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft(); + if (QTextList *textList = block.textList()) { + QPointF pos = blockPosition; + QTextLayout *layout = block.layout(); + if (layout->lineCount() > 0) { + QTextLine firstLine = layout->lineAt(0); + Q_ASSERT(firstLine.isValid()); + + engine.setCurrentLine(firstLine); + + QRectF textRect = firstLine.naturalTextRect(); + pos += textRect.topLeft(); + if (block.textDirection() == Qt::RightToLeft) + pos.rx() += textRect.width(); + + const QTextCharFormat charFormat = block.charFormat(); + QFont font(charFormat.font()); + QFontMetricsF fontMetrics(font); + QTextListFormat listFormat = textList->format(); + + QString listItemBullet; + switch (listFormat.style()) { + case QTextListFormat::ListCircle: + listItemBullet = QChar(0x25E6); // White bullet + break; + case QTextListFormat::ListSquare: + listItemBullet = QChar(0x25AA); // Black small square + break; + case QTextListFormat::ListDecimal: + case QTextListFormat::ListLowerAlpha: + case QTextListFormat::ListUpperAlpha: + case QTextListFormat::ListLowerRoman: + case QTextListFormat::ListUpperRoman: + listItemBullet = textList->itemText(block); + break; + default: + listItemBullet = QChar(0x2022); // Black bullet + break; + }; + + QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height()); + qreal xoff = fontMetrics.width(QLatin1Char(' ')); + if (block.textDirection() == Qt::LeftToRight) + xoff = -xoff - size.width(); + engine.setPosition(pos + QPointF(xoff, 0)); + + QTextLayout layout; + layout.setFont(font); + layout.setText(listItemBullet); // Bullet + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setPosition(QPointF(0, 0)); + layout.endLayout(); + + QList glyphRuns = layout.glyphRuns(); + for (int i=0; idocumentLayout()->blockBoundingRect(block).topLeft(); - engine.setPosition(position + blockPosition); + int textPos = block.position(); + QTextBlock::iterator blockIterator = block.begin(); + while (!blockIterator.atEnd()) { + QTextFragment fragment = blockIterator.fragment(); + QString text = fragment.text(); + if (text.isEmpty()) + continue; + + QTextCharFormat charFormat = fragment.charFormat(); + engine.setPosition(blockPosition); + if (text.contains(QChar::ObjectReplacementCharacter)) { + QTextFrame *frame = qobject_cast(textDocument->objectForFormat(charFormat)); + if (frame) { + BinaryTreeNode::SelectionState selectionState = + (selectionStart < textPos + text.length() + && selectionEnd >= textPos) + ? BinaryTreeNode::Selected + : BinaryTreeNode::Unselected; + + engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos); + } + textPos += text.length(); + } else { + if (!textColor.isValid()) + engine.setTextColor(charFormat.foreground().color()); + + int fragmentEnd = textPos + fragment.length(); + if (preeditPosition >= 0 + && preeditPosition >= textPos + && preeditPosition < fragmentEnd) { + fragmentEnd += preeditLength; + } - QTextCharFormat charFormat = fragment.charFormat(); - if (!textColor.isValid()) - engine.setTextColor(charFormat.foreground().color()); + while (textPos < fragmentEnd) { + int blockRelativePosition = textPos - block.position(); + QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition); + if (!engine.currentLine().isValid() + || line.lineNumber() != engine.currentLine().lineNumber()) { + engine.setCurrentLine(line); + } - int fragmentEnd = textPos + fragment.length(); - if (preeditPosition >= 0 - && preeditPosition >= textPos - && preeditPosition < fragmentEnd) { - fragmentEnd += preeditLength; - } + Q_ASSERT(line.textLength() > 0); + int lineEnd = line.textStart() + block.position() + line.textLength(); - while (textPos < fragmentEnd) { - int blockRelativePosition = textPos - block.position(); - QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition); - Q_ASSERT(line.textLength() > 0); - if (!engine.currentLine().isValid() || line.lineNumber() != engine.currentLine().lineNumber()) - engine.setCurrentLine(line); + int len = qMin(lineEnd - textPos, fragmentEnd - textPos); + Q_ASSERT(len > 0); - int lineEnd = line.textStart() + block.position() + line.textLength(); + int currentStepEnd = textPos + len; - int len = qMin(lineEnd - textPos, fragmentEnd - textPos); - Q_ASSERT(len > 0); + engine.addGlyphsForRanges(colorChanges, + textPos - block.position(), + currentStepEnd - block.position(), + selectionStart - block.position(), + selectionEnd - block.position()); - int currentStepEnd = textPos + len; + textPos = currentStepEnd; + } + } - engine.addGlyphsForRanges(colorChanges, textPos, currentStepEnd, - selectionStart, selectionEnd); + ++blockIterator; + } - textPos = currentStepEnd; + engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed + ++it; } - - ++blockIterator; } - - engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed - ++it; } engine.addToSceneGraph(this, style, styleColor); @@ -953,143 +1254,6 @@ void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout engine.addToSceneGraph(this, style, styleColor); } - -/*! - Returns true if \a text contains any HTML tags, attributes or CSS properties which are unrelated - to text, fonts or text layout. Otherwise the function returns false. If the return value is - false, \a text is considered to be easily representable in the scenegraph. If it returns true, - then the text should be prerendered into a pixmap before it's displayed on screen. -*/ -bool QSGTextNode::isComplexRichText(QTextDocument *doc) -{ - if (doc == 0) - return false; - - static QSet supportedTags; - if (supportedTags.isEmpty()) { - supportedTags.insert(QLatin1String("i")); - supportedTags.insert(QLatin1String("b")); - supportedTags.insert(QLatin1String("u")); - supportedTags.insert(QLatin1String("div")); - supportedTags.insert(QLatin1String("big")); - supportedTags.insert(QLatin1String("blockquote")); - supportedTags.insert(QLatin1String("body")); - supportedTags.insert(QLatin1String("br")); - supportedTags.insert(QLatin1String("center")); - supportedTags.insert(QLatin1String("cite")); - supportedTags.insert(QLatin1String("code")); - supportedTags.insert(QLatin1String("tt")); - supportedTags.insert(QLatin1String("dd")); - supportedTags.insert(QLatin1String("dfn")); - supportedTags.insert(QLatin1String("em")); - supportedTags.insert(QLatin1String("font")); - supportedTags.insert(QLatin1String("h1")); - supportedTags.insert(QLatin1String("h2")); - supportedTags.insert(QLatin1String("h3")); - supportedTags.insert(QLatin1String("h4")); - supportedTags.insert(QLatin1String("h5")); - supportedTags.insert(QLatin1String("h6")); - supportedTags.insert(QLatin1String("head")); - supportedTags.insert(QLatin1String("html")); - supportedTags.insert(QLatin1String("meta")); - supportedTags.insert(QLatin1String("nobr")); - supportedTags.insert(QLatin1String("p")); - supportedTags.insert(QLatin1String("pre")); - supportedTags.insert(QLatin1String("qt")); - supportedTags.insert(QLatin1String("s")); - supportedTags.insert(QLatin1String("samp")); - supportedTags.insert(QLatin1String("small")); - supportedTags.insert(QLatin1String("span")); - supportedTags.insert(QLatin1String("strong")); - supportedTags.insert(QLatin1String("sub")); - supportedTags.insert(QLatin1String("sup")); - supportedTags.insert(QLatin1String("title")); - supportedTags.insert(QLatin1String("var")); - supportedTags.insert(QLatin1String("style")); - } - - static QSet supportedCssProperties; - if (supportedCssProperties.isEmpty()) { - supportedCssProperties.insert(QCss::Color); - supportedCssProperties.insert(QCss::Float); - supportedCssProperties.insert(QCss::Font); - supportedCssProperties.insert(QCss::FontFamily); - supportedCssProperties.insert(QCss::FontSize); - supportedCssProperties.insert(QCss::FontStyle); - supportedCssProperties.insert(QCss::FontWeight); - supportedCssProperties.insert(QCss::Margin); - supportedCssProperties.insert(QCss::MarginBottom); - supportedCssProperties.insert(QCss::MarginLeft); - supportedCssProperties.insert(QCss::MarginRight); - supportedCssProperties.insert(QCss::MarginTop); - supportedCssProperties.insert(QCss::TextDecoration); - supportedCssProperties.insert(QCss::TextIndent); - supportedCssProperties.insert(QCss::TextUnderlineStyle); - supportedCssProperties.insert(QCss::VerticalAlignment); - supportedCssProperties.insert(QCss::Whitespace); - supportedCssProperties.insert(QCss::Padding); - supportedCssProperties.insert(QCss::PaddingLeft); - supportedCssProperties.insert(QCss::PaddingRight); - supportedCssProperties.insert(QCss::PaddingTop); - supportedCssProperties.insert(QCss::PaddingBottom); - supportedCssProperties.insert(QCss::PageBreakBefore); - supportedCssProperties.insert(QCss::PageBreakAfter); - supportedCssProperties.insert(QCss::Width); - supportedCssProperties.insert(QCss::Height); - supportedCssProperties.insert(QCss::MinimumWidth); - supportedCssProperties.insert(QCss::MinimumHeight); - supportedCssProperties.insert(QCss::MaximumWidth); - supportedCssProperties.insert(QCss::MaximumHeight); - supportedCssProperties.insert(QCss::Left); - supportedCssProperties.insert(QCss::Right); - supportedCssProperties.insert(QCss::Top); - supportedCssProperties.insert(QCss::Bottom); - supportedCssProperties.insert(QCss::Position); - supportedCssProperties.insert(QCss::TextAlignment); - supportedCssProperties.insert(QCss::FontVariant); - } - - QXmlStreamReader reader(doc->toHtml("utf-8")); - while (!reader.atEnd()) { - reader.readNext(); - - if (reader.isStartElement()) { - if (!supportedTags.contains(reader.name().toString().toLower())) - return true; - - QXmlStreamAttributes attributes = reader.attributes(); - if (attributes.hasAttribute(QLatin1String("bgcolor"))) - return true; - if (attributes.hasAttribute(QLatin1String("style"))) { - QCss::StyleSheet styleSheet; - QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet); - - QVector decls; - for (int i=0; i styleRules = - styleSheet.styleRules - + styleSheet.idIndex.values().toVector() - + styleSheet.nameIndex.values().toVector(); - for (int i=0; ipropertyId)) - return true; - } - - } - } - } - - return reader.hasError(); -} - void QSGTextNode::deleteContent() { while (firstChild() != 0) diff --git a/src/declarative/items/qsgtextnode_p.h b/src/declarative/items/qsgtextnode_p.h index b8e8cd6..b49a8a1 100644 --- a/src/declarative/items/qsgtextnode_p.h +++ b/src/declarative/items/qsgtextnode_p.h @@ -60,6 +60,7 @@ class QSGContext; class QRawFont; class QSGSimpleRectNode; class QSGClipNode; +class QSGTexture; class QSGTextNode : public QSGTransformNode { @@ -94,12 +95,14 @@ public: QSGGlyphNode *addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color, QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(), QSGNode *parentNode = 0); + void addImage(const QRectF &rect, const QImage &image); private: void mergeFormats(QTextLayout *textLayout, QVarLengthArray *mergedFormats); QSGContext *m_context; QSGSimpleRectNode *m_cursorNode; + QList m_textures; }; QT_END_NAMESPACE -- 2.7.4