Introduce QGlyphRun::stringIndexes()

This introduces a way to trace each entry in the glyph index
array to a specific index in the original text passed to
QTextLayout, as well as a convenience function to access
the original string from the QGlyphRun.

The index information is stored in the logClusters array internally
in Qt, but it contains the inverse information: For each
character in the output string, it contains an index into the
glyph array. In order to get the string indexes for each glyph,
which makes a lot more sense in the context of the QGlyphRun
API, we need to do a little search to construct the data.

To avoid adding unnecessary allocations, we make the new APIs
opt-in. If you do not specify anything, you will only get the
glyph indexes and glyph positions as before. However, you
can now specify exactly which parts of the layout to extract
using an optional flags parameter.

This also adds a manual test which can be very handy to
visualize QTextLayouts and how they are split into QGlyphRuns.

Fixes: QTBUG-103932
Change-Id: Ie4288fff338b9482aba0aba29fc7e1e59fa60900
Reviewed-by: Lars Knoll <lars@knoll.priv.no>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2022-08-24 13:25:19 +02:00
parent 87cabd0b92
commit 1fe7144a07
20 changed files with 1237 additions and 15 deletions

View File

@ -465,7 +465,72 @@ QRectF QGlyphRun::boundingRect() const
*/
bool QGlyphRun::isEmpty() const
{
return d->glyphIndexDataSize == 0;
return d->glyphIndexDataSize == 0
&& d->glyphPositionDataSize == 0
&& d->stringIndexes.isEmpty()
&& d->sourceString.isEmpty();
}
/*!
Returns the string indexes corresponding to each glyph index, if the glyph run has been
constructed from a string and string indexes have been requested from the layout. In this case,
the length of the returned vector will correspond to the length of glyphIndexes(). In other
cases, it will be empty.
Since a single glyph may correspond to multiple characters in the source string, there may be
gaps in the list of string indexes. For instance, if the string "first" is processed by a font
which contains a ligature for the character pair "fi", then the five character string will
generate a glyph run consisting of only four glyphs. Then the glyph indexes may in this case be
(1, 2, 3, 4) (four arbitrary glyph indexes) whereas the string indexes would be (0, 2, 3, 4).
The glyphs are in the logical order of the string, thus it is implied that the first glyphs
spans characters 0 and 1 in this case.
Inversely, a single character may also generate multiple glyphs, in which case there will be
duplicate entries in the list of string indexes.
The string indexes correspond to the string, optionally available through sourceString().
\sa setStringIndexes(), sourceString(), QTextLayout::glyphRuns()
*/
QList<qsizetype> QGlyphRun::stringIndexes() const
{
return d->stringIndexes;
}
/*!
Sets the list of string indexes corresponding to the glyph indexes to \a stringIndexes
See stringIndexes() for more details on the conventions of this list.
\sa sourceString()
*/
void QGlyphRun::setStringIndexes(const QList<qsizetype> &stringIndexes)
{
detach();
d->stringIndexes = stringIndexes;
}
/*!
Returns the string corresponding to the glyph run, if the glyph run has been created from
a string and the string has been requested from the layout.
\sa setSourceString(), stringIndexes(), QTextLayout::glyphRuns()
*/
QString QGlyphRun::sourceString() const
{
return d->sourceString;
}
/*!
Set the string corresponding to the glyph run to \a sourceString. If set, the indexes returned
by stringIndexes() should be indexes into this string.
\sa sourceString(), stringIndexes()
*/
void QGlyphRun::setSourceString(const QString &sourceString)
{
detach();
d->sourceString = sourceString;
}
QT_END_NAMESPACE

View File

@ -74,6 +74,12 @@ public:
void setBoundingRect(const QRectF &boundingRect);
QRectF boundingRect() const;
QList<qsizetype> stringIndexes() const;
void setStringIndexes(const QList<qsizetype> &stringIndexes);
void setSourceString(const QString &sourceString);
QString sourceString() const;
bool isEmpty() const;
private:

View File

@ -42,8 +42,10 @@ public:
: QSharedData(other)
, glyphIndexes(other.glyphIndexes)
, glyphPositions(other.glyphPositions)
, stringIndexes(other.stringIndexes)
, rawFont(other.rawFont)
, boundingRect(other.boundingRect)
, sourceString(other.sourceString)
, flags(other.flags)
, glyphIndexData(other.glyphIndexData)
, glyphIndexDataSize(other.glyphIndexDataSize)
@ -56,8 +58,10 @@ public:
QList<quint32> glyphIndexes;
QList<QPointF> glyphPositions;
QList<qsizetype> stringIndexes;
QRawFont rawFont;
QRectF boundingRect;
QString sourceString;
QGlyphRun::GlyphRunFlags flags;

