Skip to content

Commit 1230e8a

Browse files
authored
Merge pull request edbee#131 from edbee/feature/128-flexible-textlayout
Feature/128 flexible textlayout
2 parents 1f7af0d + 9515613 commit 1230e8a

11 files changed

Lines changed: 319 additions & 47 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,5 @@ edbee-lib/edbee-lib_autogen/
4444
edbee-lib/qslog/
4545
edbee-test/edbee-test_autogen/
4646
edbee-test/edbee-lib/qslog/
47+
48+
build/

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
edbee.lib:
44

5+
- ref #128, Build in supprort for virtual characters. Alternate position/cursor calculations via TextLayout
56
- ref #127,
67
- Option to show Unicode BIDI characters (CVE-2021-425740). (as red icons for now)
78
- It is configurable via 'TexteditorConfig::renderBidiContolCharacters', and defaults to true

edbee-lib/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ SET(SOURCES
8787
edbee/views/components/textmargincomponent.cpp
8888
edbee/views/textcaretcache.cpp
8989
edbee/views/texteditorscrollarea.cpp
90+
edbee/views/textlayout.cpp
9091
edbee/views/textrenderer.cpp
9192
edbee/views/textselection.cpp
9293
edbee/views/texttheme.cpp
@@ -168,6 +169,7 @@ SET(HEADERS
168169
edbee/views/components/textmargincomponent.h
169170
edbee/views/textcaretcache.h
170171
edbee/views/texteditorscrollarea.h
172+
edbee/views/textlayout.h
171173
edbee/views/textrenderer.h
172174
edbee/views/textselection.h
173175
edbee/views/texttheme.h

edbee-lib/edbee-lib.pri

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ SOURCES += \
7777
$$PWD/edbee/views/components/textmargincomponent.cpp \
7878
$$PWD/edbee/views/textcaretcache.cpp \
7979
$$PWD/edbee/views/texteditorscrollarea.cpp \
80+
$$PWD/edbee/views/textlayout.cpp \
8081
$$PWD/edbee/views/textrenderer.cpp \
8182
$$PWD/edbee/views/textselection.cpp \
8283
$$PWD/edbee/views/texttheme.cpp
@@ -158,6 +159,7 @@ HEADERS += \
158159
$$PWD/edbee/views/components/textmargincomponent.h \
159160
$$PWD/edbee/views/textcaretcache.h \
160161
$$PWD/edbee/views/texteditorscrollarea.h \
162+
$$PWD/edbee/views/textlayout.h \
161163
$$PWD/edbee/views/textrenderer.h \
162164
$$PWD/edbee/views/textselection.h \
163165
$$PWD/edbee/views/texttheme.h

edbee-lib/edbee/models/textrange.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,12 @@ bool TextRange::touches(TextRange& range)
422422
return( max1 == min2 || max2 == min1 );
423423
}
424424

425+
/// checks if the given position is in this textrange
426+
bool TextRange::contains(int pos)
427+
{
428+
return min() <= pos && pos < max();
429+
}
430+
425431

426432

427433
//=========================================================================

edbee-lib/edbee/models/textrange.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class EDBEE_EXPORT TextRange {
8787

8888
bool equals(const TextRange &range );
8989
bool touches( TextRange& range );
90+
bool contains( int pos );
9091

9192
static bool lessThan( TextRange& r1, TextRange& r2 );
9293

edbee-lib/edbee/views/components/texteditorrenderer.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "edbee/views/texttheme.h"
1616
#include "edbee/views/textselection.h"
1717
#include "edbee/views/textrenderer.h"
18+
#include "edbee/views/textlayout.h"
1819
#include "edbee/util/simpleprofiler.h"
1920

2021
#include "edbee/debug.h"
@@ -92,9 +93,8 @@ void TextEditorRenderer::renderLineSelection(QPainter *painter,int line)
9293
/// TODO: iprove ranges at line by calling rangesForOffsets first for only the visible offsets!
9394
if( sel->rangesAtLine( line, firstRangeIdx, lastRangeIdx ) ) {
9495

95-
QTextLayout* textLayout = renderer()->textLayoutForLine(line);
96+
TextLayout* textLayout = renderer()->textLayoutForLine(line);
9697
QRectF rect = textLayout->boundingRect();
97-
QTextLine textLine = textLayout->lineAt(0);
9898

9999
int lastLineColumn = doc->lineLength(line);
100100

@@ -104,8 +104,8 @@ void TextEditorRenderer::renderLineSelection(QPainter *painter,int line)
104104
int startColumn = doc->columnFromOffsetAndLine( range.min(), line );
105105
int endColumn = doc->columnFromOffsetAndLine( range.max(), line );
106106

107-
int startX = textLine.cursorToX( startColumn );
108-
int endX = textLine.cursorToX( endColumn );
107+
int startX = textLayout->cursorToX(startColumn);
108+
int endX = textLayout->cursorToX(endColumn);
109109

110110
if( range.length() > 0 && endColumn+1 >= lastLineColumn) endX += 3;
111111

@@ -133,9 +133,8 @@ void TextEditorRenderer::renderLineBorderedRanges(QPainter *painter,int line)
133133
/// TODO: improve ranges at line by calling rangesForOffsets first for only the visible offsets!
134134
if( sel->rangesAtLine( line, firstRangeIdx, lastRangeIdx ) ) {
135135

136-
QTextLayout* textLayout = renderer()->textLayoutForLine(line);
136+
TextLayout* textLayout = renderer()->textLayoutForLine(line);
137137
QRectF rect = textLayout->boundingRect();
138-
QTextLine textLine = textLayout->lineAt(0);
139138

140139
int lastLineColumn = doc->lineLength(line);
141140

@@ -145,8 +144,8 @@ void TextEditorRenderer::renderLineBorderedRanges(QPainter *painter,int line)
145144
int startColumn = doc->columnFromOffsetAndLine( range.min(), line );
146145
int endColumn = doc->columnFromOffsetAndLine( range.max(), line );
147146

148-
qreal startX = textLine.cursorToX( startColumn );
149-
qreal endX = textLine.cursorToX( endColumn );
147+
qreal startX = textLayout->cursorToX( startColumn );
148+
qreal endX = textLayout->cursorToX( endColumn );
150149

151150
if( range.length() > 0 && endColumn+1 >= lastLineColumn) endX += 3;
152151

@@ -184,7 +183,7 @@ void TextEditorRenderer::renderLineSeparator(QPainter *painter,int line)
184183
void TextEditorRenderer::renderLineText(QPainter *painter, int line)
185184
{
186185
//PROF_BEGIN_NAMED("render-line-text")
187-
QTextLayout* textLayout = renderer()->textLayoutForLine(line);
186+
TextLayout* textLayout = renderer()->textLayoutForLine(line);
188187

189188
// draw the text layout
190189
QPoint lineStartPos(0, line*renderer()->lineHeight() );
@@ -226,8 +225,9 @@ void TextEditorRenderer::renderCarets(QPainter *painter)
226225

227226
QPoint lineStartPos(0, renderer()->yPosForLine(line));
228227

229-
QTextLayout* textLayout = renderer()->textLayoutForLine(line);
228+
TextLayout* textLayout = renderer()->textLayoutForLine(line);
230229
for( int caret = 0, rangeCount=sel->rangeCount(); caret < rangeCount; ++caret ) {
230+
231231
TextRange& range = sel->range(caret);
232232
int caretLine = doc->lineFromOffset( range.caret() );
233233
if( caretLine == line ) {
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#include "textlayout.h"
2+
3+
#include <QTextLayout>
4+
5+
#include "edbee/debug.h"
6+
7+
namespace edbee {
8+
9+
10+
//=================================================
11+
12+
TextLayout::TextLayout(TextDocument* document)
13+
: qtextLayout_( new QTextLayout())
14+
, textDocumentRef_(document)
15+
, singleCharRanges_(nullptr)
16+
{
17+
}
18+
19+
TextLayout::~TextLayout()
20+
{
21+
delete singleCharRanges_;
22+
delete qtextLayout_;
23+
}
24+
25+
void TextLayout::setCacheEnabled(bool enable)
26+
{
27+
qtextLayout_->setCacheEnabled(enable);
28+
}
29+
30+
QTextLayout *TextLayout::qTextLayout() const
31+
{
32+
return qtextLayout_;
33+
}
34+
35+
QRectF TextLayout::boundingRect() const
36+
{
37+
return qtextLayout_->boundingRect();
38+
}
39+
40+
void TextLayout::buildLayout()
41+
{
42+
qtextLayout_->beginLayout();
43+
qtextLine_ = qtextLayout_->createLine();
44+
qtextLayout_->endLayout();
45+
46+
}
47+
48+
/// Converts the document cursorPosition to a virtual cursorposition
49+
int TextLayout::toVirtualCursorPosition(int cursorPos) const
50+
{
51+
int delta = 0;
52+
// convert cursor to a valid location
53+
TextRangeSet* ranges = singleCharRanges();
54+
if(ranges) {
55+
for(int i=0, cnt = ranges->rangeCount(); i < cnt; i++ ) {
56+
TextRange& range = ranges->range(i);
57+
if( cursorPos + delta > range.min()) {
58+
delta += range.length() - 1;
59+
}
60+
}
61+
}
62+
return cursorPos + delta;
63+
}
64+
65+
/// Converts the virtual cursorPosition to a docuemnt cursorposition
66+
int TextLayout::fromVirtualCursorPosition(int cursor) const
67+
{
68+
// when the cursor falls in a single-character range.
69+
// Set the cursor to the start of this range
70+
int delta = 0;
71+
72+
TextRangeSet* ranges = singleCharRanges();
73+
if(ranges) {
74+
for(int i=0, cnt = ranges->rangeCount(); i < cnt; i++ ) {
75+
TextRange& range = ranges->range(i);
76+
if(range.min() <= cursor && cursor < range.max() ) {
77+
delta += cursor - range.min();
78+
} else if( range.max() <= cursor ) {
79+
delta += range.length() - 1;
80+
}
81+
}
82+
}
83+
return cursor - delta;
84+
}
85+
86+
void TextLayout::draw(QPainter *p, const QPointF &pos, const QVector<QTextLayout::FormatRange> &selections, const QRectF &clip) const
87+
{
88+
qtextLayout_->draw(p, pos, selections, clip);
89+
}
90+
91+
void TextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
92+
{
93+
int virtualCursorPosition = toVirtualCursorPosition(cursorPosition);
94+
qtextLayout_->drawCursor(painter, position, virtualCursorPosition, width);
95+
}
96+
97+
void TextLayout::setFormats(const QVector<QTextLayout::FormatRange> &formats)
98+
{
99+
qtextLayout_->setFormats(formats);
100+
}
101+
102+
void TextLayout::setText(const QString &string)
103+
{
104+
qtextLayout_->setText(string);
105+
}
106+
107+
void TextLayout::useSingleCharRanges()
108+
{
109+
if(!singleCharRanges_) {
110+
singleCharRanges_ = new TextRangeSet(textDocumentRef_);
111+
}
112+
}
113+
114+
TextRangeSet *TextLayout::singleCharRanges() const
115+
{
116+
return singleCharRanges_;
117+
}
118+
119+
void TextLayout::addSingleCharRange(int index, int length)
120+
{
121+
useSingleCharRanges();
122+
singleCharRanges()->addRange(index, index+length);
123+
}
124+
125+
126+
127+
qreal TextLayout::cursorToX(int cursorPos, QTextLine::Edge edge) const
128+
{
129+
int virtualCursorPos = toVirtualCursorPosition(cursorPos);
130+
131+
qreal x = qtextLine_.cursorToX(virtualCursorPos, edge);
132+
return x;
133+
}
134+
135+
int TextLayout::xToCursor(qreal x, QTextLine::CursorPosition cpos) const
136+
{
137+
int virtualCursor = qtextLine_.xToCursor(x, cpos);
138+
return fromVirtualCursorPosition(virtualCursor);
139+
}
140+
141+
142+
//=================================================
143+
144+
TextLayoutBuilder::TextLayoutBuilder(TextLayout *textLayout, QString & baseString, QVector<QTextLayout::FormatRange> & baseFormatRanges)
145+
: textLayoutRef_(textLayout)
146+
, baseString_(baseString)
147+
, baseFormatRanges_(baseFormatRanges)
148+
{
149+
150+
}
151+
152+
void TextLayoutBuilder::replace(int index, int length, const QString replacement, QTextCharFormat format)
153+
{
154+
baseString_.replace(index, length, replacement);
155+
textLayoutRef_->addSingleCharRange(index, replacement.length()); /// TODO: Should we store the original length!?!?!?
156+
157+
// change existing format ranges:
158+
int delta = replacement.length() - length;
159+
if( delta != 0 ) {
160+
for(int i=0, cnt = baseFormatRanges_.length(); i < cnt; i++ ) {
161+
QTextLayout::FormatRange& formatRange = baseFormatRanges_[i];
162+
if( formatRange.start >= index ) {
163+
formatRange.start += delta;
164+
}
165+
}
166+
}
167+
168+
169+
// append the text format
170+
QTextLayout::FormatRange formatRange;
171+
formatRange.format = format;
172+
formatRange.start = index;
173+
formatRange.length = replacement.length();
174+
baseFormatRanges_.append(formatRange);
175+
}
176+
177+
} // edbee
178+
179+

0 commit comments

Comments
 (0)