@@ -70,18 +70,16 @@ namespace katvan {
70
70
* test logic).
71
71
*/
72
72
73
- constexpr int CURSOR_WIDTH = 1 ;
74
-
75
73
class LayoutBlockData : public QTextBlockUserData
76
74
{
77
75
public:
78
76
static constexpr BlockDataKind DATA_KIND = BlockDataKind::LAYOUT;
79
77
80
- LayoutBlockData () : revision(- 1 ) {}
78
+ LayoutBlockData () : textHash(qHash(QStringView()) ) {}
81
79
82
80
std::unique_ptr<QTextLayout> displayLayout;
83
81
QList<ushort > displayOffsets;
84
- int revision ;
82
+ size_t textHash ;
85
83
};
86
84
87
85
static int adjustPosToDisplay (const QList<ushort >& displayOffsets, int pos)
@@ -112,6 +110,7 @@ static int adjustPosFromDisplay(const QList<ushort>& displayOffsets, int pos)
112
110
EditorLayout::EditorLayout (QTextDocument* document, CodeModel* codeModel)
113
111
: QAbstractTextDocumentLayout(document)
114
112
, d_codeModel(codeModel)
113
+ , d_cursorWidth(1 )
115
114
, d_documentSize(0 , 0 )
116
115
{
117
116
}
@@ -204,7 +203,7 @@ void EditorLayout::draw(QPainter* painter, const QAbstractTextDocumentLayout::Pa
204
203
205
204
// Make sure that the right margin is NOT part of the clip rectangle, so
206
205
// that it won't be painted over by a FullWidthSelection
207
- qreal maxX = document ()->textWidth () - document ()->documentMargin () + CURSOR_WIDTH ;
206
+ qreal maxX = document ()->textWidth () - document ()->documentMargin () + d_cursorWidth ;
208
207
clip.setRight (qMin (clip.right (), maxX));
209
208
210
209
QTextBlock block = findContainingBlock (clip.top ());
@@ -266,7 +265,7 @@ void EditorLayout::draw(QPainter* painter, const QAbstractTextDocumentLayout::Pa
266
265
layout->drawCursor (painter,
267
266
QPointF (),
268
267
adjustPosToDisplay (layoutData->displayOffsets , cursorPosInBlock),
269
- CURSOR_WIDTH );
268
+ d_cursorWidth );
270
269
}
271
270
272
271
block = block.next ();
@@ -336,10 +335,17 @@ void EditorLayout::documentChanged(int position, int charsRemoved, int charsAdde
336
335
337
336
static void buildDisplayLayout (const QTextBlock& block, LayoutBlockData* blockData)
338
337
{
339
- blockData->revision = block.revision ();
338
+ QString text = block.text ();
339
+ size_t newHash = qHash (text);
340
+
341
+ // Calculating a display layout is expensive, only do it if content actually changed
342
+ if (newHash == blockData->textHash ) {
343
+ return ;
344
+ }
345
+ blockData->textHash = newHash;
340
346
341
347
IsolatesBlockData* isolateData = BlockData::get<IsolatesBlockData>(block);
342
- if (isolateData == nullptr /* || isolateData->isolates().isEmpty()*/ ) {
348
+ if (isolateData == nullptr || isolateData->isolates ().isEmpty ()) {
343
349
blockData->displayOffsets .clear ();
344
350
blockData->displayLayout .reset ();
345
351
return ;
@@ -354,12 +360,14 @@ static void buildDisplayLayout(const QTextBlock& block, LayoutBlockData* blockDa
354
360
// not edit purposes. This moves around positions of visible characters
355
361
// relative to the editable block content stored in the QTextDocument, so
356
362
// we need to save an offset mapping.
357
- QString text = block.text ();
358
363
359
364
auto & offsets = blockData->displayOffsets ;
360
365
offsets.resize (text.size ());
361
366
offsets.fill (0 );
362
367
368
+ QMap<int , int > isolatesStartingAt;
369
+ QMap<int , int > isolatesEndingAt;
370
+
363
371
for (const auto & isolate : isolates) {
364
372
QChar startChar;
365
373
switch (isolate.dir ) {
@@ -380,6 +388,9 @@ static void buildDisplayLayout(const QTextBlock& block, LayoutBlockData* blockDa
380
388
for (int i = end + 1 ; i < offsets.length (); i++) {
381
389
offsets[i] += 2 ;
382
390
}
391
+
392
+ isolatesStartingAt[start]++;
393
+ isolatesEndingAt[end]++;
383
394
}
384
395
385
396
// Patch format ranges to be be correct for adjusted text
@@ -389,6 +400,32 @@ static void buildDisplayLayout(const QTextBlock& block, LayoutBlockData* blockDa
389
400
r.start += startOffset;
390
401
}
391
402
403
+ // The Qt method QWidgetTextControlPrivate::rectForPosition directly
404
+ // uses the QTextLine objects from the default layout to determine cursor
405
+ // and selection rectangles position and height. It is essential that
406
+ // our display layout's lines have the same bounding rectangles - otherwise
407
+ // there are various visual glitches related to mouse selection.
408
+ //
409
+ // In theory, we only add invisible control characters which shouldn't affect
410
+ // text size, BUT - due to them being control chars most fonts will (rightfully)
411
+ // claim to not cover them. We must prevent Qt from falling back to another font
412
+ // that might claim coverage, and has a bigger ascent or descent height than
413
+ // any font used for visible characters. To achieve that, add a format
414
+ // for each control char we injected that prevents font merging.
415
+ QTextLayout::FormatRange r;
416
+ r.format .setFontStyleStrategy (QFont::NoFontMerging);
417
+
418
+ for (const auto & [pos, count] : isolatesStartingAt.asKeyValueRange ()) {
419
+ r.start = pos + offsets[pos] - count;
420
+ r.length = count;
421
+ formats.append (r);
422
+ }
423
+ for (const auto & [pos, count] : isolatesEndingAt.asKeyValueRange ()) {
424
+ r.start = pos + offsets[pos] + 1 ;
425
+ r.length = count;
426
+ formats.append (r);
427
+ }
428
+
392
429
blockData->displayLayout = std::make_unique<QTextLayout>(text);
393
430
blockData->displayLayout ->setFormats (formats);
394
431
}
@@ -411,17 +448,19 @@ void EditorLayout::layoutBlock(QTextBlock& block, qreal topY)
411
448
doBlockLayout (defaultLayout, option, topY);
412
449
block.setLineCount (defaultLayout->lineCount ());
413
450
414
- // Calculating a display layout is expensive, only do it if content actually changed
415
- if (block.revision () != blockData->revision || block.revision () == 1 ) {
416
- buildDisplayLayout (block, blockData);
417
- }
451
+ buildDisplayLayout (block, blockData);
418
452
419
453
QTextLayout* displayLayout = blockData->displayLayout .get ();
420
454
if (displayLayout != nullptr ) {
421
455
displayLayout->setFont (document ()->defaultFont ());
422
456
displayLayout->setCursorMoveStyle (document ()->defaultCursorMoveStyle ());
423
457
424
458
doBlockLayout (displayLayout, option, topY);
459
+
460
+ if (displayLayout->boundingRect () != defaultLayout->boundingRect ()) {
461
+ qWarning () << " Block" << block.blockNumber () << " display bounding rect differs from default one!"
462
+ << displayLayout->boundingRect () << " vs" << defaultLayout->boundingRect ();
463
+ }
425
464
}
426
465
}
427
466
0 commit comments