Support inline images with <img> tag in StyledText

Task-number: QTBUG-21793
Change-Id: Ie7b9f293c6c9a949c1899152c38b61251b0069d3
Reviewed-by: Yann Bodson <yann.bodson@nokia.com>
This commit is contained in:
Yann Bodson 2011-11-28 11:26:40 +10:00 committed by Qt by Nokia
parent 76ed62fb83
commit 8872c0716f
21 changed files with 620 additions and 59 deletions

View File

@ -0,0 +1,55 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.0
Text {
width: parent.width
font.pointSize: 14
wrapMode: Text.WordWrap
textFormat: Text.StyledText
horizontalAlignment: main.hAlign
Rectangle {
border.color: "#efefef"
color: "transparent"
anchors.fill: parent
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,101 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.0
Rectangle {
id: main
width: 640; height: 800
focus: true
property var hAlign: Text.AlignLeft
Flickable {
anchors.fill: parent
contentWidth: parent.width
contentHeight: col.height + 20
Column {
id: col
x: 10; y: 10
spacing: 20
width: parent.width - 20
TextWithImage {
text: "This is a <b>happy</b> face<img src=\"images/face-smile.png\">"
}
TextWithImage {
text: "This is a <b>very<img src=\"images/face-smile-big.png\" align=\"middle\"/>happy</b> face aligned in the middle."
}
TextWithImage {
elide: Text.ElideRight
maximumLineCount: 2
text: "This is a sad face aligned to the top. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc rutrum dui pretium ipsum malesuada venenatis. Nulla sed turpis risus. Integer sit amet odio quis mauris varius venenatis<img src=\"images/face-sad.png\" align=\"top\">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc rutrum dui pretium ipsum malesuada venenatis. Nulla sed turpis risus. Integer sit amet odio quis mauris varius venenatis."
}
TextWithImage {
text: "This is a tiny<img src=\"images/face-smile.png\" width=\"15\" height=\"15\">happy face."
}
TextWithImage {
text: "This is a starfish<img src=\"images/starfish_2.png\" width=\"50\" height=\"50\" align=\"top\">aligned to the top and another one<img src=\"images/heart200.png\" width=\"50\" height=\"50\">aligned to the bottom."
}
TextWithImage {
text: "Qt logos<img src=\"images/qtlogo.png\" width=\"55\" height=\"60\" align=\"middle\"><img src=\"images/qtlogo.png\" width=\"37\" height=\"40\" align=\"middle\"><img src=\"images/qtlogo.png\" width=\"18\" height=\"20\" align=\"middle\">aligned in the middle with different sizes."
}
TextWithImage {
text: "Some hearts<img src=\"images/heart200.png\" width=\"20\" height=\"20\" align=\"bottom\"><img src=\"images/heart200.png\" width=\"30\" height=\"30\" align=\"bottom\"> <img src=\"images/heart200.png\" width=\"40\" height=\"40\"><img src=\"images/heart200.png\" width=\"50\" height=\"50\" align=\"bottom\">with different sizes."
}
TextWithImage {
text: "Resized image<img width=\"80\" height=\"76\" align=\"middle\" src=\"http://files.app4mobile.com/wp-content/uploads/2011/08/nokia-n9-price-specification-features-us-europe-india.jpg\">from the internet."
}
TextWithImage {
text: "Image<img align=\"middle\" src=\"http://qt.gitorious.org/images/sites/qt/logo.png\">from the internet."
}
TextWithImage {
height: 120
verticalAlignment: Text.AlignVCenter
text: "This is a <b>happy</b> face<img src=\"images/face-smile.png\"> with an explicit height."
}
}
}
Keys.onUpPressed: main.hAlign = Text.AlignHCenter
Keys.onLeftPressed: main.hAlign = Text.AlignLeft
Keys.onRightPressed: main.hAlign = Text.AlignRight
}

View File

@ -3,37 +3,36 @@
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**

View File

@ -58,12 +58,13 @@
#include <QtGui/qguiapplication.h>
#include <QtGui/qinputpanel.h>
#include <private/qdeclarativestyledtext_p.h>
#include <QtQuick/private/qdeclarativepixmapcache_p.h>
#include <qmath.h>
#include <limits.h>
DEFINE_BOOL_CONFIG_OPTION(qmlTextDebug, QML_TEXT_DEBUG)
QT_BEGIN_NAMESPACE
extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled;
@ -85,12 +86,12 @@ QQuickTextPrivate::QQuickTextPrivate()
disableDistanceField(false), internalWidthUpdate(false),
requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false),
layoutTextElided(false), richTextAsImage(false), textureImageCacheDirty(false), textHasChanged(true),
naturalWidth(0), doc(0), elipsisLayout(0), textLine(0), nodeType(NodeIsNull), updateType(UpdatePaintNode)
needToUpdateLayout(false), naturalWidth(0), doc(0), elipsisLayout(0), textLine(0), nodeType(NodeIsNull),
updateType(UpdatePaintNode), nbActiveDownloads(0)
#if defined(Q_OS_MAC)
, layoutThread(0), paintingThread(0)
#endif
{
cacheAllTextAsImage = enableImageCache();
disableDistanceField = qmlDisableDistanceField();
@ -266,6 +267,8 @@ QQuickTextPrivate::~QQuickTextPrivate()
delete elipsisLayout;
delete textLine; textLine = 0;
delete imageCache;
qDeleteAll(imgTags);
imgTags.clear();
}
qreal QQuickTextPrivate::getImplicitWidth() const
@ -295,6 +298,11 @@ void QQuickTextPrivate::updateLayout()
}
updateOnComponentComplete = false;
layoutTextElided = false;
if (!visibleImgTags.isEmpty())
visibleImgTags.clear();
needToUpdateLayout = false;
// Setup instance of QTextLayout for all cases other than richtext
if (!richText) {
if (elipsisLayout) {
@ -329,7 +337,7 @@ void QQuickTextPrivate::updateLayout()
} else {
singleline = false;
if (textHasChanged) {
QDeclarativeStyledText::parse(text, layout);
QDeclarativeStyledText::parse(text, layout, imgTags, qmlContext(q), !maximumLineCountValid);
textHasChanged = false;
}
}
@ -346,6 +354,41 @@ void QQuickTextPrivate::updateLayout()
}
updateSize();
if (needToUpdateLayout) {
needToUpdateLayout = false;
textHasChanged = true;
updateLayout();
}
}
void QQuickText::imageDownloadFinished()
{
Q_D(QQuickText);
(d->nbActiveDownloads)--;
// when all the remote images have been downloaded,
// if one of the sizes was not specified at parsing time
// we use the implicit size from pixmapcache and re-layout.
if (d->nbActiveDownloads == 0) {
bool needToUpdateLayout = false;
foreach (QDeclarativeStyledTextImgTag *img, d->visibleImgTags) {
if (!img->size.isValid()) {
img->size = img->pix->implicitSize();
needToUpdateLayout = true;
}
}
if (needToUpdateLayout) {
d->textHasChanged = true;
d->updateLayout();
} else {
d->updateType = QQuickTextPrivate::UpdatePaintNode;
update();
}
}
}
void QQuickTextPrivate::updateSize()
@ -659,6 +702,7 @@ QRect QQuickTextPrivate::setupTextLayout()
lineWidth = INT_MAX;
int linesLeft = maximumLineCount;
int visibleTextLength = 0;
forever {
QTextLine line = layout.createLine();
if (!line.isValid())
@ -667,13 +711,10 @@ QRect QQuickTextPrivate::setupTextLayout()
visibleCount++;
qreal preLayoutHeight = height;
if (customLayout) {
if (customLayout)
setupCustomLineGeometry(line, height);
} else if (lineWidth) {
line.setLineWidth(lineWidth);
line.setPosition(QPointF(line.position().x(), height));
height += (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : line.height() * lineHeight;
}
else if (lineWidth)
setLineGeometry(line, lineWidth, height);
bool elide = false;
if (multilineElideEnabled && q->heightValid() && height > q->height()) {
@ -682,7 +723,7 @@ QRect QQuickTextPrivate::setupTextLayout()
if (visibleCount > 1) {
--visibleCount;
height = preLayoutHeight;
line.setLineWidth(0.0);
setLineGeometry(line, 0.0, height);
line.setPosition(QPointF(FLT_MAX,FLT_MAX));
line = layout.lineAt(visibleCount-1);
}
@ -693,13 +734,14 @@ QRect QQuickTextPrivate::setupTextLayout()
if (elide || (maximumLineCountValid && --linesLeft == 0)) {
if (visibleTextLength < text.length()) {
truncate = true;
height = preLayoutHeight;
if (multilineElideEnabled) {
qreal elideWidth = fm.width(elideChar);
// Need to correct for alignment
if (customLayout)
setupCustomLineGeometry(line, height, elideWidth);
else
line.setLineWidth(lineWidth - elideWidth);
setLineGeometry(line, lineWidth - elideWidth, height);
if (layout.text().mid(line.textStart(), line.textLength()).isRightToLeft()) {
line.setPosition(QPointF(line.position().x() + elideWidth, line.position().y()));
elidePos.setX(line.naturalTextRect().left() - elideWidth);
@ -722,6 +764,7 @@ QRect QQuickTextPrivate::setupTextLayout()
br = br.united(line.naturalTextRect());
}
layout.endLayout();
br.moveTop(0);
//Update truncated
if (truncated != truncate) {
@ -740,10 +783,71 @@ QRect QQuickTextPrivate::setupTextLayout()
lineCount = visibleCount;
emit q->lineCountChanged();
}
return QRect(qRound(br.x()), qRound(br.y()), qCeil(br.width()), qCeil(br.height()));
}
void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal &height)
{
Q_Q(QQuickText);
line.setLineWidth(lineWidth);
if (imgTags.isEmpty()) {
line.setPosition(QPointF(line.position().x(), height));
height += (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : line.height() * lineHeight;
return;
}
qreal textTop = 0;
qreal textHeight = line.height();
qreal totalLineHeight = textHeight;
QList<QDeclarativeStyledTextImgTag *> imagesInLine;
foreach (QDeclarativeStyledTextImgTag *image, imgTags) {
if (image->position >= line.textStart() &&
image->position < line.textStart() + line.textLength()) {
if (!image->pix) {
QUrl url = qmlContext(q)->resolvedUrl(image->url);
image->pix = new QDeclarativePixmap(qmlEngine(q), url, image->size);
if (image->pix->isLoading()) {
image->pix->connectFinished(q, SLOT(imageDownloadFinished()));
nbActiveDownloads++;
} else if (image->pix->isReady()) {
if (!image->size.isValid()) {
image->size = image->pix->implicitSize();
// if the size of the image was not explicitly set, we need to
// call updateLayout() once again.
needToUpdateLayout = true;
}
} else if (image->pix->isError()) {
qmlInfo(q) << image->pix->error();
}
}
qreal ih = qreal(image->size.height());
if (image->align == QDeclarativeStyledTextImgTag::Top)
image->pos.setY(0);
else if (image->align == QDeclarativeStyledTextImgTag::Middle)
image->pos.setY((textHeight / 2.0) - (ih / 2.0));
else
image->pos.setY(textHeight - ih);
imagesInLine << image;
textTop = qMax(textTop, qAbs(image->pos.y()));
}
}
foreach (QDeclarativeStyledTextImgTag *image, imagesInLine) {
totalLineHeight = qMax(totalLineHeight, textTop + image->pos.y() + image->size.height());
image->pos.setX(line.cursorToX(image->position));
image->pos.setY(image->pos.y() + height + textTop);
visibleImgTags << image;
}
line.setPosition(QPointF(line.position().x(), height + textTop));
height += (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : totalLineHeight * lineHeight;
}
/*!
Returns a painted version of the QQuickTextPrivate::layout QTextLayout.
If \a drawStyle is true, the style color overrides all colors in the document.
@ -1251,6 +1355,8 @@ void QQuickText::setText(const QString &n)
d->determineHorizontalAlignment();
}
d->textHasChanged = true;
qDeleteAll(d->imgTags);
d->imgTags.clear();
d->updateLayout();
emit textChanged(d->text);
}
@ -1629,6 +1735,7 @@ void QQuickText::resetMaximumLineCount()
<font color="color_name" size="1-7"></font>
<h1> to <h6> - headers
<a href=""> - anchor
<img src="" align="top,middle,bottom" width="" height=""> - inline images
<ol type="">, <ul type=""> and <li> - ordered and unordered lists
<pre></pre> - preformatted
&gt; &lt; &amp;
@ -1947,6 +2054,24 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data
node->addTextLayout(QPoint(0, bounds.y()), d->elipsisLayout, d->color, d->style, d->styleColor);
}
foreach (QDeclarativeStyledTextImgTag *img, d->visibleImgTags) {
if (qmlTextDebug()) {
QSGRectangleNode *rectangle = d->sceneGraphContext()->createRectangleNode();
rectangle->setRect(QRectF(img->pos.x(), img->pos.y() + bounds.y(),img->size.width(), img->size.height()));
rectangle->setColor(QColor("red"));
rectangle->update();
node->appendChildNode(rectangle);
}
QDeclarativePixmap *pix = img->pix;
if (pix && pix->isReady()) {
QSGTexture *texture = d->sceneGraphContext()->textureForFactory(pix->textureFactory());
QSGImageNode *imgnode = d->sceneGraphContext()->createImageNode();
imgnode->setTexture(texture);
imgnode->setTargetRect(QRectF(img->pos.x(), img->pos.y() + bounds.y(), pix->width(), pix->height()));
node->appendChildNode(imgnode);
imgnode->update();
}
}
return node;
}
}

View File

@ -44,9 +44,7 @@
#define QQUICKTEXT_P_H
#include "qquickimplicitsizeitem_p.h"
#include <private/qtquickglobal_p.h>
#include <QtGui/qtextoption.h>
QT_BEGIN_HEADER
@ -212,6 +210,7 @@ protected:
private Q_SLOTS:
void q_imagesLoaded();
void triggerPreprocess();
void imageDownloadFinished();
private:
Q_DISABLE_COPY(QQuickText)

View File

@ -60,6 +60,7 @@
#include <QtDeclarative/qdeclarative.h>
#include <QtGui/qabstracttextdocumentlayout.h>
#include <QtGui/qtextlayout.h>
#include <private/qdeclarativestyledtext_p.h>
QT_BEGIN_NAMESPACE
@ -82,6 +83,7 @@ public:
void mirrorChange();
QTextDocument *textDocument();
bool isLineLaidOutConnected();
void setLineGeometry(QTextLine &line, qreal lineWidth, qreal &height);
QString text;
QUrl baseUrl;
@ -127,6 +129,7 @@ public:
bool richTextAsImage:1;
bool textureImageCacheDirty:1;
bool textHasChanged:1;
bool needToUpdateLayout:1;
QRect layedOutTextRect;
QSize paintedSize;
@ -168,6 +171,10 @@ public:
};
UpdateType updateType;
QList<QDeclarativeStyledTextImgTag*> imgTags;
QList<QDeclarativeStyledTextImgTag*> visibleImgTags;
int nbActiveDownloads;
#if defined(Q_OS_MAC)
QList<QRectF> linesRects;
QThread *layoutThread;

View File

@ -46,6 +46,7 @@
#include <QDebug>
#include <qmath.h>
#include "qdeclarativestyledtext_p.h"
#include <QDeclarativeContext>
/*
QDeclarativeStyledText supports few tags:
@ -61,6 +62,7 @@
<a href=""> - anchor
<ol type="">, <ul type=""> and <li> - ordered and unordered lists
<pre></pre> - preformated
<img src=""> - images
The opening and closing tags must be correctly nested.
*/
@ -79,9 +81,12 @@ public:
ListFormat format;
};
QDeclarativeStyledTextPrivate(const QString &t, QTextLayout &l)
: text(t), layout(l), baseFont(layout.font()), hasNewLine(false)
, preFormat(false), prependSpace(false), hasSpace(true)
QDeclarativeStyledTextPrivate(const QString &t, QTextLayout &l,
QList<QDeclarativeStyledTextImgTag*> &imgTags,
QDeclarativeContext *context,
bool preloadImages)
: text(t), layout(l), imgTags(&imgTags), baseFont(layout.font()), hasNewLine(false), nbImages(0), updateImagePositions(false)
, preFormat(false), prependSpace(false), hasSpace(true), preloadImages(preloadImages), context(context)
{
}
@ -94,6 +99,7 @@ public:
bool parseOrderedListAttributes(const QChar *&ch, const QString &textIn);
bool parseUnorderedListAttributes(const QChar *&ch, const QString &textIn);
bool parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format);
void parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut);
QPair<QStringRef,QStringRef> parseAttribute(const QChar *&ch, const QString &textIn);
QStringRef parseValue(const QChar *&ch, const QString &textIn);
@ -108,12 +114,17 @@ public:
QString text;
QTextLayout &layout;
QList<QDeclarativeStyledTextImgTag*> *imgTags;
QFont baseFont;
QStack<List> listStack;
bool hasNewLine;
int nbImages;
bool updateImagePositions;
bool preFormat;
bool prependSpace;
bool hasSpace;
bool preloadImages;
QDeclarativeContext *context;
static const QChar lessThan;
static const QChar greaterThan;
@ -143,8 +154,10 @@ const QChar QDeclarativeStyledTextPrivate::square(0x25a1);
const QChar QDeclarativeStyledTextPrivate::lineFeed(QLatin1Char('\n'));
const QChar QDeclarativeStyledTextPrivate::space(QLatin1Char(' '));
QDeclarativeStyledText::QDeclarativeStyledText(const QString &string, QTextLayout &layout)
: d(new QDeclarativeStyledTextPrivate(string, layout))
QDeclarativeStyledText::QDeclarativeStyledText(const QString &string, QTextLayout &layout,
QList<QDeclarativeStyledTextImgTag*> &imgTags, QDeclarativeContext *context,
bool preloadImages)
: d(new QDeclarativeStyledTextPrivate(string, layout, imgTags, context, preloadImages))
{
}
@ -153,11 +166,13 @@ QDeclarativeStyledText::~QDeclarativeStyledText()
delete d;
}
void QDeclarativeStyledText::parse(const QString &string, QTextLayout &layout)
void QDeclarativeStyledText::parse(const QString &string, QTextLayout &layout,
QList<QDeclarativeStyledTextImgTag*> &imgTags, QDeclarativeContext *context,
bool preloadImages)
{
if (string.isEmpty())
return;
QDeclarativeStyledText styledText(string, layout);
QDeclarativeStyledText styledText(string, layout, imgTags, context, preloadImages);
styledText.d->parse();
}
@ -169,6 +184,8 @@ void QDeclarativeStyledTextPrivate::parse()
QString drawText;
drawText.reserve(text.count());
updateImagePositions = !imgTags->isEmpty();
int textStart = 0;
int textLength = 0;
int rangeStart = 0;
@ -401,6 +418,10 @@ bool QDeclarativeStyledTextPrivate::parseTag(const QChar *&ch, const QString &te
if (tag == QLatin1String("a")) {
return parseAnchorAttributes(ch, textIn, format);
}
if (tag == QLatin1String("img")) {
parseImageAttributes(ch, textIn, textOut);
return false;
}
if (*ch == greaterThan || ch->isNull())
continue;
} else if (*ch != slash) {
@ -606,6 +627,69 @@ bool QDeclarativeStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, cons
return valid;
}
void QDeclarativeStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut)
{
qreal imgWidth = 0.0;
if (!updateImagePositions) {
QDeclarativeStyledTextImgTag *image = new QDeclarativeStyledTextImgTag;
image->position = textOut.length() + 1;
QPair<QStringRef,QStringRef> attr;
do {
attr = parseAttribute(ch, textIn);
if (attr.first == QLatin1String("src")) {
image->url = QUrl(attr.second.toString());
} else if (attr.first == QLatin1String("width")) {
image->size.setWidth(attr.second.toString().toInt());
} else if (attr.first == QLatin1String("height")) {
image->size.setHeight(attr.second.toString().toInt());
} else if (attr.first == QLatin1String("align")) {
if (attr.second.toString() == QLatin1String("top")) {
image->align = QDeclarativeStyledTextImgTag::Top;
} else if (attr.second.toString() == QLatin1String("middle")) {
image->align = QDeclarativeStyledTextImgTag::Middle;
}
}
} while (!ch->isNull() && !attr.first.isEmpty());
if (preloadImages && !image->size.isValid()) {
// if we don't know its size but the image is a local image,
// we load it in the pixmap cache and save its implicit size
// to avoid a relayout later on.
QUrl url = context->resolvedUrl(image->url);
if (url.isLocalFile()) {
QDeclarativePixmap *pix = new QDeclarativePixmap(context->engine(), url, image->size);
if (pix && pix->isReady()) {
image->size = pix->implicitSize();
image->pix = pix;
}
}
}
imgWidth = image->size.width();
imgTags->append(image);
} else {
// if we already have a list of img tags for this text
// we only want to update the positions of these tags.
QDeclarativeStyledTextImgTag *image = imgTags->value(nbImages);
image->position = textOut.length() + 1;
imgWidth = image->size.width();
QPair<QStringRef,QStringRef> attr;
do {
attr = parseAttribute(ch, textIn);
} while (!ch->isNull() && !attr.first.isEmpty());
nbImages++;
}
QFontMetricsF fm(layout.font());
QString padding(qFloor(imgWidth / fm.width(QChar::Nbsp)), QChar::Nbsp);
textOut += QChar(' ');
textOut += padding;
textOut += QChar(' ');
}
QPair<QStringRef,QStringRef> QDeclarativeStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn)
{
skipSpace(ch);

View File

@ -42,23 +42,55 @@
#ifndef QDECLARATIVESTYLEDTEXT_H
#define QDECLARATIVESTYLEDTEXT_H
#include <QSizeF>
#include <QSize>
#include <QPointF>
#include <QList>
#include <QUrl>
#include <QtQuick/private/qdeclarativepixmapcache_p.h>
QT_BEGIN_NAMESPACE
class QPainter;
class QPointF;
class QString;
class QDeclarativeStyledTextImgTag;
class QDeclarativeStyledTextPrivate;
class QTextLayout;
class QString;
class QDeclarativeContext;
class Q_AUTOTEST_EXPORT QDeclarativeStyledTextImgTag
{
public:
QDeclarativeStyledTextImgTag()
: position(0), align(QDeclarativeStyledTextImgTag::Bottom), pix(0)
{ }
~QDeclarativeStyledTextImgTag() { delete pix; }
enum Align {
Bottom,
Middle,
Top
};
QUrl url;
QPointF pos;
QSize size;
int position;
Align align;
QDeclarativePixmap *pix;
};
class Q_AUTOTEST_EXPORT QDeclarativeStyledText
{
public:
static void parse(const QString &string, QTextLayout &layout);
static void parse(const QString &string, QTextLayout &layout,
QList<QDeclarativeStyledTextImgTag*> &imgTags,
QDeclarativeContext *context,
bool preloadImages);
private:
QDeclarativeStyledText(const QString &string, QTextLayout &layout);
QDeclarativeStyledText(const QString &string, QTextLayout &layout,
QList<QDeclarativeStyledTextImgTag*> &imgTags,
QDeclarativeContext *context,
bool preloadImages);
~QDeclarativeStyledText();
QDeclarativeStyledTextPrivate *d;

View File

@ -41,7 +41,8 @@
#include <qtest.h>
#include <QtTest/QtTest>
#include <QtGui/QTextLayout>
#include <private/qdeclarativestyledtext_p.h>
#include <QtCore/QList>
#include <QtQuick/private/qdeclarativestyledtext_p.h>
class tst_qdeclarativestyledtext : public QObject
{
@ -148,6 +149,7 @@ void tst_qdeclarativestyledtext::textOutput_data()
QTest::newRow("space before bold") << "this is <b>bold</b>" << "this is bold" << (FormatList() << Format(Format::Bold, 8, 4));
QTest::newRow("space leading bold") << "this is<b> bold</b>" << "this is bold" << (FormatList() << Format(Format::Bold, 7, 5));
QTest::newRow("space trailing bold") << "this is <b>bold </b>" << "this is bold " << (FormatList() << Format(Format::Bold, 8, 5));
QTest::newRow("img") << "a<img src=\"blah.png\"/>b" << "a b" << FormatList();
}
void tst_qdeclarativestyledtext::textOutput()
@ -157,7 +159,8 @@ void tst_qdeclarativestyledtext::textOutput()
QFETCH(FormatList, formats);
QTextLayout layout;
QDeclarativeStyledText::parse(input, layout);
QList<QDeclarativeStyledTextImgTag*> imgTags;
QDeclarativeStyledText::parse(input, layout, imgTags, 0, false);
QCOMPARE(layout.text(), output);

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,24 @@
import QtQuick 2.0
Item {
width: 300
height: 200
Text {
id: myText
objectName: "myText"
elide: Text.ElideRight
maximumLineCount: 2
width: 200
wrapMode: Text.WordWrap
text: "This is a sad face aligned to the top. Lorem ipsum dolor sit amet. Nulla sed turpis risus. Integer sit amet odio quis mauris varius venenatis<img src=\"images/face-sad.png\" width=\"30\" height=\"30\" align=\"top\">Lorem ipsum dolor sit amet. Nulla sed turpis risus. Integer sit amet odio quis mauris varius venenatis. Lorem ipsum dolor sit amet. Nulla sed turpis risus.Lorem ipsum dolor sit amet. Nulla sed turpis risus. Lorem ipsum dolor sit amet. Nulla sed turpis risus.Lorem ipsum dolor sit amet. Nulla sed turpis risus."
}
MouseArea {
anchors.fill: parent
onClicked: myText.width = 400
}
}

View File

@ -0,0 +1,12 @@
import QtQuick 2.0
Rectangle {
id: main
width: 300; height: 400
Text {
id: myText
objectName: "myText"
text: ""
}
}

View File

@ -107,6 +107,12 @@ private slots:
void lineLaidOut();
void imgTagsAlign_data();
void imgTagsAlign();
void imgTagsMultipleImages();
void imgTagsElide();
void imgTagsUpdates();
void imgTagsError();
private:
QStringList standard;
@ -1478,6 +1484,120 @@ void tst_qquicktext::lineLaidOut()
delete canvas;
}
void tst_qquicktext::imgTagsAlign_data()
{
QTest::addColumn<QString>("src");
QTest::addColumn<int>("imgHeight");
QTest::addColumn<QString>("align");
QTest::newRow("heart-bottom") << "data/images/heart200.png" << 181 << "bottom";
QTest::newRow("heart-middle") << "data/images/heart200.png" << 181 << "middle";
QTest::newRow("heart-top") << "data/images/heart200.png" << 181 << "top";
QTest::newRow("starfish-bottom") << "data/images/starfish_2.png" << 217 << "bottom";
QTest::newRow("starfish-middle") << "data/images/starfish_2.png" << 217 << "middle";
QTest::newRow("starfish-top") << "data/images/starfish_2.png" << 217 << "top";
}
void tst_qquicktext::imgTagsAlign()
{
QFETCH(QString, src);
QFETCH(int, imgHeight);
QFETCH(QString, align);
QString componentStr = "import QtQuick 2.0\nText { text: \"This is a test <img src=\\\"" + src + "\\\" align=\\\"" + align + "\\\"> of image.\" }";
QDeclarativeComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != 0);
QVERIFY(textObject->height() == imgHeight);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != 0);
QRectF br = textPrivate->layout.boundingRect();
if (align == "bottom")
QVERIFY(br.y() == imgHeight - br.height());
else if (align == "middle")
QVERIFY(br.y() == imgHeight / 2.0 - br.height() / 2.0);
else if (align == "top")
QVERIFY(br.y() == 0);
delete textObject;
}
void tst_qquicktext::imgTagsMultipleImages()
{
QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.png\\\" width=\\\"60\\\" height=\\\"60\\\" > and another one<img src=\\\"data/images/heart200.png\\\" width=\\\"85\\\" height=\\\"85\\\">.\" }";
QDeclarativeComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != 0);
QVERIFY(textObject->height() == 85);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject);
QVERIFY(textPrivate != 0);
QVERIFY(textPrivate->visibleImgTags.count() == 2);
delete textObject;
}
void tst_qquicktext::imgTagsElide()
{
QQuickView *canvas = createView(testFile("imgTagsElide.qml"));
QQuickText *myText = canvas->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != 0);
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText);
QVERIFY(textPrivate != 0);
QVERIFY(textPrivate->visibleImgTags.count() == 0);
myText->setMaximumLineCount(20);
QTRY_VERIFY(textPrivate->visibleImgTags.count() == 1);
delete myText;
delete canvas;
}
void tst_qquicktext::imgTagsUpdates()
{
QQuickView *canvas = createView(testFile("imgTagsUpdates.qml"));
QQuickText *myText = canvas->rootObject()->findChild<QQuickText*>("myText");
QVERIFY(myText != 0);
QSignalSpy spy(myText, SIGNAL(paintedSizeChanged()));
QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText);
QVERIFY(textPrivate != 0);
myText->setText("This is a heart<img src=\"images/heart200.png\">.");
QVERIFY(textPrivate->visibleImgTags.count() == 1);
QVERIFY(spy.count() == 1);
myText->setMaximumLineCount(2);
myText->setText("This is another heart<img src=\"images/heart200.png\">.");
QTRY_VERIFY(textPrivate->visibleImgTags.count() == 1);
// if maximumLineCount is set and the img tag doesn't have an explicit size
// we relayout twice.
QVERIFY(spy.count() == 3);
delete myText;
delete canvas;
}
void tst_qquicktext::imgTagsError()
{
QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.pn\\\" width=\\\"60\\\" height=\\\"60\\\">.\" }";
QDeclarativeComponent textComponent(&engine);
QTest::ignoreMessage(QtWarningMsg, "file::2:1: QML Text: Cannot open: file:data/images/starfish_2.pn");
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create());
QVERIFY(textObject != 0);
delete textObject;
}
QTEST_MAIN(tst_qquicktext)
#include "tst_qquicktext.moc"