Skip to content

Commit 08422c6

Browse files
committed
Fix visual glitches with injected isolates
1 parent def0e8e commit 08422c6

File tree

4 files changed

+60
-16
lines changed

4 files changed

+60
-16
lines changed

core/katvan_editor.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ QMenu* Editor::createInsertMenu()
206206
menu->addSeparator();
207207

208208
QAction* insertInlineMathAction = menu->addAction(tr("Inline &Math"), this, [this]() {
209-
insertSurroundingMarks(utils::LRI_MARK + QStringLiteral("$"), QStringLiteral("$") + utils::PDI_MARK);
209+
insertSurroundingMarks(QStringLiteral("$"), QStringLiteral("$"));
210210
});
211211
insertInlineMathAction->setIcon(utils::fontIcon(QLatin1Char('$')));
212212
insertInlineMathAction->setShortcut(Qt::CTRL | Qt::Key_M);

core/katvan_editorlayout.cpp

+52-13
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,16 @@ namespace katvan {
7070
* test logic).
7171
*/
7272

73-
constexpr int CURSOR_WIDTH = 1;
74-
7573
class LayoutBlockData : public QTextBlockUserData
7674
{
7775
public:
7876
static constexpr BlockDataKind DATA_KIND = BlockDataKind::LAYOUT;
7977

80-
LayoutBlockData() : revision(-1) {}
78+
LayoutBlockData() : textHash(qHash(QStringView())) {}
8179

8280
std::unique_ptr<QTextLayout> displayLayout;
8381
QList<ushort> displayOffsets;
84-
int revision;
82+
size_t textHash;
8583
};
8684

8785
static int adjustPosToDisplay(const QList<ushort>& displayOffsets, int pos)
@@ -112,6 +110,7 @@ static int adjustPosFromDisplay(const QList<ushort>& displayOffsets, int pos)
112110
EditorLayout::EditorLayout(QTextDocument* document, CodeModel* codeModel)
113111
: QAbstractTextDocumentLayout(document)
114112
, d_codeModel(codeModel)
113+
, d_cursorWidth(1)
115114
, d_documentSize(0, 0)
116115
{
117116
}
@@ -204,7 +203,7 @@ void EditorLayout::draw(QPainter* painter, const QAbstractTextDocumentLayout::Pa
204203

205204
// Make sure that the right margin is NOT part of the clip rectangle, so
206205
// 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;
208207
clip.setRight(qMin(clip.right(), maxX));
209208

210209
QTextBlock block = findContainingBlock(clip.top());
@@ -266,7 +265,7 @@ void EditorLayout::draw(QPainter* painter, const QAbstractTextDocumentLayout::Pa
266265
layout->drawCursor(painter,
267266
QPointF(),
268267
adjustPosToDisplay(layoutData->displayOffsets, cursorPosInBlock),
269-
CURSOR_WIDTH);
268+
d_cursorWidth);
270269
}
271270

272271
block = block.next();
@@ -336,10 +335,17 @@ void EditorLayout::documentChanged(int position, int charsRemoved, int charsAdde
336335

337336
static void buildDisplayLayout(const QTextBlock& block, LayoutBlockData* blockData)
338337
{
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;
340346

341347
IsolatesBlockData* isolateData = BlockData::get<IsolatesBlockData>(block);
342-
if (isolateData == nullptr /*|| isolateData->isolates().isEmpty()*/) {
348+
if (isolateData == nullptr || isolateData->isolates().isEmpty()) {
343349
blockData->displayOffsets.clear();
344350
blockData->displayLayout.reset();
345351
return;
@@ -354,12 +360,14 @@ static void buildDisplayLayout(const QTextBlock& block, LayoutBlockData* blockDa
354360
// not edit purposes. This moves around positions of visible characters
355361
// relative to the editable block content stored in the QTextDocument, so
356362
// we need to save an offset mapping.
357-
QString text = block.text();
358363

359364
auto& offsets = blockData->displayOffsets;
360365
offsets.resize(text.size());
361366
offsets.fill(0);
362367

368+
QMap<int, int> isolatesStartingAt;
369+
QMap<int, int> isolatesEndingAt;
370+
363371
for (const auto& isolate : isolates) {
364372
QChar startChar;
365373
switch (isolate.dir) {
@@ -380,6 +388,9 @@ static void buildDisplayLayout(const QTextBlock& block, LayoutBlockData* blockDa
380388
for (int i = end + 1; i < offsets.length(); i++) {
381389
offsets[i] += 2;
382390
}
391+
392+
isolatesStartingAt[start]++;
393+
isolatesEndingAt[end]++;
383394
}
384395

385396
// Patch format ranges to be be correct for adjusted text
@@ -389,6 +400,32 @@ static void buildDisplayLayout(const QTextBlock& block, LayoutBlockData* blockDa
389400
r.start += startOffset;
390401
}
391402

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+
392429
blockData->displayLayout = std::make_unique<QTextLayout>(text);
393430
blockData->displayLayout->setFormats(formats);
394431
}
@@ -411,17 +448,19 @@ void EditorLayout::layoutBlock(QTextBlock& block, qreal topY)
411448
doBlockLayout(defaultLayout, option, topY);
412449
block.setLineCount(defaultLayout->lineCount());
413450

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);
418452

419453
QTextLayout* displayLayout = blockData->displayLayout.get();
420454
if (displayLayout != nullptr) {
421455
displayLayout->setFont(document()->defaultFont());
422456
displayLayout->setCursorMoveStyle(document()->defaultCursorMoveStyle());
423457

424458
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+
}
425464
}
426465
}
427466

core/katvan_editorlayout.h

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ class EditorLayout : public QAbstractTextDocumentLayout
2828
{
2929
Q_OBJECT
3030

31+
// Undocumented property that QTextEdit (actually QWidgetTextControlPrivate)
32+
// wants to exist on document layouts
33+
Q_PROPERTY(int cursorWidth MEMBER d_cursorWidth)
34+
3135
public:
3236
EditorLayout(QTextDocument* document, CodeModel* codeModel);
3337

@@ -51,6 +55,7 @@ class EditorLayout : public QAbstractTextDocumentLayout
5155

5256
CodeModel* d_codeModel;
5357

58+
int d_cursorWidth;
5459
QSizeF d_documentSize;
5560
};
5661

demo.typ

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#set heading(numbering: "1.")
55

66
= סדרת פיבונאצ'י
7-
סדרת פיבונאצ'י מוגדרת באמצעות נוסחת הנסיגה $F_n = F_(n-1) + F_(n+2)$. ניתן גם לייצג אותה באמצעות _הנוסחא הסגורה_:
7+
סדרת פיבונאצ'י מוגדרת באמצעות נוסחת הנסיגה $F_n = F_(n-1) + F_(n+2)$. ניתן ג ם לייצג אותה באמצעות _הנוסחא הסגורה_:
88

99
$ F_n = round(1 / sqrt(5) phi.alt^n), quad phi.alt = (1 + sqrt(5)) / 2 $
1010

@@ -15,7 +15,7 @@ $ F_n = round(1 / sqrt(5) phi.alt^n), quad phi.alt = (1 + sqrt(5)) / 2 $
1515
else { fib(n - 1) + fib(n - 2) }
1616
)
1717

18-
#count המספרים הראשונים בסדרה הם:
18+
#count המספרים הראשונים בסדרה הם:
1919

2020
#text(dir: ltr,
2121
align(center,

0 commit comments

Comments
 (0)