Qt 6.x
The Qt SDK
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
321{ d = new QTextEngine(); }
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
443{
444 d->option = option;
445}
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)
472}
473
481{
482 return d->preeditAreaPosition();
483}
484
491{
492 return d->preeditAreaText();
493}
494
504{
506
507 if (QTextDocumentPrivate::get(d->block) != nullptr)
509}
510
519{
520 return d->formats();
521}
522
531{
533}
534
546{
547 d->cacheGlyphs = enable;
548}
549
557{
558 return d->cacheGlyphs;
559}
560
570{
572}
573
581{
583}
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
930{
933 d->forceJustification = true;
934 }
935
937 d->ignoreBidi = true;
939 }
940}
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)
1010{
1012}
1013# endif
1014#endif
1015
1032#if !defined(QT_NO_RAWFONT)
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
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[i].length >= from) {
1047 QList<QGlyphRun> glyphRuns = QTextLine(i, d).glyphRuns(from, length, retrievalFlags);
1048
1049 for (int j = 0; j < glyphRuns.size(); j++) {
1050 const QGlyphRun &glyphRun = glyphRuns.at(j);
1051
1052 QRawFont rawFont = glyphRun.rawFont();
1053
1054 QFontEngine *fontEngine = rawFont.d->fontEngine;
1055 QGlyphRun::GlyphRunFlags flags = glyphRun.flags();
1056 QPair<QFontEngine *, int> key(fontEngine, int(flags));
1057 // merge the glyph runs using the same font
1058 QGlyphRun &oldGlyphRun = glyphRunHash[key];
1059 if (oldGlyphRun.isEmpty()) {
1060 oldGlyphRun = glyphRun;
1061 } else {
1062 QList<quint32> indexes = oldGlyphRun.glyphIndexes();
1063 QList<QPointF> positions = oldGlyphRun.positions();
1064 QList<qsizetype> stringIndexes = oldGlyphRun.stringIndexes();
1065 QRectF boundingRect = oldGlyphRun.boundingRect();
1066
1067 indexes += glyphRun.glyphIndexes();
1068 positions += glyphRun.positions();
1069 stringIndexes += glyphRun.stringIndexes();
1071
1072 oldGlyphRun.setGlyphIndexes(indexes);
1073 oldGlyphRun.setPositions(positions);
1074 oldGlyphRun.setStringIndexes(stringIndexes);
1075 oldGlyphRun.setBoundingRect(boundingRect);
1076 }
1077 }
1078 }
1079 }
1080
1081 return glyphRunHash.values();
1082}
1083#endif // QT_NO_RAWFONT
1084
1090void QTextLayout::draw(QPainter *p, const QPointF &pos, const QList<FormatRange> &selections, const QRectF &clip) const
1091{
1092 if (d->lines.isEmpty())
1093 return;
1094
1095 if (!d->layoutData)
1096 d->itemize();
1097
1099
1100 QFixed clipy = (INT_MIN/256);
1101 QFixed clipe = (INT_MAX/256);
1102 if (clip.isValid()) {
1103 clipy = QFixed::fromReal(clip.y() - position.y());
1104 clipe = clipy + QFixed::fromReal(clip.height());
1105 }
1106
1107 int firstLine = 0;
1108 int lastLine = d->lines.size();
1109 for (int i = 0; i < d->lines.size(); ++i) {
1110 QTextLine l(i, d);
1111 const QScriptLine &sl = d->lines.at(i);
1112
1113 if (sl.y > clipe) {
1114 lastLine = i;
1115 break;
1116 }
1117 if ((sl.y + sl.height()) < clipy) {
1118 firstLine = i;
1119 continue;
1120 }
1121 }
1122
1123 QPainterPath excludedRegion;
1124 QPainterPath textDoneRegion;
1125 for (int i = 0; i < selections.size(); ++i) {
1126 FormatRange selection = selections.at(i);
1127 QPainterPath region;
1129
1130 for (int line = firstLine; line < lastLine; ++line) {
1131 const QScriptLine &sl = d->lines.at(line);
1132 QTextLine tl(line, d);
1133
1134 QRectF lineRect(tl.naturalTextRect());
1135 lineRect.translate(position);
1136 lineRect.adjust(0, 0, d->leadingSpaceWidth(sl).toReal(), 0);
1137 lineRect.setBottom(qCeil(lineRect.bottom()));
1138
1139 bool isLastLineInBlock = (line == d->lines.size()-1);
1140 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1141
1142
1143 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1144 continue; // no actual intersection
1145
1146 const bool selectionStartInLine = sl.from <= selection.start;
1147 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1148
1149 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1150 addSelectedRegionsToPath(d, line, position, &selection, &region, clipIfValid(lineRect, clip));
1151 } else {
1152 region.addRect(clipIfValid(lineRect, clip));
1153 }
1154
1155 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1156 QRectF fullLineRect(tl.rect());
1157 fullLineRect.translate(position);
1158 fullLineRect.setRight(QFIXED_MAX);
1159 fullLineRect.setBottom(qCeil(fullLineRect.bottom()));
1160
1161 const bool rightToLeft = d->isRightToLeft();
1162
1163 if (!selectionEndInLine) {
1164 region.addRect(clipIfValid(rightToLeft ? QRectF(fullLineRect.topLeft(), lineRect.bottomLeft())
1165 : QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1166 }
1167 if (!selectionStartInLine) {
1168 region.addRect(clipIfValid(rightToLeft ? QRectF(lineRect.topRight(), fullLineRect.bottomRight())
1169 : QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1170 }
1171 } else if (!selectionEndInLine
1172 && isLastLineInBlock
1174 region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1175 lineRect.height()/4, lineRect.height()), clip));
1176 }
1177
1178 }
1179 {
1180 const QPen oldPen = p->pen();
1181 const QBrush oldBrush = p->brush();
1182
1183 p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1184 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1185 p->drawPath(region);
1186
1187 p->setPen(oldPen);
1188 p->setBrush(oldBrush);
1189 }
1190
1191
1192
1193 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1194 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1195
1196 if (hasBackground) {
1197 selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1198 // don't just clear the property, set an empty brush that overrides a potential
1199 // background brush specified in the text
1200 selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1201 selection.format.clearProperty(QTextFormat::OutlinePen);
1202 }
1203
1204 selection.format.setProperty(SuppressText, !hasText);
1205
1206 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1207 continue;
1208
1209 p->save();
1210 p->setClipPath(region, Qt::IntersectClip);
1211
1212 for (int line = firstLine; line < lastLine; ++line) {
1213 QTextLine l(line, d);
1214 l.draw_internal(p, position, &selection);
1215 }
1216 p->restore();
1217
1218 if (hasText) {
1219 textDoneRegion += region;
1220 } else {
1221 if (hasBackground)
1222 textDoneRegion -= region;
1223 }
1224
1225 excludedRegion += region;
1226 }
1227
1228 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1229 if (!needsTextButNoBackground.isEmpty()){
1230 p->save();
1231 p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1233 selection.start = 0;
1234 selection.length = INT_MAX;
1235 selection.format.setProperty(SuppressBackground, true);
1236 for (int line = firstLine; line < lastLine; ++line) {
1237 QTextLine l(line, d);
1238 l.draw_internal(p, position, &selection);
1239 }
1240 p->restore();
1241 }
1242
1243 if (!excludedRegion.isEmpty()) {
1244 p->save();
1247 br.setRight(QFIXED_MAX);
1248 if (!clip.isNull())
1249 br = br.intersected(clip);
1250 path.addRect(br);
1251 path -= excludedRegion;
1252 p->setClipPath(path, Qt::IntersectClip);
1253 }
1254
1255 for (int i = firstLine; i < lastLine; ++i) {
1256 QTextLine l(i, d);
1257 l.draw(p, position);
1258 }
1259 if (!excludedRegion.isEmpty())
1260 p->restore();
1261
1262
1263 if (!d->cacheGlyphs)
1264 d->freeMemory();
1265}
1266
1275void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1276{
1277 drawCursor(p, pos, cursorPosition, 1);
1278}
1279
1287void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1288{
1289 if (d->lines.isEmpty())
1290 return;
1291
1292 if (!d->layoutData)
1293 d->itemize();
1294
1296
1297 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.size());
1298 int line = d->lineNumberForTextPosition(cursorPosition);
1299 if (line < 0)
1300 line = 0;
1301 if (line >= d->lines.size())
1302 return;
1303
1304 QTextLine l(line, d);
1305 const QScriptLine &sl = d->lines.at(line);
1306
1307 qreal x = position.x() + l.cursorToX(cursorPosition);
1308
1309 QFixed base = sl.base();
1310 QFixed descent = sl.descent;
1311 bool rightToLeft = d->isRightToLeft();
1312
1313 const int realCursorPosition = cursorPosition;
1314 if (d->visualCursorMovement()) {
1315 if (cursorPosition == sl.from + sl.length)
1316 --cursorPosition;
1317 } else {
1318 --cursorPosition;
1319 }
1320 int itm = d->findItem(cursorPosition);
1321
1322 if (itm >= 0) {
1323 const QScriptItem *si = &d->layoutData->items.at(itm);
1324 // Same logic as in cursorToX to handle edges between writing directions to prioritise the script item
1325 // that matches the writing direction of the paragraph.
1326 if (d->layoutData->hasBidi && !d->visualCursorMovement() && si->analysis.bidiLevel % 2 != rightToLeft) {
1327 int neighborItem = itm;
1328 if (neighborItem > 0 && si->position == realCursorPosition)
1329 --neighborItem;
1330 else if (neighborItem < d->layoutData->items.size() - 1 && si->position + si->num_glyphs == realCursorPosition)
1331 ++neighborItem;
1332 const bool onBoundary = neighborItem != itm
1333 && si->analysis.bidiLevel != d->layoutData->items[neighborItem].analysis.bidiLevel;
1334 if (onBoundary && rightToLeft != si->analysis.bidiLevel % 2) {
1335 itm = neighborItem;
1336 si = &d->layoutData->items[itm];
1337 }
1338 }
1339 // objects need some special treatment as they can have special alignment or be floating
1341 if (si->ascent > 0)
1342 base = si->ascent;
1343 if (si->descent > 0)
1344 descent = si->descent;
1345 }
1346 rightToLeft = si->analysis.bidiLevel % 2;
1347 }
1348 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1349 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1350 && (p->transform().type() > QTransform::TxTranslate);
1351 if (toggleAntialiasing)
1352 p->setRenderHint(QPainter::Antialiasing);
1353 QPainter::CompositionMode origCompositionMode = p->compositionMode();
1354 if (p->paintEngine()->hasFeature(QPaintEngine::RasterOpModes))
1355 p->setCompositionMode(QPainter::RasterOp_NotDestination);
1356 const QTransform &deviceTransform = p->deviceTransform();
1357 const qreal xScale = deviceTransform.m11();
1358 if (deviceTransform.type() != QTransform::TxScale || std::trunc(xScale) == xScale) {
1359 p->fillRect(QRectF(x, y, qreal(width), (base + descent).toReal()), p->pen().brush());
1360 } else {
1361 // Ensure consistently rendered cursor width under fractional scaling
1362 const QPen origPen = p->pen();
1363 QPen pen(origPen.brush(), qRound(width * xScale), Qt::SolidLine, Qt::FlatCap);
1364 pen.setCosmetic(true);
1365 const qreal center = x + qreal(width) / 2;
1366 p->setPen(pen);
1367 p->drawLine(QPointF(center, y), QPointF(center, qCeil(y + (base + descent).toReal())));
1368 p->setPen(origPen);
1369 }
1370 p->setCompositionMode(origCompositionMode);
1371 if (toggleAntialiasing)
1372 p->setRenderHint(QPainter::Antialiasing, false);
1373 if (d->layoutData->hasBidi) {
1374 const int arrow_extent = 4;
1375 int sign = rightToLeft ? -1 : 1;
1376 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1377 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1378 }
1379 return;
1380}
1381
1449{
1450 const QScriptLine& sl = eng->lines.at(index);
1451 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1452}
1453
1458{
1459 const QScriptLine& sl = eng->lines.at(index);
1460 QFixed x = sl.x + eng->alignLine(sl);
1461
1462 QFixed width = sl.textWidth;
1463 if (sl.justified)
1464 width = sl.width;
1465
1466 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1467}
1468
1475{
1476 return eng->lines.at(index).x.toReal();
1477}
1478
1485{
1486 return eng->lines.at(index).y.toReal();
1487}
1488
1495{
1496 return eng->lines.at(index).width.toReal();
1497}
1498
1499
1506{
1507 return eng->lines.at(index).ascent.toReal();
1508}
1509
1516{
1517 return eng->lines.at(index).descent.toReal();
1518}
1519
1528{
1529 return eng->lines.at(index).height().ceil().toReal();
1530}
1531
1540{
1541 return eng->lines.at(index).leading.toReal();
1542}
1543
1559{
1560 eng->lines[index].leadingIncluded= included;
1561
1562}
1563
1575{
1576 return eng->lines.at(index).leadingIncluded;
1577}
1578
1585{
1586 return eng->lines.at(index).textWidth.toReal();
1587}
1588
1600{
1601 return eng->lines.at(index).textAdvance.toReal();
1602}
1603
1612{
1613 QScriptLine &line = eng->lines[index];
1614 if (!eng->layoutData) {
1615 qWarning("QTextLine: Can't set a line width while not layouting.");
1616 return;
1617 }
1618
1620 if (line.length
1621 && line.textWidth <= line.width
1622 && line.from + line.length == eng->layoutData->string.size())
1623 // no need to do anything if the line is already layouted and the last one. This optimization helps
1624 // when using things in a single line layout.
1625 return;
1626 line.length = 0;
1627 line.textWidth = 0;
1628
1629 layout_helper(INT_MAX);
1630}
1631
1639void QTextLine::setNumColumns(int numColumns)
1640{
1641 QScriptLine &line = eng->lines[index];
1642 line.width = QFIXED_MAX;
1643 line.length = 0;
1644 line.textWidth = 0;
1645 layout_helper(numColumns);
1646}
1647
1656void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1657{
1658 QScriptLine &line = eng->lines[index];
1659 line.width = QFixed::fromReal(alignmentWidth);
1660 line.length = 0;
1661 line.textWidth = 0;
1662 layout_helper(numColumns);
1663}
1664
1665#if 0
1666#define LB_DEBUG qDebug
1667#else
1668#define LB_DEBUG if (0) qDebug
1669#endif
1670
1671namespace {
1672
1673 struct LineBreakHelper
1674 {
1675 LineBreakHelper()
1676 : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(nullptr), logClusters(nullptr),
1677 manualWrap(false), whiteSpaceOrObject(true)
1678 {
1679 }
1680
1681
1682 QScriptLine tmpData;
1683 QScriptLine spaceData;
1684
1685 QGlyphLayout glyphs;
1686
1687 int glyphCount;
1688 int maxGlyphs;
1689 int currentPosition;
1690 glyph_t previousGlyph;
1691 QFontEngine *previousGlyphFontEngine;
1692
1693 QFixed minw;
1694 QFixed currentSoftHyphenWidth;
1695 QFixed commitedSoftHyphenWidth;
1696 QFixed rightBearing;
1697 QFixed minimumRightBearing;
1698
1699 QFontEngine *fontEngine;
1700 const unsigned short *logClusters;
1701
1702 bool manualWrap;
1703 bool whiteSpaceOrObject;
1704
1705 bool checkFullOtherwiseExtend(QScriptLine &line);
1706
1707 QFixed calculateNewWidth(const QScriptLine &line) const {
1708 return line.textWidth + tmpData.textWidth + spaceData.textWidth
1709 + (line.textWidth > 0 ? currentSoftHyphenWidth : QFixed()) + negativeRightBearing();
1710 }
1711
1712 inline glyph_t currentGlyph() const
1713 {
1714 Q_ASSERT(currentPosition > 0);
1715 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1716
1717 return glyphs.glyphs[logClusters[currentPosition - 1]];
1718 }
1719
1720 inline void saveCurrentGlyph()
1721 {
1722 previousGlyph = 0;
1723 if (currentPosition > 0 &&
1724 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1725 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1726 previousGlyphFontEngine = fontEngine;
1727 }
1728 }
1729
1730 inline void calculateRightBearing(QFontEngine *engine, glyph_t glyph)
1731 {
1733 qreal rb;
1734 engine->getGlyphBearings(glyph, nullptr, &rb);
1735
1736 // We only care about negative right bearings, so we limit the range
1737 // of the bearing here so that we can assume it's negative in the rest
1738 // of the code, as well ase use QFixed(1) as a sentinel to represent
1739 // the state where we have yet to compute the right bearing.
1740 rightBearing = qMin(QFixed::fromReal(rb), QFixed(0));
1741 }
1742
1743 inline void calculateRightBearing()
1744 {
1745 if (currentPosition <= 0)
1746 return;
1747 calculateRightBearing(fontEngine, currentGlyph());
1748 }
1749
1750 inline void calculateRightBearingForPreviousGlyph()
1751 {
1752 if (previousGlyph > 0)
1753 calculateRightBearing(previousGlyphFontEngine, previousGlyph);
1754 }
1755
1756 static const QFixed RightBearingNotCalculated;
1757
1758 inline void resetRightBearing()
1759 {
1760 rightBearing = RightBearingNotCalculated;
1761 }
1762
1763 // We express the negative right bearing as an absolute number
1764 // so that it can be applied to the width using addition.
1765 inline QFixed negativeRightBearing() const
1766 {
1767 if (rightBearing == RightBearingNotCalculated)
1768 return QFixed(0);
1769
1770 return qAbs(rightBearing);
1771 }
1772 };
1773
1774Q_CONSTINIT const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1);
1775
1776inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1777{
1778 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1779
1780 QFixed newWidth = calculateNewWidth(line);
1781 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1782 return true;
1783
1784 const QFixed oldTextWidth = line.textWidth;
1785 line += tmpData;
1786 line.textWidth += spaceData.textWidth;
1787
1788 line.length += spaceData.length;
1789 tmpData.textWidth = 0;
1790 tmpData.length = 0;
1791 spaceData.textWidth = 0;
1792 spaceData.length = 0;
1793
1794 if (oldTextWidth != line.textWidth || currentSoftHyphenWidth > 0) {
1795 commitedSoftHyphenWidth = currentSoftHyphenWidth;
1796 currentSoftHyphenWidth = 0;
1797 }
1798
1799 return false;
1800}
1801
1802} // anonymous namespace
1803
1804
1805static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1806 const QScriptItem &current, const unsigned short *logClusters,
1807 const QGlyphLayout &glyphs, QFixed *clusterWidth = nullptr)
1808{
1809 int glyphPosition = logClusters[pos];
1810 do { // got to the first next cluster
1811 ++pos;
1812 ++line.length;
1813 } while (pos < end && logClusters[pos] == glyphPosition);
1814 QFixed clusterWid = line.textWidth;
1815 do { // calculate the textWidth for the rest of the current cluster.
1816 if (!glyphs.attributes[glyphPosition].dontPrint)
1817 line.textWidth += glyphs.advances[glyphPosition];
1818 ++glyphPosition;
1819 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1820
1821 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1822
1823 if (clusterWidth)
1824 *clusterWidth += (line.textWidth - clusterWid);
1825 ++glyphCount;
1826}
1827
1828
1829// fill QScriptLine
1830void QTextLine::layout_helper(int maxGlyphs)
1831{
1832 QScriptLine &line = eng->lines[index];
1833 line.length = 0;
1834 line.trailingSpaces = 0;
1835 line.textWidth = 0;
1836 line.hasTrailingSpaces = false;
1837
1838 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.size()) {
1839 line.setDefaultHeight(eng);
1840 return;
1841 }
1842
1843 Q_ASSERT(line.from < eng->layoutData->string.size());
1844
1845 LineBreakHelper lbh;
1846
1847 lbh.maxGlyphs = maxGlyphs;
1848
1849 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1850 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1851 const bool breakWordOrAny = breakany || (wrapMode == QTextOption::WrapAtWordBoundaryOrAnywhere);
1852 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1853
1854 int item = -1;
1855 int newItem = eng->findItem(line.from);
1856 Q_ASSERT(newItem >= 0);
1857
1858 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, int(eng->layoutData->items.size()), line.width.toReal());
1859
1860 Qt::Alignment alignment = eng->option.alignment();
1861
1862 const QCharAttributes *attributes = eng->attributes();
1863 if (!attributes)
1864 return;
1865 lbh.currentPosition = line.from;
1866 int end = 0;
1867 lbh.logClusters = eng->layoutData->logClustersPtr;
1868 lbh.previousGlyph = 0;
1869
1870 bool manuallyWrapped = false;
1871 bool hasInlineObject = false;
1872 QFixed maxInlineObjectHeight = 0;
1873
1874 while (newItem < eng->layoutData->items.size()) {
1875 lbh.resetRightBearing();
1876 if (newItem != item) {
1877 item = newItem;
1878 const QScriptItem &current = eng->layoutData->items.at(item);
1879 if (!current.num_glyphs) {
1880 eng->shape(item);
1881 attributes = eng->attributes();
1882 if (!attributes)
1883 return;
1884 lbh.logClusters = eng->layoutData->logClustersPtr;
1885 }
1886 lbh.currentPosition = qMax(line.from, current.position);
1887 end = current.position + eng->length(item);
1888 lbh.glyphs = eng->shapedGlyphs(&current);
1889 QFontEngine *fontEngine = eng->fontEngine(current);
1890 if (lbh.fontEngine != fontEngine) {
1891 lbh.fontEngine = fontEngine;
1892 lbh.minimumRightBearing = qMin(QFixed(),
1893 QFixed::fromReal(fontEngine->minRightBearing()));
1894 }
1895 }
1896 const QScriptItem &current = eng->layoutData->items.at(item);
1897
1898 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1899 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1900 current.ascent);
1901 if (current.analysis.flags != QScriptAnalysis::Object) {
1902 // objects need some special treatment as they can special alignment or be floating
1903 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1904 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1905 }
1906
1908 lbh.whiteSpaceOrObject = true;
1909 if (lbh.checkFullOtherwiseExtend(line))
1910 goto found;
1911
1912 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1913 QFixed tabWidth = eng->calculateTabWidth(item, x);
1914 attributes = eng->attributes();
1915 if (!attributes)
1916 return;
1917 lbh.logClusters = eng->layoutData->logClustersPtr;
1918 lbh.glyphs = eng->shapedGlyphs(&current);
1919
1920 lbh.spaceData.textWidth += tabWidth;
1921 lbh.spaceData.length++;
1922 newItem = item + 1;
1923
1924 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1925 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1926
1927 if (lbh.checkFullOtherwiseExtend(line))
1928 goto found;
1930 lbh.whiteSpaceOrObject = true;
1931 // if the line consists only of the line separator make sure
1932 // we have a sane height
1933 if (!line.length && !lbh.tmpData.length)
1934 line.setDefaultHeight(eng);
1936 if (lbh.checkFullOtherwiseExtend(line))
1937 goto found;
1938
1939 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1940 current, lbh.logClusters, lbh.glyphs);
1941 } else {
1942 lbh.tmpData.length++;
1943 lbh.calculateRightBearingForPreviousGlyph();
1944 }
1945 line += lbh.tmpData;
1946 manuallyWrapped = true;
1947 goto found;
1948 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1949 lbh.whiteSpaceOrObject = true;
1950 lbh.tmpData.length++;
1951
1952 if (QTextDocumentPrivate::get(eng->block) != nullptr) {
1953 QTextInlineObject inlineObject(item, eng);
1954 QTextFormat f = inlineObject.format();
1955 eng->docLayout()->positionInlineObject(inlineObject, eng->block.position() + current.position, f);
1956 QTextCharFormat::VerticalAlignment valign = f.toCharFormat().verticalAlignment();
1957 if (valign != QTextCharFormat::AlignTop && valign != QTextCharFormat::AlignBottom) {
1958 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1959 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1960 }
1961 }
1962
1963 hasInlineObject = true;
1964 maxInlineObjectHeight = qMax(maxInlineObjectHeight, current.ascent + current.descent);
1965
1966 lbh.tmpData.textWidth += current.width;
1967
1968 newItem = item + 1;
1969 ++lbh.glyphCount;
1970 if (lbh.checkFullOtherwiseExtend(line))
1971 goto found;
1972 } else if (attributes[lbh.currentPosition].whiteSpace
1973 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1974 lbh.whiteSpaceOrObject = true;
1975 while (lbh.currentPosition < end
1976 && attributes[lbh.currentPosition].whiteSpace
1977 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1978 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1979 current, lbh.logClusters, lbh.glyphs);
1980 }
1981 } else {
1982 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width)
1983 goto found;
1984
1985 lbh.whiteSpaceOrObject = false;
1986 bool sb_or_ws = false;
1987 lbh.saveCurrentGlyph();
1988 QFixed accumulatedTextWidth;
1989 do {
1990 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1991 current, lbh.logClusters, lbh.glyphs, &accumulatedTextWidth);
1992
1993 // This is a hack to fix a regression caused by the introduction of the
1994 // whitespace flag to non-breakable spaces and will cause the non-breakable
1995 // spaces to behave as in previous Qt versions in the line breaking algorithm.
1996 // The line breaks do not currently follow the Unicode specs, but fixing this would
1997 // require refactoring the code and would cause behavioral regressions.
1998 const bool isBreakableSpace = lbh.currentPosition < eng->layoutData->string.size()
1999 && attributes[lbh.currentPosition].whiteSpace
2000 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak;
2001
2002 if (lbh.currentPosition >= eng->layoutData->string.size()
2003 || isBreakableSpace
2004 || attributes[lbh.currentPosition].lineBreak
2005 || lbh.tmpData.textWidth >= QFIXED_MAX) {
2006 sb_or_ws = true;
2007 break;
2008 } else if (attributes[lbh.currentPosition].graphemeBoundary) {
2009 if (breakWordOrAny) {
2010 lbh.minw = qMax(accumulatedTextWidth, lbh.minw);
2011 accumulatedTextWidth = 0;
2012 }
2013 if (breakany)
2014 break;
2015 }
2016 } while (lbh.currentPosition < end);
2017 lbh.minw = qMax(accumulatedTextWidth, lbh.minw);
2018
2019 if (lbh.currentPosition > 0 && lbh.currentPosition <= end
2020 && (lbh.currentPosition == end || attributes[lbh.currentPosition].lineBreak)
2021 && eng->layoutData->string.at(lbh.currentPosition - 1) == QChar::SoftHyphen) {
2022 // if we are splitting up a word because of
2023 // a soft hyphen then we ...
2024 //
2025 // a) have to take the width of the soft hyphen into
2026 // account to see if the first syllable(s) /and/
2027 // the soft hyphen fit into the line
2028 //
2029 // b) if we are so short of available width that the
2030 // soft hyphen is the first breakable position, then
2031 // we don't want to show it. However we initially
2032 // have to take the width for it into account so that
2033 // the text document layout sees the overflow and
2034 // switch to break-anywhere mode, in which we
2035 // want the soft-hyphen to slip into the next line
2036 // and thus become invisible again.
2037 //
2038 lbh.currentSoftHyphenWidth = lbh.glyphs.advances[lbh.logClusters[lbh.currentPosition - 1]];
2039 }
2040
2041 if (sb_or_ws|breakany) {
2042 // To compute the final width of the text we need to take negative right bearing
2043 // into account (negative right bearing means the glyph has pixel data past the
2044 // advance length). Note that the negative right bearing is an absolute number,
2045 // so that we can apply it to the width using straight forward addition.
2046
2047 // Store previous right bearing (for the already accepted glyph) in case we
2048 // end up breaking due to the current glyph being too wide.
2049 QFixed previousRightBearing = lbh.rightBearing;
2050
2051 // We skip calculating the right bearing if the minimum negative bearing is too
2052 // small to possibly expand the text beyond the edge. Note that this optimization
2053 // will in some cases fail, as the minimum right bearing reported by the font
2054 // engine may not cover all the glyphs in the font. The result is that we think
2055 // we don't need to break at the current glyph (because the right bearing is 0),
2056 // and when we then end up breaking on the next glyph we compute the right bearing
2057 // and end up with a line width that is slightly larger width than what was requested.
2058 // Unfortunately we can't remove this optimization as it will slow down text
2059 // layouting significantly, so we accept the slight correctness issue.
2060 if ((lbh.calculateNewWidth(line) + qAbs(lbh.minimumRightBearing)) > line.width)
2061 lbh.calculateRightBearing();
2062
2063 if (lbh.checkFullOtherwiseExtend(line)) {
2064
2065 // We are too wide to accept the next glyph with its bearing, so we restore the
2066 // right bearing to that of the previous glyph (the one that was already accepted),
2067 // so that the bearing can be be applied to the final width of the text below.
2068 if (previousRightBearing != LineBreakHelper::RightBearingNotCalculated)
2069 lbh.rightBearing = previousRightBearing;
2070 else
2071 lbh.calculateRightBearingForPreviousGlyph();
2072
2073 line.textWidth += lbh.commitedSoftHyphenWidth;
2074
2075 goto found;
2076 }
2077 }
2078 lbh.saveCurrentGlyph();
2079 }
2080 if (lbh.currentPosition == end)
2081 newItem = item + 1;
2082 }
2083 LB_DEBUG("reached end of line");
2084 lbh.checkFullOtherwiseExtend(line);
2085 line.textWidth += lbh.commitedSoftHyphenWidth;
2086found:
2087 line.textAdvance = line.textWidth;
2088
2089 // If right bearing has not been calculated yet, do that now
2090 if (lbh.rightBearing == LineBreakHelper::RightBearingNotCalculated && !lbh.whiteSpaceOrObject)
2091 lbh.calculateRightBearing();
2092
2093 // Then apply any negative right bearing
2094 line.textWidth += lbh.negativeRightBearing();
2095
2096 if (line.length == 0) {
2097 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
2098 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
2099 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
2100 line += lbh.tmpData;
2101 }
2102
2103 if (hasInlineObject && QTextDocumentPrivate::get(eng->block) != nullptr) {
2104 // position top/bottom aligned inline objects
2105 if (maxInlineObjectHeight > line.ascent + line.descent) {
2106 // extend line height if required
2107 QFixed toAdd = (maxInlineObjectHeight - line.ascent - line.descent)/2;
2108 line.ascent += toAdd;
2109 line.descent = maxInlineObjectHeight - line.ascent;
2110 }
2111 int startItem = eng->findItem(line.from);
2112 int endItem = eng->findItem(line.from + line.length);
2113 if (endItem < 0)
2114 endItem = eng->layoutData->items.size();
2115 for (int item = startItem; item < endItem; ++item) {
2116 QScriptItem &current = eng->layoutData->items[item];
2117 if (current.analysis.flags == QScriptAnalysis::Object) {
2118 QTextInlineObject inlineObject(item, eng);
2119 QTextCharFormat::VerticalAlignment align = inlineObject.format().toCharFormat().verticalAlignment();
2120 QFixed height = current.ascent + current.descent;
2121 switch (align) {
2123 current.ascent = line.ascent;
2124 current.descent = height - line.ascent;
2125 break;
2127 current.ascent = (line.ascent + line.descent) / 2 - line.descent + height / 2;
2128 current.descent = height - line.ascent;
2129 break;
2131 current.descent = line.descent;
2132 current.ascent = height - line.descent;
2133 break;
2134 default:
2135 break;
2136 }
2137 Q_ASSERT(line.ascent >= current.ascent);
2138 Q_ASSERT(line.descent >= current.descent);
2139 }
2140 }
2141 }
2142
2143
2144 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
2145 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
2146 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
2147
2148 const QFixed trailingSpace = (eng->option.flags() & QTextOption::IncludeTrailingSpaces
2149 ? lbh.spaceData.textWidth
2150 : QFixed(0));
2152 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
2153 || (lbh.maxGlyphs == INT_MAX && line.textWidth > (line.width - trailingSpace))) {
2154
2156 layout_helper(lbh.maxGlyphs);
2158 return;
2159 }
2160 }
2161
2162 if (lbh.manualWrap) {
2163 eng->minWidth = qMax(eng->minWidth, line.textWidth);
2164 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
2165 } else {
2166 eng->minWidth = qMax(eng->minWidth, lbh.minw);
2169 if (!manuallyWrapped) {
2170 if (qAddOverflow(eng->layoutData->currentMaxWidth, lbh.spaceData.textWidth, &eng->layoutData->currentMaxWidth))
2172 }
2173 eng->maxWidth = qMax(eng->maxWidth, eng->layoutData->currentMaxWidth);
2174 if (manuallyWrapped)
2175 eng->layoutData->currentMaxWidth = 0;
2176 }
2177
2178 line.textWidth += trailingSpace;
2179 if (lbh.spaceData.length) {
2180 line.trailingSpaces = lbh.spaceData.length;
2181 line.hasTrailingSpaces = true;
2182 }
2183
2184 line.justified = false;
2185 line.gridfitted = false;
2186}
2187
2192{
2193 eng->lines[index].x = QFixed::fromReal(pos.x());
2194 eng->lines[index].y = QFixed::fromReal(pos.y());
2195}
2196
2201{
2202 return QPointF(eng->lines.at(index).x.toReal(), eng->lines.at(index).y.toReal());
2203}
2204
2205// ### DOC: I have no idea what this means/does.
2206// You create a text layout with a string of text. Once you laid
2207// it out, it contains a number of QTextLines. from() returns the position
2208// inside the text string where this line starts. If you e.g. has a
2209// text of "This is a string", laid out into two lines (the second
2210// starting at the word 'a'), layout.lineAt(0).from() == 0 and
2211// layout.lineAt(1).from() == 8.
2217{
2218 return eng->lines.at(index).from;
2219}
2220
2227{
2229 && eng->block.isValid() && index == eng->lines.size()-1) {
2230 return eng->lines.at(index).length - 1;
2231 }
2232 return eng->lines.at(index).length + eng->lines.at(index).trailingSpaces;
2233}
2234
2235static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2236{
2237 QBrush c = chf.foreground();
2238 if (c.style() == Qt::NoBrush) {
2239 p->setPen(defaultPen);
2240 }
2241
2242 QBrush bg = chf.background();
2243 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2244 p->fillRect(r.toAlignedRect(), bg);
2245 if (c.style() != Qt::NoBrush) {
2246 p->setPen(QPen(c, 0));
2247 }
2248
2249}
2250
2251#if !defined(QT_NO_RAWFONT)
2253 const QString &text,
2254 const QGlyphLayout &glyphLayout,
2255 const QPointF &pos,
2256 const QGlyphRun::GlyphRunFlags &flags,
2257 QTextLayout::GlyphRunRetrievalFlags retrievalFlags,
2258 QFixed selectionX,
2259 QFixed selectionWidth,
2260 int glyphsStart,
2261 int glyphsEnd,
2262 unsigned short *logClusters,
2263 int textPosition,
2264 int textLength)
2265{
2266 Q_ASSERT(logClusters != nullptr);
2267
2268 QGlyphRun glyphRun;
2269
2271
2272 int rangeStart = textPosition;
2273 int logClusterIndex = 0;
2274 while (logClusters[logClusterIndex] != glyphsStart && rangeStart < textPosition + textLength) {
2275 ++logClusterIndex;
2276 ++rangeStart;
2277 }
2278
2279 int rangeEnd = rangeStart;
2280 while (logClusters[logClusterIndex] != glyphsEnd && rangeEnd < textPosition + textLength) {
2281 ++logClusterIndex;
2282 ++rangeEnd;
2283 }
2284
2285 d->textRangeStart = rangeStart;
2286 d->textRangeEnd = rangeEnd;
2287
2288 // Make a font for this particular engine
2289 QRawFont font;
2291 fontD->setFontEngine(fontEngine);
2292
2293 QVarLengthArray<glyph_t> glyphsArray;
2294 QVarLengthArray<QFixedPoint> positionsArray;
2295
2296 QTextItem::RenderFlags renderFlags;
2297 if (flags.testFlag(QGlyphRun::Overline))
2298 renderFlags |= QTextItem::Overline;
2299 if (flags.testFlag(QGlyphRun::Underline))
2300 renderFlags |= QTextItem::Underline;
2301 if (flags.testFlag(QGlyphRun::StrikeOut))
2302 renderFlags |= QTextItem::StrikeOut;
2303 if (flags.testFlag(QGlyphRun::RightToLeft))
2304 renderFlags |= QTextItem::RightToLeft;
2305
2306 fontEngine->getGlyphPositions(glyphLayout, QTransform(), renderFlags, glyphsArray,
2307 positionsArray);
2308 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2309
2310 qreal fontHeight = font.ascent() + font.descent();
2311 qreal minY = 0;
2312 qreal maxY = 0;
2313 QList<quint32> glyphs;
2314 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2315 glyphs.reserve(glyphsArray.size());
2317 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2318 positions.reserve(glyphsArray.size());
2319 QList<qsizetype> stringIndexes;
2320 if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2321 stringIndexes.reserve(glyphsArray.size());
2322
2323 int nextClusterIndex = 0;
2324 int currentClusterIndex = 0;
2325 for (int i = 0; i < glyphsArray.size(); ++i) {
2326 const int glyphArrayIndex = i + glyphsStart;
2327 // Search for the next cluster in the string (or the end of string if there are no
2328 // more clusters)
2329 if (retrievalFlags & QTextLayout::RetrieveStringIndexes) {
2330 if (nextClusterIndex < textLength && logClusters[nextClusterIndex] == glyphArrayIndex) {
2331 currentClusterIndex = nextClusterIndex; // Store current cluster
2332 while (logClusters[nextClusterIndex] == glyphArrayIndex && nextClusterIndex < textLength)
2333 ++nextClusterIndex;
2334 }
2335
2336 // We are now either at end of string (no more clusters) or we are not yet at the
2337 // next cluster in glyph array. We fill in current cluster so that there is always one
2338 // entry in stringIndexes for each glyph.
2339 Q_ASSERT(nextClusterIndex == textLength || logClusters[nextClusterIndex] != glyphArrayIndex);
2340 stringIndexes.append(textPosition + currentClusterIndex);
2341 }
2342
2343 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes) {
2344 glyph_t glyphIndex = glyphsArray.at(i) & 0xffffff;
2345 glyphs.append(glyphIndex);
2346 }
2347
2348 QPointF position = positionsArray.at(i).toPointF() + pos;
2349 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2350 positions.append(position);
2351
2352 if (i == 0) {
2353 maxY = minY = position.y();
2354 } else {
2355 minY = qMin(minY, position.y());
2356 maxY = qMax(maxY, position.y());
2357 }
2358 }
2359
2360 qreal height = maxY + fontHeight - minY;
2361
2362 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2363 glyphRun.setGlyphIndexes(glyphs);
2364 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2365 glyphRun.setPositions(positions);
2366 if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2367 glyphRun.setStringIndexes(stringIndexes);
2368 if (retrievalFlags & QTextLayout::RetrieveString)
2369 glyphRun.setSourceString(text);
2370 glyphRun.setFlags(flags);
2371 glyphRun.setRawFont(font);
2372
2373 glyphRun.setBoundingRect(QRectF(selectionX.toReal(), minY - font.ascent(),
2374 selectionWidth.toReal(), height));
2375
2376 return glyphRun;
2377}
2378
2379# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
2400QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
2401{
2403}
2404# endif
2405
2424 int length,
2425 QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
2426{
2427 const QScriptLine &line = eng->lines.at(index);
2428
2429 if (line.length == 0)
2430 return QList<QGlyphRun>();
2431
2432 if (from < 0)
2433 from = textStart();
2434
2435 if (length < 0)
2436 length = textLength();
2437
2438 if (length == 0)
2439 return QList<QGlyphRun>();
2440
2442 selection.start = from;
2444
2445 QTextLineItemIterator iterator(eng, index, QPointF(), &selection);
2446 qreal y = line.y.toReal() + line.base().toReal();
2448 while (!iterator.atEnd()) {
2449 QScriptItem &si = iterator.next();
2451 continue;
2452
2453 if (from >= 0 && length >= 0 && (from >= iterator.itemEnd || from + length <= iterator.itemStart))
2454 continue;
2455
2456 QPointF pos(iterator.x.toReal(), y);
2457
2458 QFont font;
2459 QGlyphRun::GlyphRunFlags flags;
2460 if (!eng->useRawFont) {
2461 font = eng->font(si);
2462 if (font.overline())
2464 if (font.underline())
2466 if (font.strikeOut())
2468 }
2469
2470 bool rtl = false;
2471 if (si.analysis.bidiLevel % 2) {
2473 rtl = true;
2474 }
2475
2476 int relativeFrom = qMax(iterator.itemStart, from) - si.position;
2477 int relativeTo = qMin(iterator.itemEnd, from + length) - 1 - si.position;
2478
2479 unsigned short *logClusters = eng->logClusters(&si);
2480 int glyphsStart = logClusters[relativeFrom];
2481 int glyphsEnd = (relativeTo == iterator.itemLength) ? si.num_glyphs - 1 : logClusters[relativeTo];
2482 // the glyph index right next to the requested range
2483 int nextGlyphIndex = (relativeTo < iterator.itemLength - 1) ? logClusters[relativeTo + 1] : si.num_glyphs;
2484 if (nextGlyphIndex - 1 > glyphsEnd)
2485 glyphsEnd = nextGlyphIndex - 1;
2486 bool startsInsideLigature = relativeFrom > 0 && logClusters[relativeFrom - 1] == glyphsStart;
2487 bool endsInsideLigature = nextGlyphIndex == glyphsEnd;
2488
2489 int itemGlyphsStart = logClusters[iterator.itemStart - si.position];
2490 int itemGlyphsEnd = logClusters[iterator.itemEnd - 1 - si.position];
2491
2492 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si);
2493
2494 // Calculate new x position of glyph layout for a subset. This becomes somewhat complex
2495 // when we're breaking a RTL script item, since the expected position passed into
2496 // getGlyphPositions() is the left-most edge of the left-most glyph in an RTL run.
2497 if (relativeFrom != (iterator.itemStart - si.position) && !rtl) {
2498 for (int i = itemGlyphsStart; i < glyphsStart; ++i) {
2499 if (!glyphLayout.attributes[i].dontPrint) {
2500 QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2501 pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2502 }
2503 }
2504 } else if (relativeTo != (iterator.itemEnd - si.position - 1) && rtl) {
2505 for (int i = itemGlyphsEnd; i > glyphsEnd; --i) {
2506 if (!glyphLayout.attributes[i].dontPrint) {
2507 QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2508 pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2509 }
2510 }
2511 }
2512
2513 glyphLayout = glyphLayout.mid(glyphsStart, glyphsEnd - glyphsStart + 1);
2514
2515 QFixed x;
2516 QFixed width;
2517 iterator.getSelectionBounds(&x, &width);
2518
2519 if (glyphLayout.numGlyphs > 0) {
2520 QFontEngine *mainFontEngine;
2521#ifndef QT_NO_RAWFONT
2522 if (eng->useRawFont && eng->rawFont.isValid())
2523 mainFontEngine= eng->fontEngine(si);
2524 else
2525#endif
2526 mainFontEngine = font.d->engineForScript(si.analysis.script);
2527
2528 if (mainFontEngine->type() == QFontEngine::Multi) {
2529 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2530 int start = rtl ? glyphLayout.numGlyphs : 0;
2531 int end = start - 1;
2532 int which = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2533 for (; (rtl && start > 0) || (!rtl && end < glyphLayout.numGlyphs - 1);
2534 rtl ? --start : ++end) {
2535 const int e = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2536 if (e == which)
2537 continue;
2538
2539 QGlyphLayout subLayout = glyphLayout.mid(start, end - start + 1);
2540 multiFontEngine->ensureEngineAt(which);
2541
2542 QGlyphRun::GlyphRunFlags subFlags = flags;
2543 if (start == 0 && startsInsideLigature)
2544 subFlags |= QGlyphRun::SplitLigature;
2545
2546 {
2547 QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
2548 eng->text,
2549 subLayout,
2550 pos,
2551 subFlags,
2552 retrievalFlags,
2553 x,
2554 width,
2555 glyphsStart + start,
2556 glyphsStart + end,
2557 logClusters + relativeFrom,
2558 relativeFrom + si.position,
2559 relativeTo - relativeFrom + 1);
2560 if (!glyphRun.isEmpty())
2561 glyphRuns.append(glyphRun);
2562 }
2563 for (int i = 0; i < subLayout.numGlyphs; ++i) {
2564 if (!subLayout.attributes[i].dontPrint) {
2565 QFixed justification = QFixed::fromFixed(subLayout.justifications[i].space_18d6);
2566 pos.rx() += (subLayout.advances[i] + justification).toReal();
2567 }
2568 }
2569
2570 if (rtl)
2571 end = start - 1;
2572 else
2573 start = end + 1;
2574 which = e;
2575 }
2576
2577 QGlyphLayout subLayout = glyphLayout.mid(start, end - start + 1);
2578 multiFontEngine->ensureEngineAt(which);
2579
2580 QGlyphRun::GlyphRunFlags subFlags = flags;
2581 if ((start == 0 && startsInsideLigature) || endsInsideLigature)
2582 subFlags |= QGlyphRun::SplitLigature;
2583
2584 QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
2585 eng->text,
2586 subLayout,
2587 pos,
2588 subFlags,
2589 retrievalFlags,
2590 x,
2591 width,
2592 glyphsStart + start,
2593 glyphsStart + end,
2594 logClusters + relativeFrom,
2595 relativeFrom + si.position,
2596 relativeTo - relativeFrom + 1);
2597 if (!glyphRun.isEmpty())
2598 glyphRuns.append(glyphRun);
2599 } else {
2600 if (startsInsideLigature || endsInsideLigature)
2602 QGlyphRun glyphRun = glyphRunWithInfo(mainFontEngine,
2603 eng->text,
2604 glyphLayout,
2605 pos,
2606 flags,
2607 retrievalFlags,
2608 x,
2609 width,
2610 glyphsStart,
2611 glyphsEnd,
2612 logClusters + relativeFrom,
2613 relativeFrom + si.position,
2614 relativeTo - relativeFrom + 1);
2615 if (!glyphRun.isEmpty())
2616 glyphRuns.append(glyphRun);
2617 }
2618 }
2619 }
2620
2621 return glyphRuns;
2622}
2623#endif // QT_NO_RAWFONT
2624
2631{
2632 draw_internal(painter, position, nullptr);
2633}
2634
2635void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
2637{
2638#ifndef QT_NO_RAWFONT
2639 // Not intended to work with rawfont
2640 Q_ASSERT(!eng->useRawFont);
2641#endif
2642 const QScriptLine &line = eng->lines[index];
2643 QPen pen = p->pen();
2644
2645 bool noText = (selection && selection->format.property(SuppressText).toBool());
2646
2647 if (!line.length) {
2648 if (selection
2649 && selection->start <= line.from
2650 && selection->start + selection->length > line.from) {
2651
2652 const qreal lineHeight = line.height().toReal();
2653 QRectF r(origPos.x() + line.x.toReal(), origPos.y() + line.y.toReal(),
2654 lineHeight / 2, QFontMetrics(eng->font()).horizontalAdvance(u' '));
2656 p->setPen(pen);
2657 }
2658 return;
2659 }
2660
2661 Q_CONSTINIT static QRectF maxFixedRect(-QFIXED_MAX / 2, -QFIXED_MAX / 2, QFIXED_MAX, QFIXED_MAX);
2662 const bool xlateToFixedRange = !maxFixedRect.contains(origPos);
2663 QPointF pos;
2664 if (Q_LIKELY(!xlateToFixedRange))
2665 pos = origPos;
2666 else
2667 p->translate(origPos);
2668
2670 QFixed lineBase = line.base();
2671 eng->clearDecorations();
2673
2674 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2675
2676 const QTextFormatCollection *formatCollection = eng->formatCollection();
2677
2678 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2679 while (!iterator.atEnd()) {
2680 QScriptItem &si = iterator.next();
2681
2682 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2683 continue;
2684
2687 continue;
2688
2689 QFixed itemBaseLine = y;
2690 QFont f = eng->font(si);
2692 if (formatCollection != nullptr)
2693 format = formatCollection->defaultTextFormat();
2694
2695 if (eng->hasFormats() || selection || formatCollection) {
2696 format.merge(eng->format(&si));
2697
2698 if (suppressColors) {
2699 format.clearForeground();
2700 format.clearBackground();
2702 }
2703 if (selection)
2704 format.merge(selection->format);
2705
2706 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2707 iterator.itemWidth.toReal(), line.height().toReal()));
2708
2709 const qreal baseLineOffset = format.baselineOffset() / 100.0;
2710 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2714 {
2715 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2716 QFixed height = fe->ascent() + fe->descent();
2717 itemBaseLine -= height * QFixed::fromReal(baseLineOffset);
2718
2719 if (valign == QTextCharFormat::AlignSubScript)
2720 itemBaseLine += height * QFixed::fromReal(format.subScriptBaseline() / 100.0);
2721 else if (valign == QTextCharFormat::AlignSuperScript)
2722 itemBaseLine -= height * QFixed::fromReal(format.superScriptBaseline() / 100.0);
2723 }
2724 }
2725
2727
2728 if (eng->hasFormats()) {
2729 p->save();
2731 QFixed itemY = y - si.ascent;
2732 switch (format.verticalAlignment()) {
2734 itemY = y - lineBase;
2735 break;
2737 itemY = y - lineBase + (line.height() - si.height()) / 2;
2738 break;
2740 itemY = y - lineBase + line.height() - si.height();
2741 break;
2742 default:
2743 break;
2744 }
2745
2746 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2747
2748 eng->docLayout()->drawInlineObject(p, itemRect,
2749 QTextInlineObject(iterator.item, eng),
2750 si.position + eng->block.position(),
2751 format);
2752 if (selection) {
2753 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2754 if (bg.style() != Qt::NoBrush) {
2755 QColor c = bg.color();
2756 c.setAlpha(128);
2757 p->fillRect(itemRect, c);
2758 }
2759 }
2760 } else { // si.isTab
2761 QFont f = eng->font(si);
2762 QTextItemInt gf(si, &f, format);
2763 gf.chars = nullptr;
2764 gf.num_chars = 0;
2765 gf.width = iterator.itemWidth;
2766 QPainterPrivate::get(p)->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf, eng);
2768 const QChar visualTab = QChar(QChar::VisualTabCharacter);
2769 int w = QFontMetrics(f).horizontalAdvance(visualTab);
2770 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2771 if (x < 0)
2772 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2773 iterator.itemWidth.toReal(), line.height().toReal()),
2775 else
2776 x /= 2; // Centered
2777 p->setFont(f);
2778 p->drawText(QPointF(iterator.x.toReal() + x,
2779 y.toReal()), visualTab);
2780 }
2781
2782 }
2783 p->restore();
2784 }
2785
2786 continue;
2787 }
2788
2789 unsigned short *logClusters = eng->logClusters(&si);
2790 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2791
2792 QTextItemInt gf(glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart),
2793 &f, eng->layoutData->string.unicode() + iterator.itemStart,
2794 iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format);
2795 gf.logClusters = logClusters + iterator.itemStart - si.position;
2796 gf.width = iterator.itemWidth;
2797 gf.justified = line.justified;
2798 gf.initWithScriptItem(si);
2799
2800 Q_ASSERT(gf.fontEngine);
2801
2802 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2803 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2805 path.setFillRule(Qt::WindingFill);
2806
2807 if (gf.glyphs.numGlyphs)
2808 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2809 if (gf.flags) {
2810 const QFontEngine *fe = gf.fontEngine;
2811 const qreal lw = fe->lineThickness().toReal();
2812 if (gf.flags & QTextItem::Underline) {
2813 qreal offs = fe->underlinePosition().toReal();
2814 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2815 }
2816 if (gf.flags & QTextItem::Overline) {
2817 qreal offs = fe->ascent().toReal() + 1;
2818 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2819 }
2820 if (gf.flags & QTextItem::StrikeOut) {
2821 qreal offs = fe->ascent().toReal() / 3;
2822 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2823 }
2824 }
2825
2826 p->save();
2827 p->setRenderHint(QPainter::Antialiasing);
2828 //Currently QPen with a Qt::NoPen style still returns a default
2829 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2830 if (p->pen().style() == Qt::NoPen)
2831 p->setBrush(Qt::NoBrush);
2832 else
2833 p->setBrush(p->pen().brush());
2834
2835 p->setPen(format.textOutline());
2836 p->drawPath(path);
2837 p->restore();
2838 } else {
2839 if (noText)
2840 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2842 }
2843
2847 QBrush c = format.foreground();
2848 if (c.style() != Qt::NoBrush)
2849 p->setPen(c.color());
2850 const QChar visualSpace = si.analysis.flags == QScriptAnalysis::Space ? u'\xb7' : u'\xb0';
2851 QFont oldFont = p->font();
2852 p->setFont(eng->font(si));
2853 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2854 p->setPen(pen);
2855 p->setFont(oldFont);
2856 }
2857 }
2858 eng->drawDecorations(p);
2859
2860 if (xlateToFixedRange)
2861 p->translate(-origPos);
2862
2863 if (eng->hasFormats())
2864 p->setPen(pen);
2865}
2866
2883qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2884{
2885 const QScriptLine &line = eng->lines[index];
2886 bool lastLine = index >= eng->lines.size() - 1;
2887
2888 QFixed x = line.x + eng->alignLine(line) - eng->leadingSpaceWidth(line);
2889
2890 if (!eng->layoutData)
2891 eng->itemize();
2892 if (!eng->layoutData->items.size()) {
2893 *cursorPos = line.from;
2894 return x.toReal();
2895 }
2896
2897 int lineEnd = line.from + line.length + line.trailingSpaces;
2898 int pos = qBound(line.from, *cursorPos, lineEnd);
2899 const QCharAttributes *attributes = eng->attributes();
2900 if (!attributes) {
2901 *cursorPos = line.from;
2902 return x.toReal();
2903 }
2904 while (pos < lineEnd && !attributes[pos].graphemeBoundary)
2905 pos++;
2906 // end of line ensure we have the last item on the line
2907 int itm = pos == lineEnd ? eng->findItem(pos-1) : eng->findItem(pos);
2908 if (itm < 0) {
2909 *cursorPos = line.from;
2910 return x.toReal();
2911 }
2912 eng->shapeLine(line);
2913
2914 const QScriptItem *scriptItem = &eng->layoutData->items[itm];
2915 if (!scriptItem->num_glyphs)
2916 eng->shape(itm);
2917
2918 if ((scriptItem->analysis.bidiLevel % 2 != eng->isRightToLeft()) && !eng->visualCursorMovement()) {
2919 // If the item we found has a different writing direction than the engine,
2920 // check if the cursor is between two items with different writing direction
2921 int neighborItem = itm;
2922 if (neighborItem > 0 && scriptItem->position == pos)
2923 --neighborItem;
2924 else if (neighborItem < eng->layoutData->items.size() - 1 && scriptItem->position + scriptItem->num_glyphs == pos)
2925 ++neighborItem;
2926 const bool onBoundary = neighborItem != itm && scriptItem->analysis.bidiLevel != eng->layoutData->items[neighborItem].analysis.bidiLevel;
2927 // If we are, prioritise the neighbor item that has the same direction as the engine
2928 if (onBoundary) {
2929 if (eng->isRightToLeft() != scriptItem->analysis.bidiLevel % 2) {
2930 itm = neighborItem;
2931 scriptItem = &eng->layoutData->items[itm];
2932 if (!scriptItem->num_glyphs)
2933 eng->shape(itm);
2934 }
2935 }
2936 }
2937
2938 const int l = eng->length(itm);
2939 pos = qBound(0, pos - scriptItem->position, l);
2940
2941 QGlyphLayout glyphs = eng->shapedGlyphs(scriptItem);
2942 unsigned short *logClusters = eng->logClusters(scriptItem);
2943 Q_ASSERT(logClusters);
2944
2945 int glyph_pos = pos == l ? scriptItem->num_glyphs : logClusters[pos];
2946 if (edge == Trailing && glyph_pos < scriptItem->num_glyphs) {
2947 // trailing edge is leading edge of next cluster
2948 glyph_pos++;
2949 while (glyph_pos < scriptItem->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2950 glyph_pos++;
2951 }
2952
2953 bool reverse = scriptItem->analysis.bidiLevel % 2;
2954
2955
2956 // add the items left of the cursor
2957
2958 int firstItem = eng->findItem(line.from);
2959 int lastItem = eng->findItem(lineEnd - 1, itm);
2960 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2961
2962 QVarLengthArray<int> visualOrder(nItems);
2964 for (int i = 0; i < nItems; ++i)
2965 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2966 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2967
2968 for (int i = 0; i < nItems; ++i) {
2969 int item = visualOrder[i]+firstItem;
2970 if (item == itm)
2971 break;
2972 QScriptItem &si = eng->layoutData->items[item];
2973 if (!si.num_glyphs)
2974 eng->shape(item);
2975
2977 x += si.width;
2978 continue;
2979 }
2980
2981 const int itemLength = eng->length(item);
2982 int start = qMax(line.from, si.position);
2983 int end = qMin(lineEnd, si.position + itemLength);
2984
2985 logClusters = eng->logClusters(&si);
2986
2987 int gs = logClusters[start-si.position];
2988 int ge = (end == si.position + itemLength) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2989
2990 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2991
2992 while (gs <= ge) {
2993 x += glyphs.effectiveAdvance(gs);
2994 ++gs;
2995 }
2996 }
2997
2998 logClusters = eng->logClusters(scriptItem);
2999 glyphs = eng->shapedGlyphs(scriptItem);
3000 if (scriptItem->analysis.flags >= QScriptAnalysis::TabOrObject) {
3001 if (pos == (reverse ? 0 : l))
3002 x += scriptItem->width;
3003 } else {
3004 bool rtl = eng->isRightToLeft();
3005 bool visual = eng->visualCursorMovement();
3006 int end = qMin(lineEnd, scriptItem->position + l) - scriptItem->position;
3007 if (reverse) {
3008 int glyph_end = end == l ? scriptItem->num_glyphs : logClusters[end];
3009 int glyph_start = glyph_pos;
3010 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
3011 glyph_start++;
3012 for (int i = glyph_end - 1; i >= glyph_start; i--)
3013 x += glyphs.effectiveAdvance(i);
3014 x -= eng->offsetInLigature(scriptItem, pos, end, glyph_pos);
3015 } else {
3016 int start = qMax(line.from - scriptItem->position, 0);
3017 int glyph_start = logClusters[start];
3018 int glyph_end = glyph_pos;
3019 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
3020 glyph_end--;
3021 for (int i = glyph_start; i <= glyph_end; i++)
3022 x += glyphs.effectiveAdvance(i);
3023 x += eng->offsetInLigature(scriptItem, pos, end, glyph_pos);
3024 }
3025 }
3026
3027 if (eng->option.wrapMode() != QTextOption::NoWrap && x > line.x + line.width)
3028 x = line.x + line.width;
3029 if (eng->option.wrapMode() != QTextOption::NoWrap && x < 0)
3030 x = 0;
3031
3032 *cursorPos = pos + scriptItem->position;
3033 return x.toReal();
3034}
3035
3046{
3048 const QScriptLine &line = eng->lines[index];
3049 bool lastLine = index >= eng->lines.size() - 1;
3050 int lineNum = index;
3051
3052 if (!eng->layoutData)
3053 eng->itemize();
3054
3055 int line_length = textLength();
3056
3057 if (!line_length)
3058 return line.from;
3059
3060 int firstItem = eng->findItem(line.from);
3061 int lastItem = eng->findItem(line.from + line_length - 1, firstItem);
3062 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
3063
3064 if (!nItems)
3065 return 0;
3066
3067 x -= line.x;
3068 x -= eng->alignLine(line);
3069// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
3070
3071 QVarLengthArray<int> visualOrder(nItems);
3073 for (int i = 0; i < nItems; ++i)
3074 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
3075 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
3076
3077 bool visual = eng->visualCursorMovement();
3078 if (x <= 0) {
3079 // left of first item
3080 if (eng->isRightToLeft())
3081 return line.from + line_length;
3082 return line.from;
3083 } else if (x < line.textWidth || (line.justified && x < line.width)) {
3084 // has to be in one of the runs
3085 QFixed pos;
3086 bool rtl = eng->isRightToLeft();
3087
3088 eng->shapeLine(line);
3089 const auto insertionPoints = (visual && rtl) ? eng->insertionPointsForLine(lineNum) : std::vector<int>();
3090 int nchars = 0;
3091 for (int i = 0; i < nItems; ++i) {
3092 int item = visualOrder[i]+firstItem;
3093 QScriptItem &si = eng->layoutData->items[item];
3094 int item_length = eng->length(item);
3095// qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
3096
3097 int start = qMax(line.from - si.position, 0);
3098 int end = qMin(line.from + line_length - si.position, item_length);
3099
3100 unsigned short *logClusters = eng->logClusters(&si);
3101
3102 int gs = logClusters[start];
3103 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
3104 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
3105
3106 QFixed item_width = 0;
3108 item_width = si.width;
3109 } else {
3110 int g = gs;
3111 while (g <= ge) {
3112 item_width += glyphs.effectiveAdvance(g);
3113 ++g;
3114 }
3115 }
3116// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
3117
3118 if (pos + item_width < x) {
3119 pos += item_width;
3120 nchars += end;
3121 continue;
3122 }
3123// qDebug(" inside run");
3125 if (cpos == QTextLine::CursorOnCharacter)
3126 return si.position;
3127 bool left_half = (x - pos) < item_width/2;
3128
3129 if (bool(si.analysis.bidiLevel % 2) != left_half)
3130 return si.position;
3131 return si.position + 1;
3132 }
3133
3134 int glyph_pos = -1;
3135 QFixed edge;
3136 // has to be inside run
3137 if (cpos == QTextLine::CursorOnCharacter) {
3138 if (si.analysis.bidiLevel % 2) {
3139 pos += item_width;
3140 glyph_pos = gs;
3141 while (gs <= ge) {
3142 if (glyphs.attributes[gs].clusterStart) {
3143 if (pos < x)
3144 break;
3145 glyph_pos = gs;
3146 edge = pos;
3147 }
3148 pos -= glyphs.effectiveAdvance(gs);
3149 ++gs;
3150 }
3151 } else {
3152 glyph_pos = gs;
3153 while (gs <= ge) {
3154 if (glyphs.attributes[gs].clusterStart) {
3155 if (pos > x)
3156 break;
3157 glyph_pos = gs;
3158 edge = pos;
3159 }
3160 pos += glyphs.effectiveAdvance(gs);
3161 ++gs;
3162 }
3163 }
3164 } else {
3165 QFixed dist = INT_MAX/256;
3166 if (si.analysis.bidiLevel % 2) {
3167 if (!visual || rtl || (lastLine && i == nItems - 1)) {
3168 pos += item_width;
3169 while (gs <= ge) {
3170 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3171 glyph_pos = gs;
3172 edge = pos;
3173 dist = qAbs(x-pos);
3174 }
3175 pos -= glyphs.effectiveAdvance(gs);
3176 ++gs;
3177 }
3178 } else {
3179 while (ge >= gs) {
3180 if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
3181 glyph_pos = ge;
3182 edge = pos;
3183 dist = qAbs(x-pos);
3184 }
3185 pos += glyphs.effectiveAdvance(ge);
3186 --ge;
3187 }
3188 }
3189 } else {
3190 if (!visual || !rtl || (lastLine && i == 0)) {
3191 while (gs <= ge) {
3192 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3193 glyph_pos = gs;
3194 edge = pos;
3195 dist = qAbs(x-pos);
3196 }
3197 pos += glyphs.effectiveAdvance(gs);
3198 ++gs;
3199 }
3200 } else {
3201 QFixed oldPos = pos;
3202 while (gs <= ge) {
3203 pos += glyphs.effectiveAdvance(gs);
3204 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3205 glyph_pos = gs;
3206 edge = pos;
3207 dist = qAbs(x-pos);
3208 }
3209 ++gs;
3210 }
3211 pos = oldPos;
3212 }
3213 }
3214 if (qAbs(x-pos) < dist) {
3215 if (visual) {
3216 if (!rtl && i < nItems - 1) {
3217 nchars += end;
3218 continue;
3219 }
3220 if (rtl && nchars > 0)
3221 return insertionPoints[size_t(lastLine ? nchars : nchars - 1)];
3222 }
3223 return eng->positionInLigature(&si, end, x, pos, -1,
3225 }
3226 }
3227 Q_ASSERT(glyph_pos != -1);
3228 return eng->positionInLigature(&si, end, x, edge, glyph_pos,
3230 }
3231 }
3232 // right of last item
3233 int pos = line.from;
3234 if (!eng->isRightToLeft())
3235 pos += line_length;
3236
3237 // except for the last line we assume that the
3238 // character between lines is a space and we want
3239 // to position the cursor to the left of that
3240 // character.
3241 if (index < eng->lines.size() - 1)
3243
3244 return pos;
3245}
3246
virtual void drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int posInDocument, const QTextFormat &format)
This function is called to draw the inline object, object, with the given painter within the rectangl...
virtual void positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
Lays out the inline object item using the given text format.
\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
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
\inmodule QtCore
Definition qchar.h:48
@ VisualTabCharacter
Definition qchar.h:65
@ LineSeparator
Definition qchar.h:64
@ SoftHyphen
Definition qchar.h:58
@ NoBreak
Definition qchar.h:356
Decomposition decompositionTag() const noexcept
Returns the tag defining the composition of the character.
Definition qchar.h:445
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
void ensureEngineAt(int at)
QFontEngine * engine(int at) const
virtual QFixed descent() const
virtual qreal minRightBearing() const
virtual QFixed ascent() const
virtual QFixed averageCharWidth() const
Type type() const
void getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, QVarLengthArray< glyph_t > &glyphs_out, QVarLengthArray< QFixedPoint > &positions)
virtual QFixed lineThickness() const
virtual QFixed underlinePosition() 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:238
\reentrant
Definition qfont.h:20
bool strikeOut() const
Returns true if strikeout has been set; otherwise returns false.
Definition qfont.cpp:1289
bool underline() const
Returns true if underline has been set; otherwise returns false.
Definition qfont.cpp:1236
bool overline() const
Returns true if overline has been set; otherwise returns false.
Definition qfont.cpp:1263
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
QList< qsizetype > stringIndexes() const
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.
QRawFont rawFont() const
Returns the font selected for this QGlyphRun object.
GlyphRunFlags flags() const
Returns the flags set for this QGlyphRun.
@ 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.
QRectF boundingRect() const
Returns the smallest rectangle that contains all glyphs in this QGlyphRun.
QList< QPointF > positions() const
Returns the position of the edge of the baseline for each glyph in this set of glyph indexes.
\inmodule QtCore
Definition qhash.h:818
QList< T > values() const
Returns a list containing all the values in the hash, in an arbitrary order.
Definition qhash.h:1088
\inmodule QtCore
Definition qline.h:182
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
qsizetype length() const noexcept
Definition qlist.h:388
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
void reserve(qsizetype size)
Definition qlist.h:746
void append(parameter_type t)
Definition qlist.h:441
\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.
bool isEmpty() const
Returns true if either there are no elements in this path, or if the only element is a MoveToElement;...
static QPainterPrivate * get(QPainter *painter)
Definition qpainter_p.h:217
void drawTextItem(const QPointF &p, const QTextItem &_ti, QTextEngine *textEngine)
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:25
void setCosmetic(bool cosmetic)
Sets this pen to cosmetic or non-cosmetic, depending on the value of cosmetic.
Definition qpen.cpp:797
QBrush brush() const
Returns the brush used to fill strokes generated with this pen.
Definition qpen.cpp:741
\inmodule QtCore\reentrant
Definition qpoint.h:214
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:333
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:338
static QRawFontPrivate * get(const QRawFont &font)
Definition qrawfont_p.h:104
QFontEngine * fontEngine
Definition qrawfont_p.h:106
void setFontEngine(QFontEngine *engine)
Definition qrawfont_p.h:73
The QRawFont class provides access to a single physical instance of a font.
Definition qrawfont.h:24
bool isValid() const
Returns true if the QRawFont is valid and false otherwise.
Definition qrawfont.cpp:182
\inmodule QtCore\reentrant
Definition qrect.h:483
constexpr qreal bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:499
constexpr void setBottom(qreal pos) noexcept
Sets the bottom edge of the rectangle to the given finite y coordinate.
Definition qrect.h:670
constexpr void setRight(qreal pos) noexcept
Sets the right edge of the rectangle to the given finite x coordinate.
Definition qrect.h:664
constexpr qreal y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:658
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:718
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:748
constexpr QPointF bottomLeft() const noexcept
Returns the position of the rectangle's bottom-left corner.
Definition qrect.h:513
constexpr bool isNull() const noexcept
Returns true if the rectangle is a null rectangle, otherwise returns false.
Definition qrect.h:644
constexpr QPointF topLeft() const noexcept
Returns the position of the rectangle's top-left corner.
Definition qrect.h:510
constexpr QPointF bottomRight() const noexcept
Returns the position of the rectangle's bottom-right corner.
Definition qrect.h:511
constexpr void adjust(qreal x1, qreal y1, qreal x2, qreal y2) noexcept
Adds dx1, dy1, dx2 and dy2 respectively to the existing coordinates of the rectangle.
Definition qrect.h:791
constexpr void translate(qreal dx, qreal dy) noexcept
Moves the rectangle dx along the x-axis and dy along the y-axis, relative to the current position.
Definition qrect.h:724
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:497
constexpr bool isValid() const noexcept
Returns true if the rectangle is valid, otherwise returns false.
Definition qrect.h:652
constexpr QPointF topRight() const noexcept
Returns the position of the rectangle's top-right corner.
Definition qrect.h:512
QRectF intersected(const QRectF &other) const noexcept
Definition qrect.h:833
QRectF united(const QRectF &other) const noexcept
Definition qrect.h:838
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:498
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
bool isNull() const
Returns true if this string is null; otherwise returns false.
Definition qstring.h:898
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1079
QByteArray toUtf8() const &
Definition qstring.h:563
qsizetype length() const
Returns the number of characters in this string.
Definition qstring.h:187
const QChar * unicode() const
Returns a Unicode representation of the string.
Definition qstring.h:1085
\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.
void documentChange(int from, int length)
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
unsigned short * logClusters(const QScriptItem *si) const
bool isRightToLeft() const
void shapeLine(const QScriptLine &line)
QScriptLineArray lines
LayoutData * layoutData
QGlyphLayout shapedGlyphs(const QScriptItem *si) const
bool hasFormats() const
int preeditAreaPosition() const
int positionInLigature(const QScriptItem *si, int end, QFixed x, QFixed edge, int glyph_pos, bool cursorOnCharacter)
int findItem(int strPos, int firstItem=0) const
uint forceJustification
void shape(int item) const
void enableDelayDecorations(bool enable=true)
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
QFontEngine * fontEngine(const QScriptItem &si, QFixed *ascent=nullptr, QFixed *descent=nullptr, QFixed *leading=nullptr) const
void drawDecorations(QPainter *painter)
bool visualCursorMovement() const
QFixed calculateTabWidth(int index, QFixed x) const
returns the width of tab at index (in the tabs array) with the tab-start at position x
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
std::vector< int > insertionPointsForLine(int lineNum)
static void bidiReorder(int numRuns, const quint8 *levels, int *visualOrder)
int previousLogicalPosition(int oldPos) const
QFixed offsetInLigature(const QScriptItem *si, int pos, int max, int glyph_pos)
QFont font(const QScriptItem &si) const
void clearDecorations()
int length(int item) const
QTextFormatCollection * formatCollection() const
QAbstractTextDocumentLayout * docLayout() const
QTextCharFormat defaultTextFormat() const
\reentrant
Definition qtextformat.h:90
QBrush background() const
Returns the brush used to paint the document's background.
void merge(const QTextFormat &other)
Merges the other format with this format; where there are conflicts the other format takes precedence...
QVariant property(int propertyId) const
Returns the property specified by the given propertyId.
QBrush foreground() const
Returns the brush used to render foreground details, such as text, frame outlines,...
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
WrapMode wrapMode() const
Returns the text wrap mode defined by the option.
Definition qtextoption.h:68
Qt::Alignment alignment() const
Returns the text alignment defined by the option.
Definition qtextoption.h:55
void setWrapMode(WrapMode wrap)
Sets the option's text wrap mode to the given mode.
Definition qtextoption.h:67
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
constexpr size_type size() const noexcept
const T & at(qsizetype idx) const
T * data() noexcept
bool toBool() const
Returns the variant as a bool if the variant has userType() Bool.
QString text
double e
rect
[4]
uint alignment
EGLint EGLint * formats
typename C::iterator iterator
Combined button and popup list for selecting options.
@ AlignRight
Definition qnamespace.h:145
@ AlignJustify
Definition qnamespace.h:148
@ AlignCenter
Definition qnamespace.h:162
@ AlignLeft
Definition qnamespace.h:143
@ IntersectClip
LayoutDirection
@ LeftToRight
@ RightToLeft
@ TextJustificationForced
Definition qnamespace.h:178
@ TextForceRightToLeft
Definition qnamespace.h:180
@ TextForceLeftToRight
Definition qnamespace.h:179
@ SolidLine
@ NoPen
@ NoBrush
CursorMoveStyle
@ VisualMoveStyle
@ LogicalMoveStyle
@ WindingFill
@ FlatCap
#define Q_LIKELY(x)
std::pair< T1, T2 > QPair
static const QCssKnownValue positions[NumKnownPositionModes - 1]
#define QFIXED_MAX
Definition qfixed_p.h:127
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:303
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:281
#define qWarning
Definition qlogging.h:162
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
GLenum GLsizei len
GLsizei const GLchar *const * path
GLfloat GLfloat p
[1]
GLuint GLenum option
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)
#define SuppressBackground
#define LB_DEBUG
static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
#define ObjectSelectionBrush
unsigned int glyph_t
double qreal
Definition qtypes.h:92
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 base() const
QFixed height() const
signed int length
QFixed textAdvance
unsigned short trailingSpaces
unsigned short * logClustersPtr
QScriptItemArray items