Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
editor.cpp
Go to the documentation of this file.
1// Copyright 2019 Google LLC.
2// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3
5
9#include "src/base/SkUTF.h"
10
12
13#include <algorithm>
14#include <cfloat>
15
16using namespace SkPlainTextEditor;
17
18static inline SkRect offset(SkRect r, SkIPoint p) {
19 return r.makeOffset((float)p.x(), (float)p.y());
20}
21
22static constexpr SkRect kUnsetRect{-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX};
23
24static bool valid_utf8(const char* ptr, size_t size) { return SkUTF::CountUTF8(ptr, size) >= 0; }
25
26// Kind of like Python's readlines(), but without any allocation.
27// Calls f() on each line.
28// F is [](const char*, size_t) -> void
29template <typename F>
30static void readlines(const void* data, size_t size, F f) {
31 const char* start = (const char*)data;
32 const char* end = start + size;
33 const char* ptr = start;
34 while (ptr < end) {
35 while (*ptr++ != '\n' && ptr < end) {}
36 size_t len = ptr - start;
37 SkASSERT(len > 0);
38 f(start, len);
39 start = ptr;
40 }
41}
42
43static StringSlice remove_newline(const char* str, size_t len) {
44 return SkASSERT((str != nullptr) || (len == 0)),
45 StringSlice(str, (len > 0 && str[len - 1] == '\n') ? len - 1 : len);
46}
47
48void Editor::markDirty(TextLine* line) {
49 line->fBlob = nullptr;
50 line->fShaped = false;
51 line->fWordBoundaries = std::vector<bool>();
52}
53
55 if (font != fFont) {
56 fFont = std::move(font);
57 fNeedsReshape = true;
58 for (auto& l : fLines) { this->markDirty(&l); }
59 }
60}
61
63 fFontMgr = fontMgr;
64 fNeedsReshape = true;
65 for (auto& l : fLines) { this->markDirty(&l); }
66}
67
69 if (fWidth != w) {
70 fWidth = w;
71 fNeedsReshape = true;
72 for (auto& l : fLines) { this->markDirty(&l); }
73 }
74}
75static SkPoint to_point(SkIPoint p) { return {(float)p.x(), (float)p.y()}; }
76
78 Editor::TextPosition approximatePosition;
79 this->reshapeAll();
80 for (size_t j = 0; j < fLines.size(); ++j) {
81 const TextLine& line = fLines[j];
82 SkIRect lineRect = {0,
83 line.fOrigin.y(),
84 fWidth,
85 j + 1 < fLines.size() ? fLines[j + 1].fOrigin.y() : INT_MAX};
86 if (const SkTextBlob* b = line.fBlob.get()) {
87 SkIRect r = b->bounds().roundOut();
88 r.offset(line.fOrigin);
89 lineRect.join(r);
90 }
91 if (!lineRect.contains(xy.x(), xy.y())) {
92 continue;
93 }
94 SkPoint pt = to_point(xy - line.fOrigin);
95 const std::vector<SkRect>& pos = line.fCursorPos;
96 for (size_t i = 0; i < pos.size(); ++i) {
97 if (pos[i] != kUnsetRect && pos[i].contains(pt.x(), pt.y())) {
98 return Editor::TextPosition{i, j};
99 }
100 }
101 approximatePosition = {xy.x() <= line.fOrigin.x() ? 0 : line.fText.size(), j};
102 }
103 return approximatePosition;
104}
105
106static inline bool is_utf8_continuation(char v) {
107 return ((unsigned char)v & 0b11000000) ==
108 0b10000000;
109}
110
111static const char* next_utf8(const char* p, const char* end) {
112 if (p < end) {
113 do {
114 ++p;
115 } while (p < end && is_utf8_continuation(*p));
116 }
117 return p;
118}
119
120static const char* align_utf8(const char* p, const char* begin) {
121 while (p > begin && is_utf8_continuation(*p)) {
122 --p;
123 }
124 return p;
125}
126
127static const char* prev_utf8(const char* p, const char* begin) {
128 return p > begin ? align_utf8(p - 1, begin) : begin;
129}
130
132 this->reshapeAll();
133 cursor = this->move(Editor::Movement::kNowhere, cursor);
134 if (fLines.size() > 0) {
135 const TextLine& cLine = fLines[cursor.fParagraphIndex];
136 SkRect pos = {0, 0, 0, 0};
137 if (cursor.fTextByteIndex < cLine.fCursorPos.size()) {
138 pos = cLine.fCursorPos[cursor.fTextByteIndex];
139 }
140 pos.fRight = pos.fLeft + 1;
141 pos.fLeft -= 1;
142 return offset(pos, cLine.fOrigin);
143 }
144 return SkRect{0, 0, 0, 0};
145}
146
147static size_t count_char(const StringSlice& string, char value) {
148 size_t count = 0;
149 for (char c : string) { if (c == value) { ++count; } }
150 return count;
151}
152
153Editor::TextPosition Editor::insert(TextPosition pos, const char* utf8Text, size_t byteLen) {
154 if (!valid_utf8(utf8Text, byteLen) || 0 == byteLen) {
155 return pos;
156 }
157 pos = this->move(Editor::Movement::kNowhere, pos);
158 fNeedsReshape = true;
159 if (pos.fParagraphIndex < fLines.size()) {
160 fLines[pos.fParagraphIndex].fText.insert(pos.fTextByteIndex, utf8Text, byteLen);
161 this->markDirty(&fLines[pos.fParagraphIndex]);
162 } else {
163 SkASSERT(pos.fParagraphIndex == fLines.size());
164 SkASSERT(pos.fTextByteIndex == 0);
165 fLines.push_back(Editor::TextLine(StringSlice(utf8Text, byteLen)));
166 }
167 pos = Editor::TextPosition{pos.fTextByteIndex + byteLen, pos.fParagraphIndex};
168 size_t newlinecount = count_char(fLines[pos.fParagraphIndex].fText, '\n');
169 if (newlinecount > 0) {
170 StringSlice src = std::move(fLines[pos.fParagraphIndex].fText);
171 std::vector<TextLine>::const_iterator next = fLines.begin() + pos.fParagraphIndex + 1;
172 fLines.insert(next, newlinecount, TextLine());
173 TextLine* line = &fLines[pos.fParagraphIndex];
174 readlines(src.begin(), src.size(), [&line](const char* str, size_t l) {
175 (line++)->fText = remove_newline(str, l);
176 });
177 }
178 return pos;
179}
180
182 pos1 = this->move(Editor::Movement::kNowhere, pos1);
183 pos2 = this->move(Editor::Movement::kNowhere, pos2);
184 auto cmp = [](const Editor::TextPosition& u, const Editor::TextPosition& v) { return u < v; };
185 Editor::TextPosition start = std::min(pos1, pos2, cmp);
186 Editor::TextPosition end = std::max(pos1, pos2, cmp);
187 if (start == end || start.fParagraphIndex == fLines.size()) {
188 return start;
189 }
190 fNeedsReshape = true;
191 if (start.fParagraphIndex == end.fParagraphIndex) {
192 SkASSERT(end.fTextByteIndex > start.fTextByteIndex);
193 fLines[start.fParagraphIndex].fText.remove(
194 start.fTextByteIndex, end.fTextByteIndex - start.fTextByteIndex);
195 this->markDirty(&fLines[start.fParagraphIndex]);
196 } else {
197 SkASSERT(end.fParagraphIndex < fLines.size());
198 auto& line = fLines[start.fParagraphIndex];
199 line.fText.remove(start.fTextByteIndex,
200 line.fText.size() - start.fTextByteIndex);
201 line.fText.insert(start.fTextByteIndex,
202 fLines[end.fParagraphIndex].fText.begin() + end.fTextByteIndex,
203 fLines[end.fParagraphIndex].fText.size() - end.fTextByteIndex);
204 this->markDirty(&line);
205 fLines.erase(fLines.begin() + start.fParagraphIndex + 1,
206 fLines.begin() + end.fParagraphIndex + 1);
207 }
208 return start;
209}
210
211static void append(char** dst, size_t* count, const char* src, size_t n) {
212 if (*dst) {
213 ::memcpy(*dst, src, n);
214 *dst += n;
215 }
216 *count += n;
217}
218
219size_t Editor::copy(TextPosition pos1, TextPosition pos2, char* dst) const {
220 size_t size = 0;
221 pos1 = this->move(Editor::Movement::kNowhere, pos1);
222 pos2 = this->move(Editor::Movement::kNowhere, pos2);
223 auto cmp = [](const Editor::TextPosition& u, const Editor::TextPosition& v) { return u < v; };
224 Editor::TextPosition start = std::min(pos1, pos2, cmp);
225 Editor::TextPosition end = std::max(pos1, pos2, cmp);
226 if (start == end || start.fParagraphIndex == fLines.size()) {
227 return size;
228 }
229 if (start.fParagraphIndex == end.fParagraphIndex) {
230 SkASSERT(end.fTextByteIndex > start.fTextByteIndex);
231 auto& str = fLines[start.fParagraphIndex].fText;
232 append(&dst, &size, str.begin() + start.fTextByteIndex,
233 end.fTextByteIndex - start.fTextByteIndex);
234 return size;
235 }
236 SkASSERT(end.fParagraphIndex < fLines.size());
237 const std::vector<TextLine>::const_iterator firstP = fLines.begin() + start.fParagraphIndex;
238 const std::vector<TextLine>::const_iterator lastP = fLines.begin() + end.fParagraphIndex;
239 const auto& first = firstP->fText;
240 const auto& last = lastP->fText;
241
242 append(&dst, &size, first.begin() + start.fTextByteIndex, first.size() - start.fTextByteIndex);
243 for (auto line = firstP + 1; line < lastP; ++line) {
244 append(&dst, &size, "\n", 1);
245 append(&dst, &size, line->fText.begin(), line->fText.size());
246 }
247 append(&dst, &size, "\n", 1);
248 append(&dst, &size, last.begin(), end.fTextByteIndex);
249 return size;
250}
251
252static inline const char* begin(const StringSlice& s) { return s.begin(); }
253
254static inline const char* end(const StringSlice& s) { return s.end(); }
255
256static size_t align_column(const StringSlice& str, size_t p) {
257 if (p >= str.size()) {
258 return str.size();
259 }
260 return align_utf8(begin(str) + p, begin(str)) - begin(str);
261}
262
263// returns smallest i such that list[i] > value. value > list[i-1]
264// Use a binary search since list is monotonic
265template <typename T>
266static size_t find_first_larger(const std::vector<T>& list, T value) {
267 return (size_t)(std::upper_bound(list.begin(), list.end(), value) - list.begin());
268}
269
270static size_t find_closest_x(const std::vector<SkRect>& bounds, float x, size_t b, size_t e) {
271 if (b >= e) {
272 return b;
273 }
274 SkASSERT(e <= bounds.size());
275 size_t best_index = b;
276 float best_diff = ::fabsf(bounds[best_index].x() - x);
277 for (size_t i = b + 1; i < e; ++i) {
278 float d = ::fabsf(bounds[i].x() - x);
279 if (d < best_diff) {
280 best_diff = d;
281 best_index = i;
282 }
283 }
284 return best_index;
285}
286
288 if (fLines.empty()) {
289 return {0, 0};
290 }
291 // First thing: fix possible bad input values.
292 if (pos.fParagraphIndex >= fLines.size()) {
293 pos.fParagraphIndex = fLines.size() - 1;
294 pos.fTextByteIndex = fLines[pos.fParagraphIndex].fText.size();
295 } else {
296 pos.fTextByteIndex = align_column(fLines[pos.fParagraphIndex].fText, pos.fTextByteIndex);
297 }
298
299 SkASSERT(pos.fParagraphIndex < fLines.size());
300 SkASSERT(pos.fTextByteIndex <= fLines[pos.fParagraphIndex].fText.size());
301
302 SkASSERT(pos.fTextByteIndex == fLines[pos.fParagraphIndex].fText.size() ||
303 !is_utf8_continuation(fLines[pos.fParagraphIndex].fText.begin()[pos.fTextByteIndex]));
304
305 switch (move) {
307 break;
309 if (0 == pos.fTextByteIndex) {
310 if (pos.fParagraphIndex > 0) {
311 --pos.fParagraphIndex;
312 pos.fTextByteIndex = fLines[pos.fParagraphIndex].fText.size();
313 }
314 } else {
315 const auto& str = fLines[pos.fParagraphIndex].fText;
316 pos.fTextByteIndex =
317 prev_utf8(begin(str) + pos.fTextByteIndex, begin(str)) - begin(str);
318 }
319 break;
321 if (fLines[pos.fParagraphIndex].fText.size() == pos.fTextByteIndex) {
322 if (pos.fParagraphIndex + 1 < fLines.size()) {
323 ++pos.fParagraphIndex;
324 pos.fTextByteIndex = 0;
325 }
326 } else {
327 const auto& str = fLines[pos.fParagraphIndex].fText;
328 pos.fTextByteIndex =
329 next_utf8(begin(str) + pos.fTextByteIndex, end(str)) - begin(str);
330 }
331 break;
333 {
334 const std::vector<size_t>& list = fLines[pos.fParagraphIndex].fLineEndOffsets;
335 size_t f = find_first_larger(list, pos.fTextByteIndex);
336 pos.fTextByteIndex = f > 0 ? list[f - 1] : 0;
337 }
338 break;
340 {
341 const std::vector<size_t>& list = fLines[pos.fParagraphIndex].fLineEndOffsets;
342 size_t f = find_first_larger(list, pos.fTextByteIndex);
343 if (f < list.size()) {
344 pos.fTextByteIndex = list[f] > 0 ? list[f] - 1 : 0;
345 } else {
346 pos.fTextByteIndex = fLines[pos.fParagraphIndex].fText.size();
347 }
348 }
349 break;
351 {
352 SkASSERT(pos.fTextByteIndex < fLines[pos.fParagraphIndex].fCursorPos.size());
353 float x = fLines[pos.fParagraphIndex].fCursorPos[pos.fTextByteIndex].left();
354 const std::vector<size_t>& list = fLines[pos.fParagraphIndex].fLineEndOffsets;
355 size_t f = find_first_larger(list, pos.fTextByteIndex);
356 // list[f] > value. value > list[f-1]
357 if (f > 0) {
358 // not the first line in paragraph.
359 pos.fTextByteIndex = find_closest_x(fLines[pos.fParagraphIndex].fCursorPos, x,
360 (f == 1) ? 0 : list[f - 2],
361 list[f - 1]);
362 } else if (pos.fParagraphIndex > 0) {
363 --pos.fParagraphIndex;
364 const auto& newLine = fLines[pos.fParagraphIndex];
365 size_t r = newLine.fLineEndOffsets.size();
366 if (r > 0) {
367 pos.fTextByteIndex = find_closest_x(newLine.fCursorPos, x,
368 newLine.fLineEndOffsets[r - 1],
369 newLine.fCursorPos.size());
370 } else {
371 pos.fTextByteIndex = find_closest_x(newLine.fCursorPos, x, 0,
372 newLine.fCursorPos.size());
373 }
374 }
375 pos.fTextByteIndex =
376 align_column(fLines[pos.fParagraphIndex].fText, pos.fTextByteIndex);
377 }
378 break;
380 {
381 const std::vector<size_t>& list = fLines[pos.fParagraphIndex].fLineEndOffsets;
382 float x = fLines[pos.fParagraphIndex].fCursorPos[pos.fTextByteIndex].left();
383
384 size_t f = find_first_larger(list, pos.fTextByteIndex);
385 if (f < list.size()) {
386 const auto& bounds = fLines[pos.fParagraphIndex].fCursorPos;
387 pos.fTextByteIndex = find_closest_x(bounds, x, list[f],
388 f + 1 < list.size() ? list[f + 1]
389 : bounds.size());
390 } else if (pos.fParagraphIndex + 1 < fLines.size()) {
391 ++pos.fParagraphIndex;
392 const auto& bounds = fLines[pos.fParagraphIndex].fCursorPos;
393 const std::vector<size_t>& l2 = fLines[pos.fParagraphIndex].fLineEndOffsets;
394 pos.fTextByteIndex = find_closest_x(bounds, x, 0,
395 l2.size() > 0 ? l2[0] : bounds.size());
396 } else {
397 pos.fTextByteIndex = fLines[pos.fParagraphIndex].fText.size();
398 }
399 pos.fTextByteIndex =
400 align_column(fLines[pos.fParagraphIndex].fText, pos.fTextByteIndex);
401 }
402 break;
404 {
405 if (pos.fTextByteIndex == 0) {
406 pos = this->move(Editor::Movement::kLeft, pos);
407 break;
408 }
409 const std::vector<bool>& words = fLines[pos.fParagraphIndex].fWordBoundaries;
410 SkASSERT(words.size() == fLines[pos.fParagraphIndex].fText.size());
411 do {
412 --pos.fTextByteIndex;
413 } while (pos.fTextByteIndex > 0 && !words[pos.fTextByteIndex]);
414 }
415 break;
417 {
418 const StringSlice& text = fLines[pos.fParagraphIndex].fText;
419 if (pos.fTextByteIndex == text.size()) {
420 pos = this->move(Editor::Movement::kRight, pos);
421 break;
422 }
423 const std::vector<bool>& words = fLines[pos.fParagraphIndex].fWordBoundaries;
424 SkASSERT(words.size() == text.size());
425 do {
426 ++pos.fTextByteIndex;
427 } while (pos.fTextByteIndex < text.size() && !words[pos.fTextByteIndex]);
428 }
429 break;
430
431 }
432 return pos;
433}
434
436 this->reshapeAll();
437 if (!c) {
438 return;
439 }
440
441 c->drawPaint(SkPaint(options.fBackgroundColor));
442
443 SkPaint selection = SkPaint(options.fSelectionColor);
444 auto cmp = [](const Editor::TextPosition& u, const Editor::TextPosition& v) { return u < v; };
445 for (TextPosition pos = std::min(options.fSelectionBegin, options.fSelectionEnd, cmp),
446 end = std::max(options.fSelectionBegin, options.fSelectionEnd, cmp);
447 pos < end;
449 {
450 SkASSERT(pos.fParagraphIndex < fLines.size());
451 const TextLine& l = fLines[pos.fParagraphIndex];
452 c->drawRect(offset(l.fCursorPos[pos.fTextByteIndex], l.fOrigin), selection);
453 }
454
455 if (fLines.size() > 0) {
456 c->drawRect(Editor::getLocation(options.fCursor), SkPaint(options.fCursorColor));
457 }
458
459 SkPaint foreground = SkPaint(options.fForegroundColor);
460 for (const TextLine& line : fLines) {
461 if (line.fBlob) {
462 c->drawTextBlob(line.fBlob.get(), line.fOrigin.x(), line.fOrigin.y(), foreground);
463 }
464 }
465}
466
467void Editor::reshapeAll() {
468 if (fNeedsReshape) {
469 if (fLines.empty()) {
470 fLines.push_back(TextLine());
471 }
472 float shape_width = (float)(fWidth);
473 #ifdef SK_EDITOR_GO_FAST
474 SkSemaphore semaphore;
475 std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool(100);
476 int jobCount = 0;
477 for (TextLine& line : fLines) {
478 if (!line.fShaped) {
479 executor->add([&]() {
480 ShapeResult result = Shape(line.fText.begin(), line.fText.size(),
481 fFont, fLocale, shape_width);
482 line.fBlob = std::move(result.blob);
483 line.fLineEndOffsets = std::move(result.lineBreakOffsets);
484 line.fCursorPos = std::move(result.glyphBounds);
485 line.fWordBoundaries = std::move(result.wordBreaks);
486 line.fHeight = result.verticalAdvance;
487 line.fShaped = true;
488 semaphore.signal();
489 }
490 ++jobCount;
491 });
492 }
493 while (jobCount-- > 0) { semaphore.wait(); }
494 #else
495 for (TextLine& line : fLines) {
496 if (!line.fShaped) {
497 ShapeResult result = Shape(line.fText.begin(), line.fText.size(),
498 fFont, fFontMgr, fLocale, shape_width);
499 line.fBlob = std::move(result.blob);
500 line.fLineEndOffsets = std::move(result.lineBreakOffsets);
501 line.fCursorPos = std::move(result.glyphBounds);
502 line.fWordBoundaries = std::move(result.wordBreaks);
503 line.fHeight = result.verticalAdvance;
504 line.fShaped = true;
505 }
506 }
507 #endif
508 int y = 0;
509 for (TextLine& line : fLines) {
510 line.fOrigin = {0, y};
511 y += line.fHeight;
512 }
513 fHeight = y;
514 fNeedsReshape = false;
515 }
516}
517
const char * options
int count
SkPoint pos
static float next(float f)
#define SkASSERT(cond)
Definition SkAssert.h:116
static bool contains(const SkRect &r, SkPoint p)
Shape
void drawRect(const SkRect &rect, const SkPaint &paint)
void drawPaint(const SkPaint &paint)
void drawTextBlob(const SkTextBlob *blob, SkScalar x, SkScalar y, const SkPaint &paint)
static std::unique_ptr< SkExecutor > MakeFIFOThreadPool(int threads=0, bool allowBorrowing=true)
void paint(SkCanvas *canvas, PaintOpts)
Definition editor.cpp:435
SkRect getLocation(TextPosition)
Definition editor.cpp:131
void setFont(SkFont font)
Definition editor.cpp:54
Text text() const
Definition editor.h:57
StringView line(size_t i) const
Definition editor.h:89
size_t copy(TextPosition pos1, TextPosition pos2, char *dst=nullptr) const
Definition editor.cpp:219
void setFontMgr(sk_sp< SkFontMgr > fontMgr)
Definition editor.cpp:62
TextPosition move(Editor::Movement move, Editor::TextPosition pos) const
Definition editor.cpp:287
TextPosition insert(TextPosition, const char *utf8Text, size_t byteLen)
Definition editor.cpp:153
TextPosition getPosition(SkIPoint)
Definition editor.cpp:77
TextPosition remove(TextPosition, TextPosition)
Definition editor.cpp:181
const SkFont & font() const
Definition editor.h:36
std::size_t size() const
Definition stringslice.h:27
void signal(int n=1)
Definition SkSemaphore.h:56
void wait()
Definition SkSemaphore.h:74
static const char * align_utf8(const char *p, const char *begin)
Definition editor.cpp:120
static size_t find_first_larger(const std::vector< T > &list, T value)
Definition editor.cpp:266
static const char * prev_utf8(const char *p, const char *begin)
Definition editor.cpp:127
static const char * begin(const StringSlice &s)
Definition editor.cpp:252
static SkPoint to_point(SkIPoint p)
Definition editor.cpp:75
static void readlines(const void *data, size_t size, F f)
Definition editor.cpp:30
static constexpr SkRect kUnsetRect
Definition editor.cpp:22
static size_t align_column(const StringSlice &str, size_t p)
Definition editor.cpp:256
static const char * next_utf8(const char *p, const char *end)
Definition editor.cpp:111
static bool valid_utf8(const char *ptr, size_t size)
Definition editor.cpp:24
static bool is_utf8_continuation(char v)
Definition editor.cpp:106
static void append(char **dst, size_t *count, const char *src, size_t n)
Definition editor.cpp:211
static size_t count_char(const StringSlice &string, char value)
Definition editor.cpp:147
static size_t find_closest_x(const std::vector< SkRect > &bounds, float x, size_t b, size_t e)
Definition editor.cpp:270
static StringSlice remove_newline(const char *str, size_t len)
Definition editor.cpp:43
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
sk_sp< SkFontMgr > fontMgr
Definition examples.cpp:32
static bool b
struct MyStruct s
glong glong end
uint8_t value
GAsyncResult * result
double y
double x
SK_SPI int CountUTF8(const char *utf8, size_t byteLength)
Definition SkUTF.cpp:47
SkScalar w
Point offset
Definition SkMD5.cpp:120
constexpr int32_t y() const
constexpr int32_t x() const
void join(const SkIRect &r)
Definition SkRect.cpp:31
void offset(int32_t dx, int32_t dy)
Definition SkRect.h:367
bool contains(int32_t x, int32_t y) const
Definition SkRect.h:463
constexpr float y() const
constexpr float x() const
constexpr SkRect makeOffset(float dx, float dy) const
Definition SkRect.h:965