Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qtextlayout.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qtextlayout.h"
5#include "qtextengine_p.h"
6
7#include <qthread.h>
8#include <qfont.h>
9#include <qmath.h>
10#include <qpainter.h>
11#include <qvarlengtharray.h>
12#include <qtextformat.h>
14#include "qtextdocument_p.h"
15#include "qtextformat_p.h"
16#include "qpainterpath.h"
17#include "qglyphrun.h"
18#include "qglyphrun_p.h"
19#include "qrawfont.h"
20#include "qrawfont_p.h"
21#include <limits.h>
22
23#include <qdebug.h>
24
25#include "qfontengine_p.h"
26#include <private/qpainter_p.h>
27
29
30#define ObjectSelectionBrush (QTextFormat::ForegroundBrush + 1)
31#define SuppressText 0x5012
32#define SuppressBackground 0x513
33
120{
121 QScriptItem& si = eng->layoutData->items[itm];
122 return QRectF(0, -si.ascent.toReal(), si.width.toReal(), si.height().toReal());
123}
124
131{
132 return eng->layoutData->items.at(itm).width.toReal();
133}
134
141{
142 return eng->layoutData->items.at(itm).ascent.toReal();
143}
144
151{
152 return eng->layoutData->items.at(itm).descent.toReal();
153}
154
162{
163 return eng->layoutData->items.at(itm).height().toReal();
164}
165
172{
173 eng->layoutData->items[itm].width = QFixed::fromReal(w);
174}
175
182{
183 eng->layoutData->items[itm].ascent = QFixed::fromReal(a);
184}
185
192{
193 eng->layoutData->items[itm].descent = QFixed::fromReal(d);
194}
195
200{
201 return eng->layoutData->items[itm].position;
202}
203
209{
210 return eng->formatIndex(&eng->layoutData->items[itm]);
211}
212
217{
218 return eng->format(&eng->layoutData->items[itm]);
219}
220
225{
226 return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight);
227}
228
322
327{
328 d = new QTextEngine();
329 d->text = text;
330}
331
343QTextLayout::QTextLayout(const QString &text, const QFont &font, const QPaintDevice *paintdevice)
344{
345 const QFont f(paintdevice ? QFont(font, paintdevice) : font);
346 d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f);
347}
348
354{
355 d = new QTextEngine();
356 d->block = block;
357}
358
363{
364 if (!d->stackEngine)
365 delete d;
366}
367
368#ifndef QT_NO_RAWFONT
377{
378 d->rawFont = rawFont;
379 d->useRawFont = true;
381}
382#endif
383
391{
392 d->fnt = font;
393#ifndef QT_NO_RAWFONT
394 d->useRawFont = false;
395#endif
397}
398
406{
407 return d->font();
408}
409
419void QTextLayout::setText(const QString& string)
420{
421 d->invalidate();
422 d->clearLineData();
423 d->text = string;
424}
425
432{
433 return d->text;
434}
435
446
453{
454 return d->option;
455}
456
465{
466 if (d->preeditAreaPosition() == position && d->preeditAreaText() == text)
467 return;
469
470 if (QTextDocumentPrivate::get(d->block) != nullptr)
471 QTextDocumentPrivate::get(d->block)->documentChange(d->block.position(), d->block.length());
472}
473
481{
482 return d->preeditAreaPosition();
483}
484
491{
492 return d->preeditAreaText();
493}
494
503void QTextLayout::setFormats(const QList<FormatRange> &formats)
504{
506
507 if (QTextDocumentPrivate::get(d->block) != nullptr)
508 QTextDocumentPrivate::get(d->block)->documentChange(d->block.position(), d->block.length());
509}
510
518QList<QTextLayout::FormatRange> QTextLayout::formats() const
519{
520 return d->formats();
521}
522
531{
532 setFormats(QList<FormatRange>());
533}
534
549
557{
558 return d->cacheGlyphs;
559}
560
573
584
594{
595#ifndef QT_NO_DEBUG
597 qWarning("QTextLayout::beginLayout: Called while already doing layout");
598 return;
599 }
600#endif
601 d->invalidate();
602 d->clearLineData();
603 d->itemize();
605}
606
613{
614#ifndef QT_NO_DEBUG
616 qWarning("QTextLayout::endLayout: Called without beginLayout()");
617 return;
618 }
619#endif
620 int l = d->lines.size();
621 if (l && d->lines.at(l-1).length < 0) {
622 QTextLine(l-1, d).setNumColumns(INT_MAX);
623 }
625 if (!d->cacheGlyphs)
626 d->freeMemory();
627}
628
639{
640 d->clearLineData();
641}
642
651{
652 const QCharAttributes *attributes = d->attributes();
653 int len = d->block.isValid() ? d->block.length() - 1
654 : d->layoutData->string.size();
655 Q_ASSERT(len <= d->layoutData->string.size());
656 if (!attributes || oldPos < 0 || oldPos >= len)
657 return oldPos;
658
659 if (mode == SkipCharacters) {
660 oldPos++;
661 while (oldPos < len && !attributes[oldPos].graphemeBoundary)
662 oldPos++;
663 } else {
664 if (oldPos < len && d->atWordSeparator(oldPos)) {
665 oldPos++;
666 while (oldPos < len && d->atWordSeparator(oldPos))
667 oldPos++;
668 } else {
669 while (oldPos < len && !attributes[oldPos].whiteSpace && !d->atWordSeparator(oldPos))
670 oldPos++;
671 }
672 while (oldPos < len && attributes[oldPos].whiteSpace)
673 oldPos++;
674 }
675
676 return oldPos;
677}
678
687{
688 const QCharAttributes *attributes = d->attributes();
689 int len = d->block.isValid() ? d->block.length() - 1
690 : d->layoutData->string.size();
691 Q_ASSERT(len <= d->layoutData->string.size());
692 if (!attributes || oldPos <= 0 || oldPos > len)
693 return oldPos;
694
695 if (mode == SkipCharacters) {
696 oldPos--;
697 while (oldPos && !attributes[oldPos].graphemeBoundary)
698 oldPos--;
699 } else {
700 while (oldPos > 0 && attributes[oldPos - 1].whiteSpace)
701 oldPos--;
702
703 if (oldPos && d->atWordSeparator(oldPos-1)) {
704 oldPos--;
705 while (oldPos && d->atWordSeparator(oldPos-1))
706 oldPos--;
707 } else {
708 while (oldPos > 0 && !attributes[oldPos - 1].whiteSpace && !d->atWordSeparator(oldPos-1))
709 oldPos--;
710 }
711 }
712
713 return oldPos;
714}
715
724{
725 int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Right);
726// qDebug("%d -> %d", oldPos, newPos);
727 return newPos;
728}
729
738{
739 int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Left);
740// qDebug("%d -> %d", oldPos, newPos);
741 return newPos;
742}
743
760{
761 const QCharAttributes *attributes = d->attributes();
762 if (!attributes || pos < 0 || pos > (int)d->layoutData->string.size())
763 return false;
764 return attributes[pos].graphemeBoundary;
765}
766
784{
785#ifndef QT_NO_DEBUG
787 qWarning("QTextLayout::createLine: Called without layouting");
788 return QTextLine();
789 }
790#endif
792 return QTextLine();
793
794 int l = d->lines.size();
795 if (l && d->lines.at(l-1).length < 0) {
796 QTextLine(l-1, d).setNumColumns(INT_MAX);
797 if (d->maxWidth > QFIXED_MAX / 2) {
798 qWarning("QTextLayout: text too long, truncated.");
799 return QTextLine();
800 }
801 }
802 int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length + d->lines.at(l-1).trailingSpaces : 0;
803 int strlen = d->layoutData->string.size();
804 if (l && from >= strlen) {
805 if (!d->lines.at(l-1).length || d->layoutData->string.at(strlen - 1) != QChar::LineSeparator)
806 return QTextLine();
807 }
808
810 line.from = from;
811 line.length = -1;
812 line.justified = false;
813 line.gridfitted = false;
814
815 d->lines.append(line);
816 return QTextLine(l, d);
817}
818
825{
826 return d->lines.size();
827}
828
835{
836 return i < lineCount() ? QTextLine(i, d) : QTextLine();
837}
838
845{
846 int lineNum = d->lineNumberForTextPosition(pos);
847 return lineNum >= 0 ? lineAt(lineNum) : QTextLine();
848}
849
859{
860 return d->position;
861}
862
869{
870 d->position = p;
871}
872
877{
878 if (d->lines.isEmpty())
879 return QRectF();
880
881 QFixed xmax, ymax;
882 QFixed xmin = d->lines.at(0).x;
883 QFixed ymin = d->lines.at(0).y;
884
885 for (int i = 0; i < d->lines.size(); ++i) {
886 const QScriptLine &si = d->lines.at(i);
887 xmin = qMin(xmin, si.x);
888 ymin = qMin(ymin, si.y);
889 QFixed lineWidth = si.width < QFIXED_MAX ? qMax(si.width, si.textWidth) : si.textWidth;
890 xmax = qMax(xmax, si.x+lineWidth);
891 // ### shouldn't the ascent be used in ymin???
892 ymax = qMax(ymax, si.y+si.height().ceil());
893 }
894 return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal());
895}
896
907{
908 return d->minWidth.toReal();
909}
910
921{
922 return d->maxWidth.toReal();
923}
924
925
941
943 QPainterPath *region, const QRectF &boundingRect)
944{
945 const QScriptLine &line = eng->lines[lineNumber];
946
947 QTextLineItemIterator iterator(eng, lineNumber, pos, selection);
948
949
950
951 const qreal selectionY = pos.y() + line.y.toReal();
952 const qreal lineHeight = line.height().toReal();
953
954 QFixed lastSelectionX = iterator.x;
955 QFixed lastSelectionWidth;
956
957 while (!iterator.atEnd()) {
958 iterator.next();
959
960 QFixed selectionX, selectionWidth;
961 if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) {
962 if (selectionX == lastSelectionX + lastSelectionWidth) {
963 lastSelectionWidth += selectionWidth;
964 continue;
965 }
966
967 if (lastSelectionWidth > 0) {
968 const QRectF rect = boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight);
969 region->addRect(rect.toAlignedRect());
970 }
971
972 lastSelectionX = selectionX;
973 lastSelectionWidth = selectionWidth;
974 }
975 }
976 if (lastSelectionWidth > 0) {
977 const QRectF rect = boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight);
978 region->addRect(rect.toAlignedRect());
979 }
980}
981
982static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
983{
984 return clip.isValid() ? (rect & clip) : rect;
985}
986
987
988#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
1008# if !defined(QT_NO_RAWFONT)
1009QList<QGlyphRun> QTextLayout::glyphRuns(int from, int length) const
1010{
1012}
1013# endif
1014#endif
1015
1032#if !defined(QT_NO_RAWFONT)
1033QList<QGlyphRun> QTextLayout::glyphRuns(int from,
1034 int length,
1035 QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
1036{
1037 if (from < 0)
1038 from = 0;
1039 if (length < 0)
1040 length = text().size();
1041
1042 QHash<QPair<QFontEngine *, int>, QGlyphRun> glyphRunHash;
1043 for (int i=0; i<d->lines.size(); ++i) {
1044 if (d->lines.at(i).from > from + length)
1045 break;
1046 else if (d->lines.at(i).from + d->lines.at(i).length >= from) {
1047 const QList<QGlyphRun> glyphRuns = QTextLine(i, d).glyphRuns(from, length, retrievalFlags);
1048 for (const QGlyphRun &glyphRun : glyphRuns) {
1049 QRawFont rawFont = glyphRun.rawFont();
1050
1051 QFontEngine *fontEngine = rawFont.d->fontEngine;
1052 QGlyphRun::GlyphRunFlags flags = glyphRun.flags();
1053 QPair<QFontEngine *, int> key(fontEngine, int(flags));
1054 // merge the glyph runs using the same font
1055 QGlyphRun &oldGlyphRun = glyphRunHash[key];
1056 if (oldGlyphRun.isEmpty()) {
1057 oldGlyphRun = glyphRun;
1058 } else {
1059 QList<quint32> indexes = oldGlyphRun.glyphIndexes();
1060 QList<QPointF> positions = oldGlyphRun.positions();
1061 QList<qsizetype> stringIndexes = oldGlyphRun.stringIndexes();
1062 QRectF boundingRect = oldGlyphRun.boundingRect();
1063
1064 indexes += glyphRun.glyphIndexes();
1065 positions += glyphRun.positions();
1066 stringIndexes += glyphRun.stringIndexes();
1067 boundingRect = boundingRect.united(glyphRun.boundingRect());
1068
1069 oldGlyphRun.setGlyphIndexes(indexes);
1070 oldGlyphRun.setPositions(positions);
1071 oldGlyphRun.setStringIndexes(stringIndexes);
1072 oldGlyphRun.setBoundingRect(boundingRect);
1073 }
1074 }
1075 }
1076 }
1077
1078 return glyphRunHash.values();
1079}
1080#endif // QT_NO_RAWFONT
1081
1087void QTextLayout::draw(QPainter *p, const QPointF &pos, const QList<FormatRange> &selections, const QRectF &clip) const
1088{
1089 if (d->lines.isEmpty())
1090 return;
1091
1092 if (!d->layoutData)
1093 d->itemize();
1094
1096
1097 QFixed clipy = (INT_MIN/256);
1098 QFixed clipe = (INT_MAX/256);
1099 if (clip.isValid()) {
1100 clipy = QFixed::fromReal(clip.y() - position.y());
1101 clipe = clipy + QFixed::fromReal(clip.height());
1102 }
1103
1104 int firstLine = 0;
1105 int lastLine = d->lines.size();
1106 for (int i = 0; i < d->lines.size(); ++i) {
1107 const QScriptLine &sl = d->lines.at(i);
1108
1109 if (sl.y > clipe) {
1110 lastLine = i;
1111 break;
1112 }
1113 if ((sl.y + sl.height()) < clipy) {
1114 firstLine = i;
1115 continue;
1116 }
1117 }
1118
1119 QPainterPath excludedRegion;
1120 QPainterPath textDoneRegion;
1121 for (int i = 0; i < selections.size(); ++i) {
1122 FormatRange selection = selections.at(i);
1123 QPainterPath region;
1125
1126 for (int line = firstLine; line < lastLine; ++line) {
1127 const QScriptLine &sl = d->lines.at(line);
1128 QTextLine tl(line, d);
1129
1130 QRectF lineRect(tl.naturalTextRect());
1131 lineRect.translate(position);
1132 lineRect.adjust(0, 0, d->leadingSpaceWidth(sl).toReal(), 0);
1133 lineRect.setBottom(qCeil(lineRect.bottom()));
1134
1135 bool isLastLineInBlock = (line == d->lines.size()-1);
1136 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1137
1138
1139 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1140 continue; // no actual intersection
1141
1142 const bool selectionStartInLine = sl.from <= selection.start;
1143 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1144
1145 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1146 addSelectedRegionsToPath(d, line, position, &selection, &region, clipIfValid(lineRect, clip));
1147 } else {
1148 region.addRect(clipIfValid(lineRect, clip));
1149 }
1150
1151 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1152 QRectF fullLineRect(tl.rect());
1153 fullLineRect.translate(position);
1154 fullLineRect.setRight(QFIXED_MAX);
1155 fullLineRect.setBottom(qCeil(fullLineRect.bottom()));
1156
1157 const bool rightToLeft = d->isRightToLeft();
1158
1159 if (!selectionEndInLine) {
1160 region.addRect(clipIfValid(rightToLeft ? QRectF(fullLineRect.topLeft(), lineRect.bottomLeft())
1161 : QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1162 }
1163 if (!selectionStartInLine) {
1164 region.addRect(clipIfValid(rightToLeft ? QRectF(lineRect.topRight(), fullLineRect.bottomRight())
1165 : QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1166 }
1167 } else if (!selectionEndInLine
1168 && isLastLineInBlock
1170 region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1171 lineRect.height()/4, lineRect.height()), clip));
1172 }
1173
1174 }
1175 {
1176 const QPen oldPen = p->pen();
1177 const QBrush oldBrush = p->brush();
1178
1179 p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1180 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1181 p->drawPath(region);
1182
1183 p->setPen(oldPen);
1184 p->setBrush(oldBrush);
1185 }
1186
1187
1188
1189 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1190 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1191
1192 if (hasBackground) {
1193 selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1194 // don't just clear the property, set an empty brush that overrides a potential
1195 // background brush specified in the text
1196 selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1197 selection.format.clearProperty(QTextFormat::OutlinePen);
1198 }
1199
1200 selection.format.setProperty(SuppressText, !hasText);
1201
1202 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1203 continue;
1204
1205 p->save();
1206 p->setClipPath(region, Qt::IntersectClip);
1207
1208 for (int line = firstLine; line < lastLine; ++line) {
1209 QTextLine l(line, d);
1210 l.draw_internal(p, position, &selection);
1211 }
1212 p->restore();
1213
1214 if (hasText) {
1215 textDoneRegion += region;
1216 } else {
1217 if (hasBackground)
1218 textDoneRegion -= region;
1219 }
1220
1221 excludedRegion += region;
1222 }
1223
1224 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1225 if (!needsTextButNoBackground.isEmpty()){
1226 p->save();
1227 p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1229 selection.start = 0;
1230 selection.length = INT_MAX;
1231 selection.format.setProperty(SuppressBackground, true);
1232 for (int line = firstLine; line < lastLine; ++line) {
1233 QTextLine l(line, d);
1234 l.draw_internal(p, position, &selection);
1235 }
1236 p->restore();
1237 }
1238
1239 if (!excludedRegion.isEmpty()) {
1240 p->save();
1243 br.setRight(QFIXED_MAX);
1244 if (!clip.isNull())
1245 br = br.intersected(clip);
1246 path.addRect(br);
1247 path -= excludedRegion;
1248 p->setClipPath(path, Qt::IntersectClip);
1249 }
1250
1251 for (int i = firstLine; i < lastLine; ++i) {
1252 QTextLine l(i, d);
1253 l.draw(p, position);
1254 }
1255 if (!excludedRegion.isEmpty())
1256 p->restore();
1257
1258
1259 if (!d->cacheGlyphs)
1260 d->freeMemory();
1261}
1262
1271void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1272{
1273 drawCursor(p, pos, cursorPosition, 1);
1274}
1275
1283void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1284{
1285 if (d->lines.isEmpty())
1286 return;
1287
1288 if (!d->layoutData)
1289 d->itemize();
1290
1292
1293 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.size());
1294 int line = d->lineNumberForTextPosition(cursorPosition);
1295 if (line < 0)
1296 line = 0;
1297 if (line >= d->lines.size())
1298 return;
1299
1300 QTextLine l(line, d);
1301 const QScriptLine &sl = d->lines.at(line);
1302
1303 qreal x = position.x() + l.cursorToX(cursorPosition);
1304
1305 QFixed base = sl.base();
1306 QFixed descent = sl.descent;
1307 bool rightToLeft = d->isRightToLeft();
1308
1309 const int realCursorPosition = cursorPosition;
1310 if (d->visualCursorMovement()) {
1311 if (cursorPosition == sl.from + sl.length)
1312 --cursorPosition;
1313 } else {
1314 --cursorPosition;
1315 }
1316 int itm = d->findItem(cursorPosition);
1317
1318 if (itm >= 0) {
1319 const QScriptItem *si = &d->layoutData->items.at(itm);
1320 // Same logic as in cursorToX to handle edges between writing directions to prioritise the script item
1321 // that matches the writing direction of the paragraph.
1322 if (d->layoutData->hasBidi && !d->visualCursorMovement() && si->analysis.bidiLevel % 2 != rightToLeft) {
1323 int neighborItem = itm;
1324 if (neighborItem > 0 && si->position == realCursorPosition)
1325 --neighborItem;
1326 else if (neighborItem < d->layoutData->items.size() - 1 && si->position + si->num_glyphs == realCursorPosition)
1327 ++neighborItem;
1328 const bool onBoundary = neighborItem != itm
1329 && si->analysis.bidiLevel != d->layoutData->items[neighborItem].analysis.bidiLevel;
1330 if (onBoundary && rightToLeft != si->analysis.bidiLevel % 2) {
1331 itm = neighborItem;
1332 si = &d->layoutData->items[itm];
1333 }
1334 }
1335 // objects need some special treatment as they can have special alignment or be floating
1337 if (si->ascent > 0)
1338 base = si->ascent;
1339 if (si->descent > 0)
1340 descent = si->descent;
1341 }
1342 rightToLeft = si->analysis.bidiLevel % 2;
1343 }
1344 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1345 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1346 && (p->transform().type() > QTransform::TxTranslate);
1347 if (toggleAntialiasing)
1348 p->setRenderHint(QPainter::Antialiasing);
1349 QPainter::CompositionMode origCompositionMode = p->compositionMode();
1350 if (p->paintEngine()->hasFeature(QPaintEngine::RasterOpModes))
1351 p->setCompositionMode(QPainter::RasterOp_NotDestination);
1352 const QTransform &deviceTransform = p->deviceTransform();
1353 const qreal xScale = deviceTransform.m11();
1354 if (deviceTransform.type() != QTransform::TxScale || std::trunc(xScale) == xScale) {
1355 p->fillRect(QRectF(x, y, qreal(width), (base + descent).toReal()), p->pen().brush());
1356 } else {
1357 // Ensure consistently rendered cursor width under fractional scaling
1358 const QPen origPen = p->pen();
1359 QPen pen(origPen.brush(), qRound(width * xScale), Qt::SolidLine, Qt::FlatCap);
1360 pen.setCosmetic(true);
1361 const qreal center = x + qreal(width) / 2;
1362 p->setPen(pen);
1363 p->drawLine(QPointF(center, y), QPointF(center, qCeil(y + (base + descent).toReal())));
1364 p->setPen(origPen);
1365 }
1366 p->setCompositionMode(origCompositionMode);
1367 if (toggleAntialiasing)
1368 p->setRenderHint(QPainter::Antialiasing, false);
1369 if (d->layoutData->hasBidi) {
1370 const int arrow_extent = 4;
1371 int sign = rightToLeft ? -1 : 1;
1372 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1373 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1374 }
1375 return;
1376}
1377
1445{
1446 const QScriptLine& sl = eng->lines.at(index);
1447 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1448}
1449
1454{
1455 const QScriptLine& sl = eng->lines.at(index);
1456 QFixed x = sl.x + eng->alignLine(sl);
1457
1458 QFixed width = sl.textWidth;
1459 if (sl.justified)
1460 width = sl.width;
1461
1462 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1463}
1464
1471{
1472 return eng->lines.at(index).x.toReal();
1473}
1474
1481{
1482 return eng->lines.at(index).y.toReal();
1483}
1484
1491{
1492 return eng->lines.at(index).width.toReal();
1493}
1494
1495
1502{
1503 return eng->lines.at(index).ascent.toReal();
1504}
1505
1512{
1513 return eng->lines.at(index).descent.toReal();
1514}
1515
1524{
1525 return eng->lines.at(index).height().ceil().toReal();
1526}
1527
1536{
1537 return eng->lines.at(index).leading.toReal();
1538}
1539
1555{
1556 eng->lines[index].leadingIncluded= included;
1557
1558}
1559
1571{
1572 return eng->lines.at(index).leadingIncluded;
1573}
1574
1581{
1582 return eng->lines.at(index).textWidth.toReal();
1583}
1584
1596{
1597 return eng->lines.at(index).textAdvance.toReal();
1598}
1599
1608{
1609 QScriptLine &line = eng->lines[index];
1610 if (!eng->layoutData) {
1611 qWarning("QTextLine: Can't set a line width while not layouting.");
1612 return;
1613 }
1614
1616 if (line.length
1617 && line.textWidth <= line.width
1618 && line.from + line.length == eng->layoutData->string.size())
1619 // no need to do anything if the line is already layouted and the last one. This optimization helps
1620 // when using things in a single line layout.
1621 return;
1622 line.length = 0;
1623 line.textWidth = 0;
1624
1625 layout_helper(INT_MAX);
1626}
1627
1635void QTextLine::setNumColumns(int numColumns)
1636{
1637 QScriptLine &line = eng->lines[index];
1638 line.width = QFIXED_MAX;
1639 line.length = 0;
1640 line.textWidth = 0;
1641 layout_helper(numColumns);
1642}
1643
1652void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1653{
1654 QScriptLine &line = eng->lines[index];
1655 line.width = QFixed::fromReal(qBound(0.0, alignmentWidth, qreal(QFIXED_MAX)));
1656 line.length = 0;
1657 line.textWidth = 0;
1658 layout_helper(numColumns);
1659}
1660
1661#if 0
1662#define LB_DEBUG qDebug
1663#else
1664#define LB_DEBUG if (0) qDebug
1665#endif
1666
1667namespace {
1668
1669 struct LineBreakHelper
1670 {
1671 LineBreakHelper() = default;
1672
1673 QScriptLine tmpData;
1674 QScriptLine spaceData;
1675
1676 QGlyphLayout glyphs;
1677
1678 int glyphCount = 0;
1679 int maxGlyphs = 0;
1680 int currentPosition = 0;
1681 glyph_t previousGlyph = 0;
1682 QExplicitlySharedDataPointer<QFontEngine> previousGlyphFontEngine;
1683
1684 QFixed minw;
1685 QFixed currentSoftHyphenWidth;
1686 QFixed commitedSoftHyphenWidth;
1687 QFixed rightBearing;
1688 QFixed minimumRightBearing;
1689
1690 QExplicitlySharedDataPointer<QFontEngine> fontEngine;
1691 const unsigned short *logClusters = nullptr;
1692
1693 bool manualWrap = false;
1694 bool whiteSpaceOrObject = true;
1695
1696 bool checkFullOtherwiseExtend(QScriptLine &line);
1697
1698 QFixed calculateNewWidth(const QScriptLine &line) const {
1699 return line.textWidth + tmpData.textWidth + spaceData.textWidth
1700 + (line.textWidth > 0 ? currentSoftHyphenWidth : QFixed()) + negativeRightBearing();
1701 }
1702
1703 inline glyph_t currentGlyph() const
1704 {
1705 Q_ASSERT(currentPosition > 0);
1706 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1707
1708 return glyphs.glyphs[logClusters[currentPosition - 1]];
1709 }
1710
1711 inline void saveCurrentGlyph()
1712 {
1713 previousGlyph = 0;
1714 if (currentPosition > 0 &&
1715 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1716 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1717 previousGlyphFontEngine = fontEngine;
1718 }
1719 }
1720
1721 inline void calculateRightBearing(QFontEngine *engine, glyph_t glyph)
1722 {
1724 qreal rb;
1725 engine->getGlyphBearings(glyph, nullptr, &rb);
1726
1727 // We only care about negative right bearings, so we limit the range
1728 // of the bearing here so that we can assume it's negative in the rest
1729 // of the code, as well ase use QFixed(1) as a sentinel to represent
1730 // the state where we have yet to compute the right bearing.
1731 rightBearing = qMin(QFixed::fromReal(rb), QFixed(0));
1732 }
1733
1734 inline void calculateRightBearing()
1735 {
1736 if (currentPosition <= 0)
1737 return;
1738 calculateRightBearing(fontEngine.data(), currentGlyph());
1739 }
1740
1741 inline void calculateRightBearingForPreviousGlyph()
1742 {
1743 if (previousGlyph > 0)
1744 calculateRightBearing(previousGlyphFontEngine.data(), previousGlyph);
1745 }
1746
1747 static const QFixed RightBearingNotCalculated;
1748
1749 inline void resetRightBearing()
1750 {
1751 rightBearing = RightBearingNotCalculated;
1752 }
1753
1754 // We express the negative right bearing as an absolute number
1755 // so that it can be applied to the width using addition.
1756 inline QFixed negativeRightBearing() const
1757 {
1758 if (rightBearing == RightBearingNotCalculated)
1759 return QFixed(0);
1760
1761 return qAbs(rightBearing);
1762 }
1763 };
1764
1765Q_CONSTINIT const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1);
1766
1767inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1768{
1769 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1770
1771 QFixed newWidth = calculateNewWidth(line);
1772 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1773 return true;
1774
1775 const QFixed oldTextWidth = line.textWidth;
1776 line += tmpData;
1777 line.textWidth += spaceData.textWidth;
1778
1779 line.length += spaceData.length;
1780 tmpData.textWidth = 0;
1781 tmpData.length = 0;
1782 spaceData.textWidth = 0;
1783 spaceData.length = 0;
1784
1785 if (oldTextWidth != line.textWidth || currentSoftHyphenWidth > 0) {
1786 commitedSoftHyphenWidth = currentSoftHyphenWidth;
1787 currentSoftHyphenWidth = 0;
1788 }
1789
1790 return false;
1791}
1792
1793} // anonymous namespace
1794
1795
1796static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1797 const QScriptItem &current, const unsigned short *logClusters,
1798 const QGlyphLayout &glyphs, QFixed *clusterWidth = nullptr)
1799{
1800 int glyphPosition = logClusters[pos];
1801 do { // got to the first next cluster
1802 ++pos;
1803 ++line.length;
1804 } while (pos < end && logClusters[pos] == glyphPosition);
1805 QFixed clusterWid = line.textWidth;
1806 do { // calculate the textWidth for the rest of the current cluster.
1807 if (!glyphs.attributes[glyphPosition].dontPrint)
1808 line.textWidth += glyphs.advances[glyphPosition];
1809 ++glyphPosition;
1810 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1811
1812 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1813
1814 if (clusterWidth)
1815 *clusterWidth += (line.textWidth - clusterWid);
1816 ++glyphCount;
1817}
1818
1819
1820// fill QScriptLine
1821void QTextLine::layout_helper(int maxGlyphs)
1822{
1823 QScriptLine &line = eng->lines[index];
1824 line.length = 0;
1825 line.trailingSpaces = 0;
1826 line.textWidth = 0;
1827 line.hasTrailingSpaces = false;
1828
1829 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.size()) {
1830 line.setDefaultHeight(eng);
1831 return;
1832 }
1833
1834 Q_ASSERT(line.from < eng->layoutData->string.size());
1835
1836 LineBreakHelper lbh;
1837
1838 lbh.maxGlyphs = maxGlyphs;
1839
1840 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1841 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1842 const bool breakWordOrAny = breakany || (wrapMode == QTextOption::WrapAtWordBoundaryOrAnywhere);
1843 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1844
1845 int item = -1;
1846 int newItem = eng->findItem(line.from);
1847 Q_ASSERT(newItem >= 0);
1848
1849 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, int(eng->layoutData->items.size()), line.width.toReal());
1850
1851 Qt::Alignment alignment = eng->option.alignment();
1852
1853 const QCharAttributes *attributes = eng->attributes();
1854 if (!attributes)
1855 return;
1856 lbh.currentPosition = line.from;
1857 int end = 0;
1858 lbh.logClusters = eng->layoutData->logClustersPtr;
1859 lbh.previousGlyph = 0;
1860
1861 bool manuallyWrapped = false;
1862 bool hasInlineObject = false;
1863 QFixed maxInlineObjectHeight = 0;
1864
1865 while (newItem < eng->layoutData->items.size()) {
1866 lbh.resetRightBearing();
1867 if (newItem != item) {
1868 item = newItem;
1869 const QScriptItem &current = eng->layoutData->items.at(item);
1870 if (!current.num_glyphs) {
1871 eng->shape(item);
1872 attributes = eng->attributes();
1873 if (!attributes)
1874 return;
1875 lbh.logClusters = eng->layoutData->logClustersPtr;
1876 }
1877 lbh.currentPosition = qMax(line.from, current.position);
1878 end = current.position + eng->length(item);
1879 lbh.glyphs = eng->shapedGlyphs(&current);
1880 QFontEngine *fontEngine = eng->fontEngine(current);
1881 if (lbh.fontEngine != fontEngine) {
1882 lbh.fontEngine = fontEngine;
1883 lbh.minimumRightBearing = qMin(QFixed(),
1884 QFixed::fromReal(fontEngine->minRightBearing()));
1885 }
1886 }
1887 const QScriptItem &current = eng->layoutData->items.at(item);
1888
1889 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1890 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1891 current.ascent);
1892 if (current.analysis.flags != QScriptAnalysis::Object) {
1893 // objects need some special treatment as they can special alignment or be floating
1894 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1895 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1896 }
1897
1899 lbh.whiteSpaceOrObject = true;
1900 if (lbh.checkFullOtherwiseExtend(line))
1901 goto found;
1902
1903 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1904 QFixed tabWidth = eng->calculateTabWidth(item, x);
1905 attributes = eng->attributes();
1906 if (!attributes)
1907 return;
1908 lbh.logClusters = eng->layoutData->logClustersPtr;
1909 lbh.glyphs = eng->shapedGlyphs(&current);
1910
1911 lbh.spaceData.textWidth += tabWidth;
1912 lbh.spaceData.length++;
1913 newItem = item + 1;
1914
1915 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1916 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1917
1918 if (lbh.checkFullOtherwiseExtend(line))
1919 goto found;
1921 lbh.whiteSpaceOrObject = true;
1922 // if the line consists only of the line separator make sure
1923 // we have a sane height
1924 if (!line.length && !lbh.tmpData.length)
1925 line.setDefaultHeight(eng);
1926 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1927 if (lbh.checkFullOtherwiseExtend(line))
1928 goto found;
1929
1930 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1931 current, lbh.logClusters, lbh.glyphs);
1932 } else {
1933 lbh.tmpData.length++;
1934 lbh.calculateRightBearingForPreviousGlyph();
1935 }
1936 line += lbh.tmpData;
1937 manuallyWrapped = true;
1938 goto found;
1939 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1940 lbh.whiteSpaceOrObject = true;
1941 lbh.tmpData.length++;
1942
1943 if (QTextDocumentPrivate::get(eng->block) != nullptr) {
1944 QTextInlineObject inlineObject(item, eng);
1945 QTextFormat f = inlineObject.format();
1946 eng->docLayout()->positionInlineObject(inlineObject, eng->block.position() + current.position, f);
1948 if (valign != QTextCharFormat::AlignTop && valign != QTextCharFormat::AlignBottom) {
1949 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1950 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1951 }
1952 }
1953
1954 hasInlineObject = true;
1955 maxInlineObjectHeight = qMax(maxInlineObjectHeight, current.ascent + current.descent);
1956
1957 lbh.tmpData.textWidth += current.width;
1958
1959 newItem = item + 1;
1960 ++lbh.glyphCount;
1961 if (lbh.checkFullOtherwiseExtend(line))
1962 goto found;
1963 } else if (attributes[lbh.currentPosition].whiteSpace
1964 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1965 lbh.whiteSpaceOrObject = true;
1966 while (lbh.currentPosition < end
1967 && attributes[lbh.currentPosition].whiteSpace
1968 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1969 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1970 current, lbh.logClusters, lbh.glyphs);
1971 }
1972 } else {
1973 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width)
1974 goto found;
1975
1976 lbh.whiteSpaceOrObject = false;
1977 bool sb_or_ws = false;
1978 lbh.saveCurrentGlyph();
1979 QFixed accumulatedTextWidth;
1980 do {
1981 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1982 current, lbh.logClusters, lbh.glyphs, &accumulatedTextWidth);
1983
1984 // This is a hack to fix a regression caused by the introduction of the
1985 // whitespace flag to non-breakable spaces and will cause the non-breakable
1986 // spaces to behave as in previous Qt versions in the line breaking algorithm.
1987 // The line breaks do not currently follow the Unicode specs, but fixing this would
1988 // require refactoring the code and would cause behavioral regressions.
1989 const bool isBreakableSpace = lbh.currentPosition < eng->layoutData->string.size()
1990 && attributes[lbh.currentPosition].whiteSpace
1991 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak;
1992
1993 if (lbh.currentPosition >= eng->layoutData->string.size()
1994 || isBreakableSpace
1995 || attributes[lbh.currentPosition].lineBreak
1996 || lbh.tmpData.textWidth >= QFIXED_MAX) {
1997 sb_or_ws = true;
1998 break;
1999 } else if (attributes[lbh.currentPosition].graphemeBoundary) {
2000 if (breakWordOrAny) {
2001 lbh.minw = qMax(accumulatedTextWidth, lbh.minw);
2002 accumulatedTextWidth = 0;
2003 }
2004 if (breakany)
2005 break;
2006 }
2007 } while (lbh.currentPosition < end);
2008 lbh.minw = qMax(accumulatedTextWidth, lbh.minw);
2009
2010 if (lbh.currentPosition > 0 && lbh.currentPosition <= end
2011 && (lbh.currentPosition == end || attributes[lbh.currentPosition].lineBreak)
2012 && eng->layoutData->string.at(lbh.currentPosition - 1) == QChar::SoftHyphen) {
2013 // if we are splitting up a word because of
2014 // a soft hyphen then we ...
2015 //
2016 // a) have to take the width of the soft hyphen into
2017 // account to see if the first syllable(s) /and/
2018 // the soft hyphen fit into the line
2019 //
2020 // b) if we are so short of available width that the
2021 // soft hyphen is the first breakable position, then
2022 // we don't want to show it. However we initially
2023 // have to take the width for it into account so that
2024 // the text document layout sees the overflow and
2025 // switch to break-anywhere mode, in which we
2026 // want the soft-hyphen to slip into the next line
2027 // and thus become invisible again.
2028 //
2029 lbh.currentSoftHyphenWidth = lbh.glyphs.advances[lbh.logClusters[lbh.currentPosition - 1]];
2030 }
2031
2032 if (sb_or_ws|breakany) {
2033 // To compute the final width of the text we need to take negative right bearing
2034 // into account (negative right bearing means the glyph has pixel data past the
2035 // advance length). Note that the negative right bearing is an absolute number,
2036 // so that we can apply it to the width using straight forward addition.
2037
2038 // Store previous right bearing (for the already accepted glyph) in case we
2039 // end up breaking due to the current glyph being too wide.
2040 QFixed previousRightBearing = lbh.rightBearing;
2041
2042 // We skip calculating the right bearing if the minimum negative bearing is too
2043 // small to possibly expand the text beyond the edge. Note that this optimization
2044 // will in some cases fail, as the minimum right bearing reported by the font
2045 // engine may not cover all the glyphs in the font. The result is that we think
2046 // we don't need to break at the current glyph (because the right bearing is 0),
2047 // and when we then end up breaking on the next glyph we compute the right bearing
2048 // and end up with a line width that is slightly larger width than what was requested.
2049 // Unfortunately we can't remove this optimization as it will slow down text
2050 // layouting significantly, so we accept the slight correctness issue.
2051 if ((lbh.calculateNewWidth(line) + qAbs(lbh.minimumRightBearing)) > line.width)
2052 lbh.calculateRightBearing();
2053
2054 if (lbh.checkFullOtherwiseExtend(line)) {
2055
2056 // We are too wide to accept the next glyph with its bearing, so we restore the
2057 // right bearing to that of the previous glyph (the one that was already accepted),
2058 // so that the bearing can be be applied to the final width of the text below.
2059 if (previousRightBearing != LineBreakHelper::RightBearingNotCalculated)
2060 lbh.rightBearing = previousRightBearing;
2061 else
2062 lbh.calculateRightBearingForPreviousGlyph();
2063
2064 line.textWidth += lbh.commitedSoftHyphenWidth;
2065
2066 goto found;
2067 }
2068 }
2069 lbh.saveCurrentGlyph();
2070 }
2071 if (lbh.currentPosition == end)
2072 newItem = item + 1;
2073 }
2074 LB_DEBUG("reached end of line");
2075 lbh.checkFullOtherwiseExtend(line);
2076 line.textWidth += lbh.commitedSoftHyphenWidth;
2077found:
2078 line.textAdvance = line.textWidth;
2079
2080 // If right bearing has not been calculated yet, do that now
2081 if (lbh.rightBearing == LineBreakHelper::RightBearingNotCalculated && !lbh.whiteSpaceOrObject)
2082 lbh.calculateRightBearing();
2083
2084 // Then apply any negative right bearing
2085 line.textWidth += lbh.negativeRightBearing();
2086
2087 if (line.length == 0) {
2088 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
2089 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
2090 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
2091 line += lbh.tmpData;
2092 }
2093
2094 if (hasInlineObject && QTextDocumentPrivate::get(eng->block) != nullptr) {
2095 // position top/bottom aligned inline objects
2096 if (maxInlineObjectHeight > line.ascent + line.descent) {
2097 // extend line height if required
2098 QFixed toAdd = (maxInlineObjectHeight - line.ascent - line.descent)/2;
2099 line.ascent += toAdd;
2100 line.descent = maxInlineObjectHeight - line.ascent;
2101 }
2102 int startItem = eng->findItem(line.from);
2103 int endItem = eng->findItem(line.from + line.length);
2104 if (endItem < 0)
2105 endItem = eng->layoutData->items.size();
2106 for (int item = startItem; item < endItem; ++item) {
2107 QScriptItem &current = eng->layoutData->items[item];
2108 if (current.analysis.flags == QScriptAnalysis::Object) {
2109 QTextInlineObject inlineObject(item, eng);
2110 QTextCharFormat::VerticalAlignment align = inlineObject.format().toCharFormat().verticalAlignment();
2111 QFixed height = current.ascent + current.descent;
2112 switch (align) {
2114 current.ascent = line.ascent;
2115 current.descent = height - line.ascent;
2116 break;
2118 current.ascent = (line.ascent + line.descent) / 2 - line.descent + height / 2;
2119 current.descent = height - line.ascent;
2120 break;
2122 current.descent = line.descent;
2123 current.ascent = height - line.descent;
2124 break;
2125 default:
2126 break;
2127 }
2128 Q_ASSERT(line.ascent >= current.ascent);
2129 Q_ASSERT(line.descent >= current.descent);
2130 }
2131 }
2132 }
2133
2134
2135 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
2136 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
2137 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
2138
2139 const QFixed trailingSpace = (eng->option.flags() & QTextOption::IncludeTrailingSpaces
2140 ? lbh.spaceData.textWidth
2141 : QFixed(0));
2142 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
2143 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
2144 || (lbh.maxGlyphs == INT_MAX && line.textWidth > (line.width - trailingSpace))) {
2145
2146 eng->option.setWrapMode(QTextOption::WrapAnywhere);
2147 layout_helper(lbh.maxGlyphs);
2148 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
2149 return;
2150 }
2151 }
2152
2153 if (lbh.manualWrap) {
2154 eng->minWidth = qMax(eng->minWidth, line.textWidth);
2155 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
2156 } else {
2157 eng->minWidth = qMax(eng->minWidth, lbh.minw);
2158 if (qAddOverflow(eng->layoutData->currentMaxWidth, line.textWidth, &eng->layoutData->currentMaxWidth))
2159 eng->layoutData->currentMaxWidth = QFIXED_MAX;
2160 if (!manuallyWrapped) {
2161 if (qAddOverflow(eng->layoutData->currentMaxWidth, lbh.spaceData.textWidth, &eng->layoutData->currentMaxWidth))
2162 eng->layoutData->currentMaxWidth = QFIXED_MAX;
2163 }
2164 eng->maxWidth = qMax(eng->maxWidth, eng->layoutData->currentMaxWidth);
2165 if (manuallyWrapped)
2166 eng->layoutData->currentMaxWidth = 0;
2167 }
2168
2169 line.textWidth += trailingSpace;
2170 if (lbh.spaceData.length) {
2171 line.trailingSpaces = lbh.spaceData.length;
2172 line.hasTrailingSpaces = true;
2173 }
2174
2175 line.justified = false;
2176 line.gridfitted = false;
2177}
2178
2183{
2184 eng->lines[index].x = QFixed::fromReal(pos.x());
2185 eng->lines[index].y = QFixed::fromReal(pos.y());
2186}
2187
2192{
2193 return QPointF(eng->lines.at(index).x.toReal(), eng->lines.at(index).y.toReal());
2194}
2195
2196// ### DOC: I have no idea what this means/does.
2197// You create a text layout with a string of text. Once you laid
2198// it out, it contains a number of QTextLines. from() returns the position
2199// inside the text string where this line starts. If you e.g. has a
2200// text of "This is a string", laid out into two lines (the second
2201// starting at the word 'a'), layout.lineAt(0).from() == 0 and
2202// layout.lineAt(1).from() == 8.
2208{
2209 return eng->lines.at(index).from;
2210}
2211
2218{
2219 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
2220 && eng->block.isValid() && index == eng->lines.size()-1) {
2221 return eng->lines.at(index).length - 1;
2222 }
2223 return eng->lines.at(index).length + eng->lines.at(index).trailingSpaces;
2224}
2225
2226static void drawBackground(QPainter *p, const QTextCharFormat &chf, const QRectF &r)
2227{
2228 QBrush bg = chf.background();
2229 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2230 p->fillRect(r.toAlignedRect(), bg);
2231}
2232
2233static void setPen(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf)
2234{
2235 QBrush c = chf.foreground();
2236 if (c.style() == Qt::NoBrush)
2237 p->setPen(defaultPen);
2238 else
2239 p->setPen(QPen(c, 0));
2240}
2241
2242#if !defined(QT_NO_RAWFONT)
2244 const QString &text,
2245 const QGlyphLayout &glyphLayout,
2246 const QPointF &pos,
2247 const QGlyphRun::GlyphRunFlags &flags,
2248 QTextLayout::GlyphRunRetrievalFlags retrievalFlags,
2249 QFixed selectionX,
2250 QFixed selectionWidth,
2251 int glyphsStart,
2252 int glyphsEnd,
2253 unsigned short *logClusters,
2254 int textPosition,
2255 int textLength)
2256{
2257 Q_ASSERT(logClusters != nullptr);
2258
2259 QGlyphRun glyphRun;
2260
2262
2263 int rangeStart = textPosition;
2264 int logClusterIndex = 0;
2265 while (logClusters[logClusterIndex] != glyphsStart && rangeStart < textPosition + textLength) {
2266 ++logClusterIndex;
2267 ++rangeStart;
2268 }
2269
2270 int rangeEnd = rangeStart;
2271 while (logClusters[logClusterIndex] != glyphsEnd && rangeEnd < textPosition + textLength) {
2272 ++logClusterIndex;
2273 ++rangeEnd;
2274 }
2275
2276 d->textRangeStart = rangeStart;
2277 d->textRangeEnd = rangeEnd;
2278
2279 // Make a font for this particular engine
2280 QRawFont font;
2282 fontD->setFontEngine(fontEngine);
2283
2284 QVarLengthArray<glyph_t> glyphsArray;
2285 QVarLengthArray<QFixedPoint> positionsArray;
2286
2287 QTextItem::RenderFlags renderFlags;
2288 if (flags.testFlag(QGlyphRun::Overline))
2289 renderFlags |= QTextItem::Overline;
2290 if (flags.testFlag(QGlyphRun::Underline))
2291 renderFlags |= QTextItem::Underline;
2292 if (flags.testFlag(QGlyphRun::StrikeOut))
2293 renderFlags |= QTextItem::StrikeOut;
2294 if (flags.testFlag(QGlyphRun::RightToLeft))
2295 renderFlags |= QTextItem::RightToLeft;
2296
2297 fontEngine->getGlyphPositions(glyphLayout, QTransform(), renderFlags, glyphsArray,
2298 positionsArray);
2299 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2300
2301 qreal fontHeight = font.ascent() + font.descent();
2302 qreal minY = 0;
2303 qreal maxY = 0;
2304 QList<quint32> glyphs;
2305 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2306 glyphs.reserve(glyphsArray.size());
2307 QList<QPointF> positions;
2308 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2309 positions.reserve(glyphsArray.size());
2310 QList<qsizetype> stringIndexes;
2311 if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2312 stringIndexes.reserve(glyphsArray.size());
2313
2314 int nextClusterIndex = 0;
2315 int currentClusterIndex = 0;
2316 for (int i = 0; i < glyphsArray.size(); ++i) {
2317 const int glyphArrayIndex = i + glyphsStart;
2318 // Search for the next cluster in the string (or the end of string if there are no
2319 // more clusters)
2320 if (retrievalFlags & QTextLayout::RetrieveStringIndexes) {
2321 if (nextClusterIndex < textLength && logClusters[nextClusterIndex] == glyphArrayIndex) {
2322 currentClusterIndex = nextClusterIndex; // Store current cluster
2323 while (logClusters[nextClusterIndex] == glyphArrayIndex && nextClusterIndex < textLength)
2324 ++nextClusterIndex;
2325 }
2326
2327 // We are now either at end of string (no more clusters) or we are not yet at the
2328 // next cluster in glyph array. We fill in current cluster so that there is always one
2329 // entry in stringIndexes for each glyph.
2330 Q_ASSERT(nextClusterIndex == textLength || logClusters[nextClusterIndex] != glyphArrayIndex);
2331 stringIndexes.append(textPosition + currentClusterIndex);
2332 }
2333
2334 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes) {
2335 glyph_t glyphIndex = glyphsArray.at(i) & 0xffffff;
2336 glyphs.append(glyphIndex);
2337 }
2338
2339 QPointF position = positionsArray.at(i).toPointF() + pos;
2340 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2341 positions.append(position);
2342
2343 if (i == 0) {
2344 maxY = minY = position.y();
2345 } else {
2346 minY = qMin(minY, position.y());
2347 maxY = qMax(maxY, position.y());
2348 }
2349 }
2350
2351 qreal height = maxY + fontHeight - minY;
2352
2353 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2354 glyphRun.setGlyphIndexes(glyphs);
2355 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2356 glyphRun.setPositions(positions);
2357 if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2358 glyphRun.setStringIndexes(stringIndexes);
2359 if (retrievalFlags & QTextLayout::RetrieveString)
2360 glyphRun.setSourceString(text);
2361 glyphRun.setFlags(flags);
2362 glyphRun.setRawFont(font);
2363
2364 glyphRun.setBoundingRect(QRectF(selectionX.toReal(), minY - font.ascent(),
2365 selectionWidth.toReal(), height));
2366
2367 return glyphRun;
2368}
2369
2370# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
2391QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
2392{
2394}
2395# endif
2396
2414QList<QGlyphRun> QTextLine::glyphRuns(int from,
2415 int length,
2416 QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
2417{
2418 const QScriptLine &line = eng->lines.at(index);
2419
2420 if (line.length == 0)
2421 return QList<QGlyphRun>();
2422
2423 if (from < 0)
2424 from = textStart();
2425
2426 if (length < 0)
2427 length = textLength();
2428
2429 if (length == 0)
2430 return QList<QGlyphRun>();
2431
2433 selection.start = from;
2435
2436 QTextLineItemIterator iterator(eng, index, QPointF(), &selection);
2437 qreal y = line.y.toReal() + line.base().toReal();
2438 QList<QGlyphRun> glyphRuns;
2439 while (!iterator.atEnd()) {
2440 QScriptItem &si = iterator.next();
2442 continue;
2443
2444 if (from >= 0 && length >= 0 && (from >= iterator.itemEnd || from + length <= iterator.itemStart))
2445 continue;
2446
2447 QPointF pos(iterator.x.toReal(), y);
2448
2449 QFont font;
2450 QGlyphRun::GlyphRunFlags flags;
2451 if (!eng->useRawFont) {
2452 font = eng->font(si);
2453 if (font.overline())
2455 if (font.underline())
2457 if (font.strikeOut())
2459 }
2460
2461 bool rtl = false;
2462 if (si.analysis.bidiLevel % 2) {
2464 rtl = true;
2465 }
2466
2467 int relativeFrom = qMax(iterator.itemStart, from) - si.position;
2468 int relativeTo = qMin(iterator.itemEnd, from + length) - 1 - si.position;
2469
2470 unsigned short *logClusters = eng->logClusters(&si);
2471 int glyphsStart = logClusters[relativeFrom];
2472 int glyphsEnd = (relativeTo == iterator.itemLength) ? si.num_glyphs - 1 : logClusters[relativeTo];
2473 // the glyph index right next to the requested range
2474 int nextGlyphIndex = (relativeTo < iterator.itemLength - 1) ? logClusters[relativeTo + 1] : si.num_glyphs;
2475 if (nextGlyphIndex - 1 > glyphsEnd)
2476 glyphsEnd = nextGlyphIndex - 1;
2477 bool startsInsideLigature = relativeFrom > 0 && logClusters[relativeFrom - 1] == glyphsStart;
2478 bool endsInsideLigature = nextGlyphIndex == glyphsEnd;
2479
2480 int itemGlyphsStart = logClusters[iterator.itemStart - si.position];
2481 int itemGlyphsEnd = logClusters[iterator.itemEnd - 1 - si.position];
2482
2483 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si);
2484
2485 // Calculate new x position of glyph layout for a subset. This becomes somewhat complex
2486 // when we're breaking a RTL script item, since the expected position passed into
2487 // getGlyphPositions() is the left-most edge of the left-most glyph in an RTL run.
2488 if (relativeFrom != (iterator.itemStart - si.position) && !rtl) {
2489 for (int i = itemGlyphsStart; i < glyphsStart; ++i) {
2490 if (!glyphLayout.attributes[i].dontPrint) {
2491 QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2492 pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2493 }
2494 }
2495 } else if (relativeTo != (iterator.itemEnd - si.position - 1) && rtl) {
2496 for (int i = itemGlyphsEnd; i > glyphsEnd; --i) {
2497 if (!glyphLayout.attributes[i].dontPrint) {
2498 QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2499 pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2500 }
2501 }
2502 }
2503
2504 glyphLayout = glyphLayout.mid(glyphsStart, glyphsEnd - glyphsStart + 1);
2505
2506 QFixed x;
2507 QFixed width;
2508 iterator.getSelectionBounds(&x, &width);
2509
2510 if (glyphLayout.numGlyphs > 0) {
2511 QFontEngine *mainFontEngine;
2512#ifndef QT_NO_RAWFONT
2513 if (eng->useRawFont && eng->rawFont.isValid())
2514 mainFontEngine= eng->fontEngine(si);
2515 else
2516#endif
2517 mainFontEngine = font.d->engineForScript(si.analysis.script);
2518
2519 if (mainFontEngine->type() == QFontEngine::Multi) {
2520 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2521 int start = rtl ? glyphLayout.numGlyphs : 0;
2522 int end = start - 1;
2523 int which = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2524 for (; (rtl && start > 0) || (!rtl && end < glyphLayout.numGlyphs - 1);
2525 rtl ? --start : ++end) {
2526 const int e = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2527 if (e == which)
2528 continue;
2529
2530 QGlyphLayout subLayout = glyphLayout.mid(start, end - start + 1);
2531 multiFontEngine->ensureEngineAt(which);
2532
2533 QGlyphRun::GlyphRunFlags subFlags = flags;
2534 if (start == 0 && startsInsideLigature)
2535 subFlags |= QGlyphRun::SplitLigature;
2536
2537 {
2538 QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
2539 eng->text,
2540 subLayout,
2541 pos,
2542 subFlags,
2543 retrievalFlags,
2544 x,
2545 width,
2546 glyphsStart + start,
2547 glyphsStart + end,
2548 logClusters + relativeFrom,
2549 relativeFrom + si.position,
2550 relativeTo - relativeFrom + 1);
2551 if (!glyphRun.isEmpty())
2552 glyphRuns.append(glyphRun);
2553 }
2554 for (int i = 0; i < subLayout.numGlyphs; ++i) {
2555 if (!subLayout.attributes[i].dontPrint) {
2556 QFixed justification = QFixed::fromFixed(subLayout.justifications[i].space_18d6);
2557 pos.rx() += (subLayout.advances[i] + justification).toReal();
2558 }
2559 }
2560
2561 if (rtl)
2562 end = start - 1;
2563 else
2564 start = end + 1;
2565 which = e;
2566 }
2567
2568 QGlyphLayout subLayout = glyphLayout.mid(start, end - start + 1);
2569 multiFontEngine->ensureEngineAt(which);
2570
2571 QGlyphRun::GlyphRunFlags subFlags = flags;
2572 if ((start == 0 && startsInsideLigature) || endsInsideLigature)
2573 subFlags |= QGlyphRun::SplitLigature;
2574
2575 QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
2576 eng->text,
2577 subLayout,
2578 pos,
2579 subFlags,
2580 retrievalFlags,
2581 x,
2582 width,
2583 glyphsStart + start,
2584 glyphsStart + end,
2585 logClusters + relativeFrom,
2586 relativeFrom + si.position,
2587 relativeTo - relativeFrom + 1);
2588 if (!glyphRun.isEmpty())
2589 glyphRuns.append(glyphRun);
2590 } else {
2591 if (startsInsideLigature || endsInsideLigature)
2593 QGlyphRun glyphRun = glyphRunWithInfo(mainFontEngine,
2594 eng->text,
2595 glyphLayout,
2596 pos,
2597 flags,
2598 retrievalFlags,
2599 x,
2600 width,
2601 glyphsStart,
2602 glyphsEnd,
2603 logClusters + relativeFrom,
2604 relativeFrom + si.position,
2605 relativeTo - relativeFrom + 1);
2606 if (!glyphRun.isEmpty())
2607 glyphRuns.append(glyphRun);
2608 }
2609 }
2610 }
2611
2612 return glyphRuns;
2613}
2614#endif // QT_NO_RAWFONT
2615
2622{
2623 draw_internal(painter, position, nullptr);
2624}
2625
2626void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
2628{
2629#ifndef QT_NO_RAWFONT
2630 // Not intended to work with rawfont
2631 Q_ASSERT(!eng->useRawFont);
2632#endif
2633 const QScriptLine &line = eng->lines[index];
2634
2635 bool noText = (selection && selection->format.property(SuppressText).toBool());
2636
2637 if (!line.length) {
2638 if (selection
2639 && selection->start <= line.from
2640 && selection->start + selection->length > line.from) {
2641
2642 const qreal lineHeight = line.height().toReal();
2643 QRectF r(origPos.x() + line.x.toReal(), origPos.y() + line.y.toReal(),
2644 lineHeight / 2, QFontMetrics(eng->font()).horizontalAdvance(u' '));
2645 drawBackground(p, selection->format, r);
2646 }
2647 return;
2648 }
2649
2650 Q_CONSTINIT static QRectF maxFixedRect(-QFIXED_MAX / 2, -QFIXED_MAX / 2, QFIXED_MAX, QFIXED_MAX);
2651 const bool xlateToFixedRange = !maxFixedRect.contains(origPos);
2652 QPointF pos;
2653 if (Q_LIKELY(!xlateToFixedRange))
2654 pos = origPos;
2655 else
2656 p->translate(origPos);
2657
2658
2659 QFixed lineBase = line.base();
2660 eng->clearDecorations();
2661 eng->enableDelayDecorations();
2662
2663 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2664
2665 const QTextFormatCollection *formatCollection = eng->formatCollection();
2666
2667 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2668
2669 auto prepareFormat = [suppressColors, selection, this](QTextCharFormat &format,
2670 QScriptItem *si) {
2671 format.merge(eng->format(si));
2672
2673 if (suppressColors) {
2674 format.clearForeground();
2675 format.clearBackground();
2677 }
2678 if (selection)
2679 format.merge(selection->format);
2680 };
2681
2682 {
2684 while (!iterator.atEnd()) {
2685 QScriptItem &si = iterator.next();
2686
2687 if (eng->hasFormats() || selection || formatCollection) {
2689 if (formatCollection != nullptr)
2690 format = formatCollection->defaultTextFormat();
2691 prepareFormat(format, &si);
2692 drawBackground(p, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2693 iterator.itemWidth.toReal(), line.height().toReal()));
2694 }
2695 }
2696 }
2697
2698 QPen pen = p->pen();
2699 {
2701 while (!iterator.atEnd()) {
2702 QScriptItem &si = iterator.next();
2703
2704 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2705 continue;
2706
2708 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2709 continue;
2710
2711 QFixed itemBaseLine = y;
2712 QFont f = eng->font(si);
2714 if (formatCollection != nullptr)
2715 format = formatCollection->defaultTextFormat();
2716
2717 if (eng->hasFormats() || selection || formatCollection) {
2718 prepareFormat(format, &si);
2719 setPen(p, pen, format);
2720
2721 const qreal baseLineOffset = format.baselineOffset() / 100.0;
2722 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2726 {
2727 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2728 QFixed height = fe->ascent() + fe->descent();
2729 itemBaseLine -= height * QFixed::fromReal(baseLineOffset);
2730
2731 if (valign == QTextCharFormat::AlignSubScript)
2732 itemBaseLine += height * QFixed::fromReal(format.subScriptBaseline() / 100.0);
2733 else if (valign == QTextCharFormat::AlignSuperScript)
2734 itemBaseLine -= height * QFixed::fromReal(format.superScriptBaseline() / 100.0);
2735 }
2736 }
2737
2739
2740 if (eng->hasFormats()) {
2741 p->save();
2743 QFixed itemY = y - si.ascent;
2744 switch (format.verticalAlignment()) {
2746 itemY = y - lineBase;
2747 break;
2749 itemY = y - lineBase + (line.height() - si.height()) / 2;
2750 break;
2752 itemY = y - lineBase + line.height() - si.height();
2753 break;
2754 default:
2755 break;
2756 }
2757
2758 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2759
2760 eng->docLayout()->drawInlineObject(p, itemRect,
2761 QTextInlineObject(iterator.item, eng),
2762 si.position + eng->block.position(),
2763 format);
2764 if (selection) {
2765 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2766 if (bg.style() != Qt::NoBrush) {
2767 QColor c = bg.color();
2768 c.setAlpha(128);
2769 p->fillRect(itemRect, c);
2770 }
2771 }
2772 } else { // si.isTab
2773 QFont f = eng->font(si);
2774 QTextItemInt gf(si, &f, format);
2775 gf.chars = nullptr;
2776 gf.num_chars = 0;
2777 gf.width = iterator.itemWidth;
2778 QPainterPrivate::get(p)->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf, eng);
2779 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2780 const QChar visualTab = QChar(QChar::VisualTabCharacter);
2781 int w = QFontMetrics(f).horizontalAdvance(visualTab);
2782 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2783 if (x < 0)
2784 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2785 iterator.itemWidth.toReal(), line.height().toReal()),
2787 else
2788 x /= 2; // Centered
2789 p->setFont(f);
2790 p->drawText(QPointF(iterator.x.toReal() + x,
2791 y.toReal()), visualTab);
2792 }
2793
2794 }
2795 p->restore();
2796 }
2797
2798 continue;
2799 }
2800
2801 unsigned short *logClusters = eng->logClusters(&si);
2802 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2803
2804 QTextItemInt gf(glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart),
2805 &f, eng->layoutData->string.unicode() + iterator.itemStart,
2806 iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format);
2807 gf.logClusters = logClusters + iterator.itemStart - si.position;
2808 gf.width = iterator.itemWidth;
2809 gf.justified = line.justified;
2810 gf.initWithScriptItem(si);
2811
2812 Q_ASSERT(gf.fontEngine);
2813
2814 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2815 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2817 path.setFillRule(Qt::WindingFill);
2818
2819 if (gf.glyphs.numGlyphs)
2820 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2821 if (gf.flags) {
2822 const QFontEngine *fe = gf.fontEngine;
2823 const qreal lw = fe->lineThickness().toReal();
2824 if (gf.flags & QTextItem::Underline) {
2825 qreal offs = fe->underlinePosition().toReal();
2826 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2827 }
2828 if (gf.flags & QTextItem::Overline) {
2829 qreal offs = fe->ascent().toReal() + 1;
2830 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2831 }
2832 if (gf.flags & QTextItem::StrikeOut) {
2833 qreal offs = fe->ascent().toReal() / 3;
2834 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2835 }
2836 }
2837
2838 p->save();
2839 p->setRenderHint(QPainter::Antialiasing);
2840 //Currently QPen with a Qt::NoPen style still returns a default
2841 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2842 if (p->pen().style() == Qt::NoPen)
2843 p->setBrush(Qt::NoBrush);
2844 else
2845 p->setBrush(p->pen().brush());
2846
2847 p->setPen(format.textOutline());
2848 p->drawPath(path);
2849 p->restore();
2850 } else {
2851 if (noText)
2852 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2853 QPainterPrivate::get(p)->drawTextItem(pos, gf, eng);
2854 }
2855
2858 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2859 QBrush c = format.foreground();
2860 if (c.style() != Qt::NoBrush)
2861 p->setPen(c.color());
2862 const QChar visualSpace = si.analysis.flags == QScriptAnalysis::Space ? u'\xb7' : u'\xb0';
2863 QFont oldFont = p->font();
2864 p->setFont(eng->font(si));
2865 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2866 p->setPen(pen);
2867 p->setFont(oldFont);
2868 }
2869 }
2870 }
2871 eng->drawDecorations(p);
2872
2873 if (xlateToFixedRange)
2874 p->translate(-origPos);
2875
2876 if (eng->hasFormats())
2877 p->setPen(pen);
2878}
2879
2896qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2897{
2898 const QScriptLine &line = eng->lines[index];
2899 bool lastLine = index >= eng->lines.size() - 1;
2900
2901 QFixed x = line.x + eng->alignLine(line) - eng->leadingSpaceWidth(line);
2902
2903 if (!eng->layoutData)
2904 eng->itemize();
2905 if (!eng->layoutData->items.size()) {
2906 *cursorPos = line.from;
2907 return x.toReal();
2908 }
2909
2910 int lineEnd = line.from + line.length + line.trailingSpaces;
2911 int pos = qBound(line.from, *cursorPos, lineEnd);
2912 const QCharAttributes *attributes = eng->attributes();
2913 if (!attributes) {
2914 *cursorPos = line.from;
2915 return x.toReal();
2916 }
2917 while (pos < lineEnd && !attributes[pos].graphemeBoundary)
2918 pos++;
2919 // end of line ensure we have the last item on the line
2920 int itm = pos == lineEnd ? eng->findItem(pos-1) : eng->findItem(pos);
2921 if (itm < 0) {
2922 *cursorPos = line.from;
2923 return x.toReal();
2924 }
2925 eng->shapeLine(line);
2926
2927 const QScriptItem *scriptItem = &eng->layoutData->items[itm];
2928 if (!scriptItem->num_glyphs)
2929 eng->shape(itm);
2930
2931 if ((scriptItem->analysis.bidiLevel % 2 != eng->isRightToLeft()) && !eng->visualCursorMovement()) {
2932 // If the item we found has a different writing direction than the engine,
2933 // check if the cursor is between two items with different writing direction
2934 int neighborItem = itm;
2935 if (neighborItem > 0 && scriptItem->position == pos)
2936 --neighborItem;
2937 else if (neighborItem < eng->layoutData->items.size() - 1 && scriptItem->position + scriptItem->num_glyphs == pos)
2938 ++neighborItem;
2939 const bool onBoundary = neighborItem != itm && scriptItem->analysis.bidiLevel != eng->layoutData->items[neighborItem].analysis.bidiLevel;
2940 // If we are, prioritise the neighbor item that has the same direction as the engine
2941 if (onBoundary) {
2942 if (eng->isRightToLeft() != scriptItem->analysis.bidiLevel % 2) {
2943 itm = neighborItem;
2944 scriptItem = &eng->layoutData->items[itm];
2945 if (!scriptItem->num_glyphs)
2946 eng->shape(itm);
2947 }
2948 }
2949 }
2950
2951 const int l = eng->length(itm);
2952 pos = qBound(0, pos - scriptItem->position, l);
2953
2954 QGlyphLayout glyphs = eng->shapedGlyphs(scriptItem);
2955 unsigned short *logClusters = eng->logClusters(scriptItem);
2956 Q_ASSERT(logClusters);
2957
2958 int glyph_pos = pos == l ? scriptItem->num_glyphs : logClusters[pos];
2959 if (edge == Trailing && glyph_pos < scriptItem->num_glyphs) {
2960 // trailing edge is leading edge of next cluster
2961 glyph_pos++;
2962 while (glyph_pos < scriptItem->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2963 glyph_pos++;
2964 }
2965
2966 bool reverse = scriptItem->analysis.bidiLevel % 2;
2967
2968
2969 // add the items left of the cursor
2970
2971 int firstItem = eng->findItem(line.from);
2972 int lastItem = eng->findItem(lineEnd - 1, itm);
2973 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2974
2975 QVarLengthArray<int> visualOrder(nItems);
2976 QVarLengthArray<uchar> levels(nItems);
2977 for (int i = 0; i < nItems; ++i)
2978 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2979 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2980
2981 for (int i = 0; i < nItems; ++i) {
2982 int item = visualOrder[i]+firstItem;
2983 if (item == itm)
2984 break;
2985 QScriptItem &si = eng->layoutData->items[item];
2986 if (!si.num_glyphs)
2987 eng->shape(item);
2988
2990 x += si.width;
2991 continue;
2992 }
2993
2994 const int itemLength = eng->length(item);
2995 int start = qMax(line.from, si.position);
2996 int end = qMin(lineEnd, si.position + itemLength);
2997
2998 logClusters = eng->logClusters(&si);
2999
3000 int gs = logClusters[start-si.position];
3001 int ge = (end == si.position + itemLength) ? si.num_glyphs-1 : logClusters[end-si.position-1];
3002
3003 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
3004
3005 while (gs <= ge) {
3006 x += glyphs.effectiveAdvance(gs);
3007 ++gs;
3008 }
3009 }
3010
3011 logClusters = eng->logClusters(scriptItem);
3012 glyphs = eng->shapedGlyphs(scriptItem);
3013 if (scriptItem->analysis.flags >= QScriptAnalysis::TabOrObject) {
3014 if (pos == (reverse ? 0 : l))
3015 x += scriptItem->width;
3016 } else {
3017 bool rtl = eng->isRightToLeft();
3018 bool visual = eng->visualCursorMovement();
3019 int end = qMin(lineEnd, scriptItem->position + l) - scriptItem->position;
3020 if (reverse) {
3021 int glyph_end = end == l ? scriptItem->num_glyphs : logClusters[end];
3022 int glyph_start = glyph_pos;
3023 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
3024 glyph_start++;
3025 for (int i = glyph_end - 1; i >= glyph_start; i--)
3026 x += glyphs.effectiveAdvance(i);
3027 x -= eng->offsetInLigature(scriptItem, pos, end, glyph_pos);
3028 } else {
3029 int start = qMax(line.from - scriptItem->position, 0);
3030 int glyph_start = logClusters[start];
3031 int glyph_end = glyph_pos;
3032 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
3033 glyph_end--;
3034 for (int i = glyph_start; i <= glyph_end; i++)
3035 x += glyphs.effectiveAdvance(i);
3036 x += eng->offsetInLigature(scriptItem, pos, end, glyph_pos);
3037 }
3038 }
3039
3040 if (eng->option.wrapMode() != QTextOption::NoWrap && x > line.x + line.width)
3041 x = line.x + line.width;
3042 if (eng->option.wrapMode() != QTextOption::NoWrap && x < 0)
3043 x = 0;
3044
3045 *cursorPos = pos + scriptItem->position;
3046 return x.toReal();
3047}
3048
3059{
3061 const QScriptLine &line = eng->lines[index];
3062 bool lastLine = index >= eng->lines.size() - 1;
3063 int lineNum = index;
3064
3065 if (!eng->layoutData)
3066 eng->itemize();
3067
3068 int line_length = textLength();
3069
3070 if (!line_length)
3071 return line.from;
3072
3073 int firstItem = eng->findItem(line.from);
3074 int lastItem = eng->findItem(line.from + line_length - 1, firstItem);
3075 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
3076
3077 if (!nItems)
3078 return 0;
3079
3080 x -= line.x;
3081 x -= eng->alignLine(line);
3082// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
3083
3084 QVarLengthArray<int> visualOrder(nItems);
3085 QVarLengthArray<unsigned char> levels(nItems);
3086 for (int i = 0; i < nItems; ++i)
3087 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
3088 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
3089
3090 bool visual = eng->visualCursorMovement();
3091 if (x <= 0) {
3092 // left of first item
3093 if (eng->isRightToLeft())
3094 return line.from + line_length;
3095 return line.from;
3096 } else if (x < line.textWidth || (line.justified && x < line.width)) {
3097 // has to be in one of the runs
3098 QFixed pos;
3099 bool rtl = eng->isRightToLeft();
3100
3101 eng->shapeLine(line);
3102 const auto insertionPoints = (visual && rtl) ? eng->insertionPointsForLine(lineNum) : std::vector<int>();
3103 int nchars = 0;
3104 for (int i = 0; i < nItems; ++i) {
3105 int item = visualOrder[i]+firstItem;
3106 QScriptItem &si = eng->layoutData->items[item];
3107 int item_length = eng->length(item);
3108// qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
3109
3110 int start = qMax(line.from - si.position, 0);
3111 int end = qMin(line.from + line_length - si.position, item_length);
3112
3113 unsigned short *logClusters = eng->logClusters(&si);
3114
3115 int gs = logClusters[start];
3116 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
3117 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
3118
3119 QFixed item_width = 0;
3121 item_width = si.width;
3122 } else {
3123 int g = gs;
3124 while (g <= ge) {
3125 item_width += glyphs.effectiveAdvance(g);
3126 ++g;
3127 }
3128 }
3129// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
3130
3131 if (pos + item_width < x) {
3132 pos += item_width;
3133 nchars += end;
3134 continue;
3135 }
3136// qDebug(" inside run");
3138 if (cpos == QTextLine::CursorOnCharacter)
3139 return si.position;
3140 bool left_half = (x - pos) < item_width/2;
3141
3142 if (bool(si.analysis.bidiLevel % 2) != left_half)
3143 return si.position;
3144 return si.position + 1;
3145 }
3146
3147 int glyph_pos = -1;
3148 QFixed edge;
3149 // has to be inside run
3150 if (cpos == QTextLine::CursorOnCharacter) {
3151 if (si.analysis.bidiLevel % 2) {
3152 pos += item_width;
3153 glyph_pos = gs;
3154 while (gs <= ge) {
3155 if (glyphs.attributes[gs].clusterStart) {
3156 if (pos < x)
3157 break;
3158 glyph_pos = gs;
3159 edge = pos;
3160 }
3161 pos -= glyphs.effectiveAdvance(gs);
3162 ++gs;
3163 }
3164 } else {
3165 glyph_pos = gs;
3166 while (gs <= ge) {
3167 if (glyphs.attributes[gs].clusterStart) {
3168 if (pos > x)
3169 break;
3170 glyph_pos = gs;
3171 edge = pos;
3172 }
3173 pos += glyphs.effectiveAdvance(gs);
3174 ++gs;
3175 }
3176 }
3177 } else {
3178 QFixed dist = INT_MAX/256;
3179 if (si.analysis.bidiLevel % 2) {
3180 if (!visual || rtl || (lastLine && i == nItems - 1)) {
3181 pos += item_width;
3182 while (gs <= ge) {
3183 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3184 glyph_pos = gs;
3185 edge = pos;
3186 dist = qAbs(x-pos);
3187 }
3188 pos -= glyphs.effectiveAdvance(gs);
3189 ++gs;
3190 }
3191 } else {
3192 while (ge >= gs) {
3193 if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
3194 glyph_pos = ge;
3195 edge = pos;
3196 dist = qAbs(x-pos);
3197 }
3198 pos += glyphs.effectiveAdvance(ge);
3199 --ge;
3200 }
3201 }
3202 } else {
3203 if (!visual || !rtl || (lastLine && i == 0)) {
3204 while (gs <= ge) {
3205 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3206 glyph_pos = gs;
3207 edge = pos;
3208 dist = qAbs(x-pos);
3209 }
3210 pos += glyphs.effectiveAdvance(gs);
3211 ++gs;
3212 }
3213 } else {
3214 QFixed oldPos = pos;
3215 while (gs <= ge) {
3216 pos += glyphs.effectiveAdvance(gs);
3217 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3218 glyph_pos = gs;
3219 edge = pos;
3220 dist = qAbs(x-pos);
3221 }
3222 ++gs;
3223 }
3224 pos = oldPos;
3225 }
3226 }
3227 if (qAbs(x-pos) < dist) {
3228 if (visual) {
3229 if (!rtl && i < nItems - 1) {
3230 nchars += end;
3231 continue;
3232 }
3233 if (rtl && nchars > 0)
3234 return insertionPoints[size_t(lastLine ? nchars : nchars - 1)];
3235 }
3236 return eng->positionInLigature(&si, end, x, pos, -1,
3238 }
3239 }
3240 Q_ASSERT(glyph_pos != -1);
3241 return eng->positionInLigature(&si, end, x, edge, glyph_pos,
3243 }
3244 }
3245 // right of last item
3246 int pos = line.from;
3247 if (!eng->isRightToLeft())
3248 pos += line_length;
3249
3250 // except for the last line we assume that the
3251 // character between lines is a space and we want
3252 // to position the cursor to the left of that
3253 // character.
3254 if (index < eng->lines.size() - 1)
3255 pos = qMin(eng->previousLogicalPosition(pos), pos);
3256
3257 return pos;
3258}
3259
\inmodule QtGui
Definition qbrush.h:30
const QColor & color() const
Returns the brush color.
Definition qbrush.h:121
Qt::BrushStyle style() const
Returns the brush style.
Definition qbrush.h:120
\inmodule QtCore
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
void setAlpha(int alpha)
Sets the alpha of this color to alpha.
Definition qcolor.cpp:1481
virtual qreal minRightBearing() const
void getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, QVarLengthArray< glyph_t > &glyphs_out, QVarLengthArray< QFixedPoint > &positions)
virtual QFixed lineThickness() const
\reentrant \inmodule QtGui
int horizontalAdvance(const QString &, int len=-1) const
Returns the horizontal advance in pixels of the first len characters of text.
QFontEngine * engineForScript(int script) const
Definition qfont.cpp:241
\reentrant
Definition qfont.h:22
bool strikeOut() const
Returns true if strikeout has been set; otherwise returns false.
Definition qfont.cpp:1304
bool underline() const
Returns true if underline has been set; otherwise returns false.
Definition qfont.cpp:1251
bool overline() const
Returns true if overline has been set; otherwise returns false.
Definition qfont.cpp:1278
static QGlyphRunPrivate * get(const QGlyphRun &glyphRun)
Definition qglyphrun_p.h:78
The QGlyphRun class provides direct access to the internal glyphs in a font.
Definition qglyphrun.h:20
void setSourceString(const QString &sourceString)
void setStringIndexes(const QList< qsizetype > &stringIndexes)
void setPositions(const QList< QPointF > &positions)
Sets the positions of the edge of the baseline for each glyph in this set of glyph indexes to positio...
void setRawFont(const QRawFont &rawFont)
Sets the font in which to look up the glyph indexes to the rawFont specified.
QList< quint32 > glyphIndexes() const
Returns the glyph indexes for this QGlyphRun object.
void setBoundingRect(const QRectF &boundingRect)
Sets the bounding rect of the glyphs in this QGlyphRun to be boundingRect.
@ RightToLeft
Definition qglyphrun.h:26
@ SplitLigature
Definition qglyphrun.h:27
void setFlags(GlyphRunFlags flags)
Sets the flags of this QGlyphRun to flags.
void setGlyphIndexes(const QList< quint32 > &glyphIndexes)
Set the glyph indexes for this QGlyphRun object to glyphIndexes.
bool isEmpty() const
Returns true if the QGlyphRun does not contain any glyphs.
\inmodule QtCore\compares equality \compareswith equality QLine \endcompareswith
Definition qline.h:192
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
qsizetype length() const noexcept
Definition qlist.h:399
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void append(parameter_type t)
Definition qlist.h:458
\inmodule QtGui
void addRect(const QRectF &rect)
Adds the given rectangle to this path as a closed subpath.
void setFillRule(Qt::FillRule fillRule)
Sets the fill rule of the painter path to the given fillRule.
static QPainterPrivate * get(QPainter *painter)
Definition qpainter_p.h:217
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
@ Antialiasing
Definition qpainter.h:52
CompositionMode
Defines the modes supported for digital image compositing.
Definition qpainter.h:97
@ RasterOp_NotDestination
Definition qpainter.h:139
\inmodule QtGui
Definition qpen.h:28
void setCosmetic(bool cosmetic)
Sets this pen to cosmetic or non-cosmetic, depending on the value of cosmetic.
Definition qpen.cpp:770
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
static QRawFontPrivate * get(const QRawFont &font)
Definition qrawfont_p.h:104
QFontEngine * fontEngine
Definition qrawfont_p.h:106
The QRawFont class provides access to a single physical instance of a font.
Definition qrawfont.h:24
\inmodule QtCore\reentrant
Definition qrect.h:484
constexpr void setRight(qreal pos) noexcept
Sets the right edge of the rectangle to the given finite x coordinate.
Definition qrect.h:678
constexpr qreal y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:672
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:732
constexpr QRectF translated(qreal dx, qreal dy) const noexcept
Returns a copy of the rectangle that is translated dx along the x axis and dy along the y axis,...
Definition qrect.h:762
constexpr bool isNull() const noexcept
Returns true if the rectangle is a null rectangle, otherwise returns false.
Definition qrect.h:658
constexpr bool isValid() const noexcept
Returns true if the rectangle is valid, otherwise returns false.
Definition qrect.h:666
QRectF intersected(const QRectF &other) const noexcept
Definition qrect.h:847
QRectF united(const QRectF &other) const noexcept
Definition qrect.h:852
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
bool isNull() const
Returns true if this string is null; otherwise returns false.
Definition qstring.h:994
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
static QString static QString qsizetype from
Definition qstring.h:291
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
qsizetype length() const noexcept
Returns the number of characters in this string.
Definition qstring.h:191
\reentrant
int length() const
Returns the length of the block in characters.
bool isValid() const
Returns true if this text block is valid; otherwise returns false.
int position() const
Returns the index of the block's first character within the document.
VerticalAlignment
This enum describes the ways that adjacent characters can be vertically aligned.
VerticalAlignment verticalAlignment() const
Returns the vertical alignment used for characters with this format.
static const QTextDocumentPrivate * get(const QTextDocument *document)
QList< QTextLayout::FormatRange > formats() const
void setPreeditArea(int position, const QString &text)
int lineNumberForTextPosition(int pos)
QTextOption option
bool isRightToLeft() const
QScriptLineArray lines
LayoutData * layoutData
int preeditAreaPosition() const
int findItem(int strPos, int firstItem=0) const
uint forceJustification
void resetFontEngineCache()
const QCharAttributes * attributes() const
QTextBlock block
QFixed leadingSpaceWidth(const QScriptLine &line)
QFixed alignLine(const QScriptLine &line)
QString preeditAreaText() const
int positionAfterVisualMovement(int oldPos, QTextCursor::MoveOperation op)
QRawFont rawFont
int formatIndex(const QScriptItem *si) const
bool visualCursorMovement() const
bool atWordSeparator(int position) const
void clearLineData()
void setFormats(const QList< QTextLayout::FormatRange > &formats)
void itemize() const
QTextCharFormat format(const QScriptItem *si) const
QPointF position
static void bidiReorder(int numRuns, const quint8 *levels, int *visualOrder)
QFont font(const QScriptItem &si) const
QTextCharFormat defaultTextFormat() const
\reentrant
Definition qtextformat.h:90
QTextCharFormat toCharFormat() const
Returns this format as a character format.
QTextFormat format() const
Returns format of the inline object within the text layout.
void setDescent(qreal d)
Sets the inline object's descent to d.
void setAscent(qreal a)
Sets the inline object's ascent to a.
qreal ascent() const
Returns the inline object's ascent.
qreal width() const
Returns the inline object's width.
qreal height() const
Returns the inline object's total height.
int formatIndex() const
Returns an integer describing the format of the inline object within the text layout.
QRectF rect() const
Returns the inline object's rectangle.
void setWidth(qreal w)
Sets the inline object's width to w.
Qt::LayoutDirection textDirection() const
Returns if the object should be laid out right-to-left or left-to-right.
qreal descent() const
Returns the inline object's descent.
int textPosition() const
The position of the inline object within the text layout.
Internal QTextItem.
const QTextOption & textOption() const
Returns the current text option used to control the layout process.
int rightCursorPosition(int oldPos) const
Returns the cursor position to the right of oldPos, next to it.
QTextLine lineForTextPosition(int pos) const
Returns the line that contains the cursor position specified by pos.
void setFont(const QFont &f)
Sets the layout's font to the given font.
QTextLine createLine()
Returns a new text line to be laid out if there is text to be inserted into the layout; otherwise ret...
void beginLayout()
Begins the layout process.
qreal minimumWidth() const
The minimum width the layout needs.
void setPosition(const QPointF &p)
Moves the text layout to point p.
void setCacheEnabled(bool enable)
Enables caching of the complete layout information if enable is true; otherwise disables layout cachi...
void setFormats(const QList< FormatRange > &overrides)
void setText(const QString &string)
Sets the layout's text to the given string.
QList< QGlyphRun > glyphRuns(int from=-1, int length=-1, GlyphRunRetrievalFlags flags=DefaultRetrievalFlags) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
QList< FormatRange > formats() const
QString text() const
Returns the layout's text.
QTextLayout()
Constructs an empty text layout.
void setRawFont(const QRawFont &rawFont)
void clearFormats()
int previousCursorPosition(int oldPos, CursorMode mode=SkipCharacters) const
Returns the first valid cursor position before oldPos that respects the given cursor mode.
int lineCount() const
Returns the number of lines in this text layout.
int preeditAreaPosition() const
Returns the position of the area in the text layout that will be processed before editing occurs.
qreal maximumWidth() const
The maximum width the layout could expand to; this is essentially the width of the entire text.
QTextLine lineAt(int i) const
Returns the {i}-th line of text in this text layout.
int nextCursorPosition(int oldPos, CursorMode mode=SkipCharacters) const
Returns the next valid cursor position after oldPos that respects the given cursor mode.
void setFlags(int flags)
void setTextOption(const QTextOption &option)
Sets the text option structure that controls the layout process to the given option.
bool isValidCursorPosition(int pos) const
Returns true if position pos is a valid cursor position.
bool cacheEnabled() const
Returns true if the complete layout information is cached; otherwise returns false.
void draw(QPainter *p, const QPointF &pos, const QList< FormatRange > &selections=QList< FormatRange >(), const QRectF &clip=QRectF()) const
Draws the whole layout on the painter p at the position specified by pos.
Qt::CursorMoveStyle cursorMoveStyle() const
The cursor movement style of this QTextLayout.
QFont font() const
Returns the current font that is used for the layout, or a default font if none is set.
~QTextLayout()
Destructs the layout.
void endLayout()
Ends the layout process.
QString preeditAreaText() const
Returns the text that is inserted in the layout before editing occurs.
void clearLayout()
@ RetrieveGlyphIndexes
Definition qtextlayout.h:73
@ RetrieveStringIndexes
Definition qtextlayout.h:75
@ RetrieveGlyphPositions
Definition qtextlayout.h:74
@ DefaultRetrievalFlags
Definition qtextlayout.h:78
void setPreeditArea(int position, const QString &text)
Sets the position and text of the area in the layout that is processed before editing occurs.
void drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setCursorMoveStyle(Qt::CursorMoveStyle style)
Sets the visual cursor movement style to the given style.
QRectF boundingRect() const
The smallest rectangle that contains all the lines in the layout.
CursorMode
\value SkipCharacters \value SkipWords
QPointF position() const
int leftCursorPosition(int oldPos) const
Returns the cursor position to the left of oldPos, next to it.
\reentrant
qreal descent() const
Returns the line's descent.
int textStart() const
Returns the start of the line from the beginning of the string passed to the QTextLayout.
void setNumColumns(int columns)
Lays out the line.
QList< QGlyphRun > glyphRuns(int from=-1, int length=-1, QTextLayout::GlyphRunRetrievalFlags flags=QTextLayout::DefaultRetrievalFlags) const
Returns the glyph indexes and positions for all glyphs in this QTextLine for characters in the range ...
QRectF naturalTextRect() const
Returns the rectangle covered by the line.
QRectF rect() const
Returns the line's bounding rectangle.
qreal height() const
Returns the line's height.
void draw(QPainter *painter, const QPointF &position) const
Draws a line on the given painter at the specified position.
bool leadingIncluded() const
qreal cursorToX(int *cursorPos, Edge edge=Leading) const
Converts the cursor position cursorPos to the corresponding x position inside the line,...
qreal naturalTextWidth() const
Returns the width of the line that is occupied by text.
void setLeadingIncluded(bool included)
qreal y() const
Returns the line's y position.
qreal leading() const
qreal width() const
Returns the line's width as specified by the layout() function.
void setPosition(const QPointF &pos)
Moves the line to position pos.
Edge
\value Leading \value Trailing
CursorPosition
\value CursorBetweenCharacters \value CursorOnCharacter
@ CursorOnCharacter
void setLineWidth(qreal width)
Lays out the line with the given width.
qreal ascent() const
Returns the line's ascent.
int xToCursor(qreal x, CursorPosition=CursorBetweenCharacters) const
Converts the x-coordinate x, to the nearest matching cursor position, depending on the cursor positio...
qreal horizontalAdvance() const
qreal x() const
Returns the line's x position.
int textLength() const
Returns the length of the text in the line.
QPointF position() const
Returns the line's position relative to the text layout's position.
\reentrant
Definition qtextoption.h:18
void setTextDirection(Qt::LayoutDirection aDirection)
Sets the direction of the text layout defined by the option to the given direction.
Definition qtextoption.h:57
void setAlignment(Qt::Alignment alignment)
Sets the option's text alignment to the specified alignment.
Flags flags() const
Returns the flags associated with the option.
Definition qtextoption.h:80
@ ShowLineAndParagraphSeparators
Definition qtextoption.h:72
@ IncludeTrailingSpaces
Definition qtextoption.h:76
WrapMode
This enum describes how text is wrapped in a document.
Definition qtextoption.h:60
@ WrapAtWordBoundaryOrAnywhere
Definition qtextoption.h:65
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
qreal m11() const
Returns the horizontal scaling factor.
Definition qtransform.h:199
QString text
rect
[4]
uint alignment
EGLint EGLint * formats
typename C::iterator iterator
Combined button and popup list for selecting options.
@ AlignRight
Definition qnamespace.h:146
@ AlignJustify
Definition qnamespace.h:149
@ AlignCenter
Definition qnamespace.h:163
@ AlignLeft
Definition qnamespace.h:144
@ IntersectClip
LayoutDirection
@ LeftToRight
@ RightToLeft
@ TextJustificationForced
Definition qnamespace.h:179
@ TextForceRightToLeft
Definition qnamespace.h:181
@ TextForceLeftToRight
Definition qnamespace.h:180
@ SolidLine
@ NoPen
@ NoBrush
CursorMoveStyle
@ VisualMoveStyle
@ LogicalMoveStyle
@ WindingFill
@ FlatCap
#define Q_LIKELY(x)
static const QCssKnownValue positions[NumKnownPositionModes - 1]
#define QFIXED_MAX
Definition qfixed_p.h:127
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
#define qWarning
Definition qlogging.h:166
int qCeil(T v)
Definition qmath.h:36
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
std::enable_if_t< std::is_unsigned_v< T >, bool > qAddOverflow(T v1, T v2, T *r)
Definition qnumeric.h:113
GLint GLint GLint GLint GLint x
[0]
GLenum mode
GLuint64 key
GLfloat GLfloat GLfloat w
[0]
GLint GLsizei GLsizei height
GLfloat GLfloat GLfloat GLfloat GLfloat maxY
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint index
[2]
GLboolean r
[2]
GLuint GLuint end
GLenum GLuint GLenum GLsizei length
GLfloat minY
GLfloat GLfloat f
GLsizei levels
GLint GLsizei width
GLbitfield flags
GLboolean enable
GLuint start
GLboolean GLboolean g
GLint GLsizei GLsizei GLenum format
GLint y
const GLubyte * c
GLsizei const GLchar *const * path
GLfloat GLfloat p
[1]
GLuint GLenum option
GLenum GLsizei len
GLsizei const GLchar *const * string
[0]
Definition qopenglext.h:694
static const QRectF boundingRect(const QPointF *points, int pointCount)
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
int baseLineOffset(QQuickContext2D::TextBaseLineType value, const QFontMetrics &metrics)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection, QPainterPath *region, const QRectF &boundingRect)
static void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount, const QScriptItem &current, const unsigned short *logClusters, const QGlyphLayout &glyphs, QFixed *clusterWidth=nullptr)
#define SuppressText
static QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
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, int glyphsEnd, unsigned short *logClusters, int textPosition, int textLength)
static void drawBackground(QPainter *p, const QTextCharFormat &chf, const QRectF &r)
#define SuppressBackground
#define LB_DEBUG
static void setPen(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf)
#define ObjectSelectionBrush
unsigned int glyph_t
double qreal
Definition qtypes.h:187
static const uint base
Definition qurlidna.cpp:20
static int sign(int x)
std::uniform_real_distribution dist(1, 2.5)
[2]
QGraphicsItem * item
rect deviceTransform(view->viewportTransform()).map(QPointF(0
QItemSelection * selection
[0]
QPainter painter(this)
[7]
QJSEngine engine
[0]
static constexpr QFixed fromReal(qreal r)
Definition qfixed_p.h:35
static constexpr QFixed fromFixed(int fixed)
Definition qfixed_p.h:36
constexpr QFixed ceil() const
Definition qfixed_p.h:47
constexpr qreal toReal() const
Definition qfixed_p.h:42
QGlyphJustification * justifications
QFixed effectiveAdvance(int item) const
QGlyphAttributes * attributes
glyph_t * glyphs
QGlyphLayout mid(int position, int n=-1) const
QFixed * advances
unsigned short flags
unsigned short bidiLevel
unsigned short script
QScriptAnalysis analysis
constexpr QFixed height() const noexcept
unsigned short num_glyphs
QFixed height() const
signed int length
QFixed textAdvance
unsigned short trailingSpaces
QScriptItemArray items