View File

@ -281,6 +281,25 @@ Qt::LayoutDirection QTextInlineObject::textDirection() const
\value SkipWords
*/
/*!
\enum QTextLayout::GlyphRunRetrievalFlag
GlyphRunRetrievalFlag specifies flags passed to the glyphRuns() functions to determine
which properties of the layout are returned in the QGlyphRun objects. Since each property
will consume memory and may require additional allocations, it is a good practice to only
request the properties you will need to access later.
\value RetrieveGlyphIndexes Retrieves the indexes in the font which correspond to the glyphs.
\value RetrieveGlyphPositions Retrieves the relative positions of the glyphs in the layout.
\value RetrieveStringIndexes Retrieves the indexes in the original string that correspond to
each of the glyphs.
\value RetrieveString Retrieves the original source string from the layout.
\value RetrieveAll Retrieves all available properties of the layout.
\omitvalue DefaultRetrievalFlags
\sa glyphRuns(), QTextLine::glyphRuns()
*/
/*!
\fn QTextEngine *QTextLayout::engine() const
\internal
@ -961,7 +980,9 @@ static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
}
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
/*!
\overload
Returns the glyph indexes and positions for all glyphs corresponding to the \a length characters
starting at the position \a from in this QTextLayout. This is an expensive function, and should
not be called in a time sensitive context.
@ -969,12 +990,44 @@ static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
If \a from is less than zero, then the glyph run will begin at the first character in the
layout. If \a length is less than zero, it will span the entire string from the start position.
\note This is equivalent to calling
glyphRuns(from,
length,
QTextLayout::GlyphRunRetrievalFlag::GlyphIndexes |
QTextLayout::GlyphRunRetrievalFlag::GlyphPositions).
\since 4.8
\sa draw(), QPainter::drawGlyphRun()
*/
#if !defined(QT_NO_RAWFONT)
# if !defined(QT_NO_RAWFONT)
QList<QGlyphRun> QTextLayout::glyphRuns(int from, int length) const
{
return glyphRuns(from, length, QTextLayout::GlyphRunRetrievalFlag::DefaultRetrievalFlags);
}
# endif
#endif
/*!
\overload
Returns the glyph indexes and positions for all glyphs corresponding to the \a length characters
starting at the position \a from in this QTextLayout. This is an expensive function, and should
not be called in a time sensitive context.
If \a from is less than zero, then the glyph run will begin at the first character in the
layout. If \a length is less than zero, it will span the entire string from the start position.
The \a retrievalFlags specifies which properties of the QGlyphRun will be retrieved from the
layout. To minimize allocations and memory consumption, this should be set to include only the
properties that you need to access later.
\since 6.5
\sa draw(), QPainter::drawGlyphRun()
*/
#if !defined(QT_NO_RAWFONT)
QList<QGlyphRun> QTextLayout::glyphRuns(int from,
int length,
QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
{
if (from < 0)
from = 0;
@ -986,10 +1039,11 @@ QList<QGlyphRun> QTextLayout::glyphRuns(int from, int length) const
if (d->lines.at(i).from > from + length)
break;
else if (d->lines.at(i).from + d->lines[i].length >= from) {
QList<QGlyphRun> glyphRuns = QTextLine(i, d).glyphRuns(from, length);
QList<QGlyphRun> glyphRuns = QTextLine(i, d).glyphRuns(from, length, retrievalFlags);
for (int j = 0; j < glyphRuns.size(); j++) {
const QGlyphRun &glyphRun = glyphRuns.at(j);
QRawFont rawFont = glyphRun.rawFont();
QFontEngine *fontEngine = rawFont.d->fontEngine;
@ -1002,14 +1056,17 @@ QList<QGlyphRun> QTextLayout::glyphRuns(int from, int length) const
} else {
QList<quint32> indexes = oldGlyphRun.glyphIndexes();
QList<QPointF> positions = oldGlyphRun.positions();
QList<qsizetype> stringIndexes = oldGlyphRun.stringIndexes();
QRectF boundingRect = oldGlyphRun.boundingRect();
indexes += glyphRun.glyphIndexes();
positions += glyphRun.positions();
stringIndexes += glyphRun.stringIndexes();
boundingRect = boundingRect.united(glyphRun.boundingRect());
oldGlyphRun.setGlyphIndexes(indexes);
oldGlyphRun.setPositions(positions);
oldGlyphRun.setStringIndexes(stringIndexes);
oldGlyphRun.setBoundingRect(boundingRect);
}
}
@ -2186,9 +2243,11 @@ static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const Q
#if !defined(QT_NO_RAWFONT)
static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
const QString &text,
const QGlyphLayout &glyphLayout,
const QPointF &pos,
const QGlyphRun::GlyphRunFlags &flags,
QTextLayout::GlyphRunRetrievalFlags retrievalFlags,
QFixed selectionX,
QFixed selectionWidth,
int glyphsStart,
@ -2204,14 +2263,15 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
QGlyphRunPrivate *d = QGlyphRunPrivate::get(glyphRun);
int rangeStart = textPosition;
while (*logClusters != glyphsStart && rangeStart < textPosition + textLength) {
++logClusters;
int logClusterIndex = 0;
while (logClusters[logClusterIndex] != glyphsStart && rangeStart < textPosition + textLength) {
++logClusterIndex;
++rangeStart;
}
int rangeEnd = rangeStart;
while (*logClusters != glyphsEnd && rangeEnd < textPosition + textLength) {
++logClusters;
while (logClusters[logClusterIndex] != glyphsEnd && rangeEnd < textPosition + textLength) {
++logClusterIndex;
++rangeEnd;
}
@ -2244,14 +2304,43 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
qreal minY = 0;
qreal maxY = 0;
QList<quint32> glyphs;
glyphs.reserve(glyphsArray.size());
if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
glyphs.reserve(glyphsArray.size());
QList<QPointF> positions;
positions.reserve(glyphsArray.size());
for (int i=0; i<glyphsArray.size(); ++i) {
glyphs.append(glyphsArray.at(i) & 0xffffff);
if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
positions.reserve(glyphsArray.size());
QList<qsizetype> stringIndexes;
if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
stringIndexes.reserve(glyphsArray.size());
int nextClusterIndex = 0;
int currentClusterIndex = 0;
for (int i = 0; i < glyphsArray.size(); ++i) {
const int glyphArrayIndex = i + glyphsStart;
// Search for the next cluster in the string (or the end of string if there are no
// more clusters)
if (retrievalFlags & QTextLayout::RetrieveStringIndexes) {
if (nextClusterIndex < textLength && logClusters[nextClusterIndex] == glyphArrayIndex) {
currentClusterIndex = nextClusterIndex; // Store current cluster
while (logClusters[nextClusterIndex] == glyphArrayIndex && nextClusterIndex < textLength)
++nextClusterIndex;
}
// We are now either at end of string (no more clusters) or we are not yet at the
// next cluster in glyph array. We fill in current cluster so that there is always one
// entry in stringIndexes for each glyph.
Q_ASSERT(nextClusterIndex == textLength || logClusters[nextClusterIndex] != glyphArrayIndex);
stringIndexes.append(textPosition + currentClusterIndex);
}
if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes) {
glyph_t glyphIndex = glyphsArray.at(i) & 0xffffff;
glyphs.append(glyphIndex);
}
QPointF position = positionsArray.at(i).toPointF() + pos;
positions.append(position);
if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
positions.append(position);
if (i == 0) {
maxY = minY = position.y();
@ -2263,8 +2352,14 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
qreal height = maxY + fontHeight - minY;
glyphRun.setGlyphIndexes(glyphs);
glyphRun.setPositions(positions);
if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
glyphRun.setGlyphIndexes(glyphs);
if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
glyphRun.setPositions(positions);
if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
glyphRun.setStringIndexes(stringIndexes);
if (retrievalFlags & QTextLayout::RetrieveString)
glyphRun.setSourceString(text);
glyphRun.setFlags(flags);
glyphRun.setRawFont(font);
@ -2274,7 +2369,9 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
return glyphRun;
}
# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
/*!
\overload
Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
in the range defined by \a from and \a length. The \a from index is relative to the beginning
of the text in the containing QTextLayout, and the range must be within the range of QTextLine
@ -2283,11 +2380,42 @@ static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
If \a from is negative, it will default to textStart(), and if \a length is negative it will
default to the return value of textLength().
\note This is equivalent to calling
glyphRuns(from,
length,
QTextLayout::GlyphRunRetrievalFlag::GlyphIndexes |
QTextLayout::GlyphRunRetrievalFlag::GlyphPositions).
\since 5.0
\sa QTextLayout::glyphRuns()
*/
QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
{
return glyphRuns(from, length, QTextLayout::GlyphRunRetrievalFlag::DefaultRetrievalFlags);
}
# endif
/*!
Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
in the range defined by \a from and \a length. The \a from index is relative to the beginning
of the text in the containing QTextLayout, and the range must be within the range of QTextLine
as given by functions textStart() and textLength().
The \a retrievalFlags specifies which properties of the QGlyphRun will be retrieved from the
layout. To minimize allocations and memory consumption, this should be set to include only the
properties that you need to access later.
If \a from is negative, it will default to textStart(), and if \a length is negative it will
default to the return value of textLength().
\since 6.5
\sa QTextLayout::glyphRuns()
*/
QList<QGlyphRun> QTextLine::glyphRuns(int from,
int length,
QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
{
const QScriptLine &line = eng->lines.at(index);
@ -2405,9 +2533,11 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
subFlags |= QGlyphRun::SplitLigature;
glyphRuns.append(glyphRunWithInfo(multiFontEngine->engine(which),
eng->text,
subLayout,
pos,
subFlags,
retrievalFlags,
x,
width,
glyphsStart + start,
@ -2435,9 +2565,11 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
subFlags |= QGlyphRun::SplitLigature;
QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
eng->text,
subLayout,
pos,
subFlags,
retrievalFlags,
x,
width,
glyphsStart + start,
@ -2451,9 +2583,11 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
if (startsInsideLigature || endsInsideLigature)
flags |= QGlyphRun::SplitLigature;
QGlyphRun glyphRun = glyphRunWithInfo(mainFontEngine,
eng->text,
glyphLayout,
pos,
flags,
retrievalFlags,
x,
width,
glyphsStart,

View File

@ -69,6 +69,17 @@ class QTextOption;
class Q_GUI_EXPORT QTextLayout
{
public:
enum GlyphRunRetrievalFlag {
RetrieveGlyphIndexes = 0x1,
RetrieveGlyphPositions = 0x2,
RetrieveStringIndexes = 0x4,
RetrieveString = 0x8,
DefaultRetrievalFlags = RetrieveGlyphIndexes | RetrieveGlyphPositions,
RetrieveAll = 0xffff
};
Q_DECLARE_FLAGS(GlyphRunRetrievalFlags, GlyphRunRetrievalFlag)
// does itemization
QTextLayout();
QTextLayout(const QString& text);
@ -148,7 +159,15 @@ public:
qreal maximumWidth() const;
#if !defined(QT_NO_RAWFONT)
# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
QList<QGlyphRun> glyphRuns(int from, int length, GlyphRunRetrievalFlags flags) const;
QList<QGlyphRun> glyphRuns(int from = -1, int length = -1) const;
# else
QList<QGlyphRun> glyphRuns(int from = -1,
int length = -1,
GlyphRunRetrievalFlags flags = GlyphRunRetrievalFlag::DefaultRetrievalFlags) const;
# endif
#endif
QTextEngine *engine() const { return d; }
@ -166,7 +185,7 @@ private:
QTextEngine *d;
};
Q_DECLARE_TYPEINFO(QTextLayout::FormatRange, Q_RELOCATABLE_TYPE);
Q_DECLARE_OPERATORS_FOR_FLAGS(QTextLayout::GlyphRunRetrievalFlags)
class Q_GUI_EXPORT QTextLine
{
@ -219,7 +238,14 @@ public:
void draw(QPainter *painter, const QPointF &position) const;
#if !defined(QT_NO_RAWFONT)
# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
QList<QGlyphRun> glyphRuns(int from, int length, QTextLayout::GlyphRunRetrievalFlags flags) const;
QList<QGlyphRun> glyphRuns(int from = -1, int length = -1) const;
# else
QList<QGlyphRun> glyphRuns(int from = -1,
int length = -1,
QTextLayout::GlyphRunRetrievalFlags flags = QTextLayout::GlyphRunRetrievalFlag::Default) const;
# endif
#endif
private:

View File

@ -13,6 +13,7 @@ set_source_files_properties("../../../shared/resources/test.ttf"
)
set(testdata_resource_files
"../../../shared/resources/test.ttf"
"Ligatures.otf"
)
qt_internal_add_test(tst_qglyphrun

Binary file not shown.

View File

@ -39,6 +39,9 @@ private slots:
void mixedScripts();
void multiLineBoundingRect();
void defaultIgnorables();
void stringIndexes();
void retrievalFlags_data();
void retrievalFlags();
private:
int m_testFontId;
@ -618,6 +621,313 @@ void tst_QGlyphRun::defaultIgnorables()
QCOMPARE(runs.at(0).glyphIndexes()[0], uint(0));
}
void tst_QGlyphRun::stringIndexes()
{
int ligatureFontId = QFontDatabase::addApplicationFont(QFINDTESTDATA("Ligatures.otf"));
QVERIFY(ligatureFontId >= 0);
QFont ligatureFont = QFont("QtLigatures");
QCOMPARE(QFontInfo(ligatureFont).family(), QString::fromLatin1("QtLigatures"));
QTextLayout::GlyphRunRetrievalFlags retrievalFlags
= QTextLayout::RetrieveGlyphIndexes | QTextLayout::RetrieveStringIndexes;
// Three characters -> three glyphs
{
QTextLayout layout;
layout.setText("f i");
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 3);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 2);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(2, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), 2);
}
}
// Two characters -> one glyph
{
QTextLayout layout;
layout.setText("fi");
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 233);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), 0);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 233);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), 1);
}
}
// Four characters -> three glyphs
{
QTextLayout layout;
layout.setText("ffii");
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 3);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 71);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), 233);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), 74);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 3);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 3);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, 1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 233);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), 1);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, 2, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 233);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 1);
QCOMPARE(stringIndexes.at(0), 1);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, 3, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 2);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 233);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), 74);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 2);
QCOMPARE(stringIndexes.at(0), 1);
QCOMPARE(stringIndexes.at(1), 3);
}
}
// One character -> two glyphs
{
QTextLayout layout;
layout.setText(QChar(0xe6)); // LATIN SMALL LETTER AE
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 2);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 66);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), 70);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 2);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 0);
}
// Three characters -> four glyphs
{
QTextLayout layout;
layout.setText(QString('f') + QChar(0xe6) + QChar('i'));
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 4);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 71);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), 66);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), 70);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(3), 74);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 4);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 1);
QCOMPARE(stringIndexes.at(3), 2);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 3);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 66);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), 70);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), 74);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 3);
QCOMPARE(stringIndexes.at(0), 1);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 2);
}
{
QList<QGlyphRun> glyphRuns = layout.glyphRuns(0, 2, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 3);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 71);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), 66);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), 70);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 3);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 1);
}
}
// Five characters -> five glyphs
{
QTextLayout layout;
layout.setText(QLatin1String("ffi") + QChar(0xe6) + QLatin1Char('i'));
layout.setFont(ligatureFont);
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, retrievalFlags);
QCOMPARE(glyphRuns.size(), 1);
QCOMPARE(glyphRuns.at(0).glyphIndexes().size(), 5);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(0), 71);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(1), 233);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(2), 66);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(3), 70);
QCOMPARE(glyphRuns.at(0).glyphIndexes().at(4), 74);
QList<qsizetype> stringIndexes = glyphRuns.at(0).stringIndexes();
QCOMPARE(stringIndexes.size(), 5);
QCOMPARE(stringIndexes.at(0), 0);
QCOMPARE(stringIndexes.at(1), 1);
QCOMPARE(stringIndexes.at(2), 3);
QCOMPARE(stringIndexes.at(3), 3);
QCOMPARE(stringIndexes.at(4), 4);
}
}
void tst_QGlyphRun::retrievalFlags_data()
{
QTest::addColumn<QTextLayout::GlyphRunRetrievalFlags>("flags");
QTest::addColumn<bool>("expectedGlyphIndexes");
QTest::addColumn<bool>("expectedStringIndexes");
QTest::addColumn<bool>("expectedString");
QTest::addColumn<bool>("expectedGlyphPositions");
QTest::newRow("Glyph indexes")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveGlyphIndexes)
<< true << false << false << false;
QTest::newRow("Glyph Positions")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveGlyphPositions)
<< false << false << false << true;
QTest::newRow("String indexes")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveStringIndexes)
<< false << true << false << false;
QTest::newRow("String")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveString)
<< false << false << true << false;
QTest::newRow("Default")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::DefaultRetrievalFlags)
<< true << false << false << true;
QTest::newRow("All")
<< QTextLayout::GlyphRunRetrievalFlags(QTextLayout::RetrieveAll)
<< true << true << true << true;
}
void tst_QGlyphRun::retrievalFlags()
{
QFETCH(QTextLayout::GlyphRunRetrievalFlags, flags);
QFETCH(bool, expectedGlyphIndexes);
QFETCH(bool, expectedStringIndexes);
QFETCH(bool, expectedString);
QFETCH(bool, expectedGlyphPositions);
QTextLayout layout;
layout.setText(QLatin1String("abc"));
layout.beginLayout();
layout.createLine();
layout.endLayout();
QList<QGlyphRun> glyphRuns = layout.glyphRuns(-1, -1, flags);
QVERIFY(!glyphRuns.isEmpty());
QGlyphRun firstGlyphRun = glyphRuns.first();
QCOMPARE(firstGlyphRun.glyphIndexes().isEmpty(), !expectedGlyphIndexes);
QCOMPARE(firstGlyphRun.stringIndexes().isEmpty(), !expectedStringIndexes);
QCOMPARE(firstGlyphRun.sourceString().isEmpty(), !expectedString);
QCOMPARE(firstGlyphRun.positions().isEmpty(), !expectedGlyphPositions);
}
#endif // QT_NO_RAWFONT
QTEST_MAIN(tst_QGlyphRun)

View File

@ -0,0 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(qglyphrun
GUI
SOURCES
main.cpp
controller.h controller.cpp controller.ui
view.h view.cpp
glyphruninspector.h glyphruninspector.cpp
singleglyphrun.h singleglyphrun.cpp singleglyphrun.ui
LIBRARIES
Qt::Gui
Qt::Widgets
ENABLE_AUTOGEN_TOOLS
uic
)

View File

@ -0,0 +1,57 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "controller.h"
#include "ui_controller.h"
Controller::Controller(QWidget *parent) :
QWidget(parent),
ui(new Ui::Controller)
{
ui->setupUi(this);
connect(ui->cbWrap, &QCheckBox::toggled, this, &Controller::updateViews);
connect(ui->gbRange, &QGroupBox::toggled, this, &Controller::updateViews);
connect(ui->teSourceString, &QPlainTextEdit::textChanged, this, &Controller::updateViews);
connect(ui->hsLineWidth, &QSlider::valueChanged, this, &Controller::updateViews);
connect(ui->inspector, &GlyphRunInspector::updateBounds, ui->view, &View::setVisualizedBounds);
connect(ui->sbFrom, &QSpinBox::valueChanged, this, &Controller::updateViews);
connect(ui->sbTo, &QSpinBox::valueChanged, this, &Controller::updateViews);
connect(ui->teSourceString, &QPlainTextEdit::selectionChanged, this, &Controller::updateRange);
connect(ui->fcbFont, &QFontComboBox::currentFontChanged, this, &Controller::updateViews);
}
Controller::~Controller()
{
delete ui;
}
void Controller::updateRange()
{
if (ui->gbRange->isChecked()) {
QTextCursor cursor = ui->teSourceString->textCursor();
if (cursor.hasSelection()) {
ui->sbFrom->setValue(cursor.selectionStart());
ui->sbTo->setValue(cursor.selectionEnd() - 1);
updateViews();
}
}
}
void Controller::updateViews()
{
QString s = ui->teSourceString->toPlainText();
ui->sbFrom->setMaximum(s.length());
ui->sbTo->setMaximum(s.length());
s.replace('\n', QChar::LineSeparator);
ui->view->updateLayout(s,
qreal(ui->hsLineWidth->value()) * ui->hsLineWidth->width() / 100.0f,
ui->cbWrap ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::ManualWrap,
ui->fcbFont->currentFont());
int start = ui->gbRange->isChecked() ? ui->sbFrom->value() : -1;
int length = ui->gbRange->isChecked() ? ui->sbTo->value() - start + 1 : -1;
ui->inspector->updateLayout(ui->view->layout(), start, length);
}

View File

@ -0,0 +1,29 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QWidget>
namespace Ui {
class Controller;
}
class Controller : public QWidget
{
Q_OBJECT
public:
explicit Controller(QWidget *parent = nullptr);
~Controller();
private slots:
void updateViews();
void updateRange();
private:
Ui::Controller *ui;
};
#endif // CONTROLLER_H

View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Controller</class>
<widget class="QWidget" name="Controller">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>549</width>
<height>419</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Source string:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="teSourceString"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QCheckBox" name="cbWrap">
<property name="text">
<string>Wrap</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QFontComboBox" name="fcbFont"/>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="gbRange">
<property name="title">
<string>Range</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>From</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="sbFrom"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>To</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="sbTo"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Display</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="View" name="view" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QSlider" name="hsLineWidth">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="GlyphRunInspector" name="inspector" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pbQuit">
<property name="text">
<string>Quit</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>View</class>
<extends>QWidget</extends>
<header>view.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GlyphRunInspector</class>
<extends>QWidget</extends>
<header>glyphruninspector.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>pbQuit</sender>
<signal>clicked()</signal>
<receiver>Controller</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>517</x>
<y>398</y>
</hint>
<hint type="destinationlabel">
<x>35</x>
<y>-16</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,48 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "glyphruninspector.h"
#include "singleglyphrun.h"
#include <QTextLayout>
#include <QtWidgets>
GlyphRunInspector::GlyphRunInspector(QWidget *parent)
: QWidget{parent}
{
QHBoxLayout *layout = new QHBoxLayout(this);
m_tabWidget = new QTabWidget;
layout->addWidget(m_tabWidget);
connect(m_tabWidget,
&QTabWidget::currentChanged,
this,
&GlyphRunInspector::updateVisualizationForTab);
}
void GlyphRunInspector::updateVisualizationForTab()
{
int currentTab = m_tabWidget->currentIndex();
SingleGlyphRun *gr = currentTab >= 0 && currentTab < m_content.size() ? m_content.at(currentTab) : nullptr;
if (gr != nullptr)
emit updateBounds(gr->bounds());
}
void GlyphRunInspector::updateLayout(QTextLayout *layout, int start, int length)
{
QList<QGlyphRun> glyphRuns = layout->glyphRuns(start, length, QTextLayout::RetrieveAll);
m_tabWidget->clear();
qDeleteAll(m_content);
m_content.clear();
for (int i = 0; i < glyphRuns.size(); ++i) {
SingleGlyphRun *w = new SingleGlyphRun(m_tabWidget);
w->updateGlyphRun(glyphRuns.at(i));
m_tabWidget->addTab(w, QStringLiteral("%1").arg(glyphRuns.at(i).rawFont().familyName()));
m_content.append(w);
}
updateVisualizationForTab();
}

View File

@ -0,0 +1,31 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef GLYPHRUNINSPECTOR_H
#define GLYPHRUNINSPECTOR_H
#include <QWidget>
class QTextLayout;
class QTabWidget;
class SingleGlyphRun;
class GlyphRunInspector : public QWidget
{
Q_OBJECT
public:
explicit GlyphRunInspector(QWidget *parent = nullptr);
void updateLayout(QTextLayout *layout, int start, int length);
private slots:
void updateVisualizationForTab();
signals:
void updateBounds(const QRegion &region);
private:
QTabWidget *m_tabWidget;
QList<SingleGlyphRun *> m_content;
};
#endif // GLYPHRUNINSPECTOR_H

View File

@ -0,0 +1,18 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "controller.h"
#include <QtWidgets>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
Controller controller;
controller.showMaximized();
return app.exec();
}
#include "main.moc"

View File

@ -0,0 +1,73 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "singleglyphrun.h"
#include "ui_singleglyphrun.h"
SingleGlyphRun::SingleGlyphRun(QWidget *parent) :
QWidget(parent),
ui(new Ui::SingleGlyphRun)
{
ui->setupUi(this);
}
SingleGlyphRun::~SingleGlyphRun()
{
delete ui;
}
void SingleGlyphRun::updateGlyphRun(const QGlyphRun &glyphRun)
{
m_bounds = QRegion();
QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
QList<qsizetype> stringIndexes = glyphRun.stringIndexes();
QList<QPointF> glyphPositions = glyphRun.positions();
ui->twGlyphRun->clearContents();
ui->twGlyphRun->setRowCount(glyphIndexes.size());
for (int i = 0; i < glyphIndexes.size(); ++i) {
{
QTableWidgetItem *glyphIndex = new QTableWidgetItem(QString::number(glyphIndexes.at(i)));
ui->twGlyphRun->setItem(i, 0, glyphIndex);
}
{
QPointF position = glyphPositions.at(i);
QTableWidgetItem *glyphPosition = new QTableWidgetItem(QStringLiteral("(%1, %2)")
.arg(position.x())
.arg(position.y()));
ui->twGlyphRun->setItem(i, 1, glyphPosition);
}
{
QTableWidgetItem *stringIndex = new QTableWidgetItem(QString::number(stringIndexes.at(i)));
ui->twGlyphRun->setItem(i, 2, stringIndex);
}
QChar c = glyphRun.sourceString().at(stringIndexes.at(i));
{
QTableWidgetItem *unicode = new QTableWidgetItem(QString::number(c.unicode(), 16));
ui->twGlyphRun->setItem(i, 3, unicode);
}
{
QTableWidgetItem *character = new QTableWidgetItem(c);
ui->twGlyphRun->setItem(i, 4, character);
}
{
QImage image = glyphRun.rawFont().alphaMapForGlyph(glyphIndexes.at(i));
QTableWidgetItem *glyphImage = new QTableWidgetItem(QIcon(QPixmap::fromImage(image)), QString{});
ui->twGlyphRun->setItem(i, 5, glyphImage);
}
QRectF brect = glyphRun.rawFont().boundingRect(glyphIndexes.at(i));
brect.adjust(glyphPositions.at(i).x(), glyphPositions.at(i).y(),
glyphPositions.at(i).x(), glyphPositions.at(i).y());
m_bounds += brect.toAlignedRect();
}
}

View File

@ -0,0 +1,30 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef SINGLEGLYPHRUN_H
#define SINGLEGLYPHRUN_H
#include <QWidget>
#include <QGlyphRun>
namespace Ui {
class SingleGlyphRun;
}
class SingleGlyphRun : public QWidget
{
Q_OBJECT
public:
explicit SingleGlyphRun(QWidget *parent = nullptr);
~SingleGlyphRun();
void updateGlyphRun(const QGlyphRun &glyphRun);
QRegion bounds() const { return m_bounds; }
private:
Ui::SingleGlyphRun *ui;
QRegion m_bounds;
};
#endif // SINGLEGLYPHRUN_H

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SingleGlyphRun</class>
<widget class="QWidget" name="SingleGlyphRun">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>599</width>
<height>300</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTableWidget" name="twGlyphRun">
<column>
<property name="text">
<string>Glyph index</string>
</property>
</column>
<column>
<property name="text">
<string>Glyph position</string>
</property>
</column>
<column>
<property name="text">
<string>String index</string>
</property>
</column>
<column>
<property name="text">
<string>Unicode</string>
</property>
</column>
<column>
<property name="text">
<string>Character</string>
</property>
</column>
<column>
<property name="text">
<string>Font glyph</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,70 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "view.h"
#include <QTextLayout>
#include <QPainter>
View::View(QWidget *parent)
: QWidget{parent}
{
setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum));
}
View::~View()
{
delete m_layout;
}
void View::updateLayout(const QString &sourceString,
float width,
QTextOption::WrapMode mode,
const QFont &font)
{
if (m_layout == nullptr)
m_layout = new QTextLayout;
m_layout->setText(sourceString);
QTextOption option;
option.setWrapMode(mode);
m_layout->setTextOption(option);
m_layout->setFont(font);
m_layout->beginLayout();
float y = 0.0f;
forever {
QTextLine line = m_layout->createLine();
if (!line.isValid())
break;
line.setLineWidth(width);
line.setPosition(QPointF(0, y));
y += line.height();
}
m_layout->endLayout();
update();
updateGeometry();
}
QSize View::sizeHint() const
{
if (m_layout != nullptr)
return m_layout->boundingRect().size().toSize();
else
return QSize(100, 100);
}
void View::paintEvent(QPaintEvent *)
{
QPainter p(this);
if (m_layout != nullptr)
m_layout->draw(&p, QPointF(0, 0));
if (!m_bounds.isEmpty()) {
p.setPen(Qt::NoPen);
p.setBrush(Qt::yellow);
p.setOpacity(0.25);
for (const QRect &r : m_bounds) {
p.drawRect(r);
}
}
}

View File

@ -0,0 +1,42 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef VIEW_H
#define VIEW_H
#include <QWidget>
#include <QTextOption>
class QTextLayout;
class View : public QWidget
{
Q_OBJECT
public:
explicit View(QWidget *parent = nullptr);
~View() override;
void updateLayout(const QString &sourceString,
float width,
QTextOption::WrapMode mode,
const QFont &font);
QSize sizeHint() const override;
QTextLayout *layout() const { return m_layout; }
public slots:
void setVisualizedBounds(const QRegion &region)
{
m_bounds = region;
update();
}
protected:
void paintEvent(QPaintEvent *e) override;
private:
QTextLayout *m_layout = nullptr;
QRegion m_bounds;
};
#endif // VIEW_H