From 517018e00897f61136418861563a49144a5fe39a Mon Sep 17 00:00:00 2001 From: Youngbok Shin Date: Mon, 20 Aug 2018 07:21:53 -0400 Subject: [PATCH] evas textblock: add/apply cursor cluster APIs based on grapheme cluster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Add a feature for moving cursor over a grapheme cluster. It is applied to edje_entry.c and elm_entry.c for improving cursor handling just like other modern text editors. ex) gedit The patch on Evas needs to update libunibreak library. So, the patch will update libunibreak, too. @feature Test Plan: 1. Put "ഹലോ" in your entry. 2. Your cursor can reach at the end of text from the beginning only in 2 right key event with this feature. Reviewers: raster, cedric, jpeg, herdsman, zmike, devilhorns Reviewed By: herdsman, zmike Subscribers: #reviewers, #committers, zmike, bowonryu, woohyun Tags: #efl Differential Revision: https://phab.enlightenment.org/D5490 --- src/Makefile_Evas.am | 8 +- src/lib/edje/edje_entry.c | 51 +- src/lib/efl/interfaces/efl_text_cursor.eo | 25 + src/lib/evas/canvas/efl_canvas_text.eo | 3 + src/lib/evas/canvas/evas_object_textblock.c | 276 +++- src/lib/evas/canvas/evas_textblock_legacy.h | 26 + src/static_libs/libunibreak/ChangeLog | 92 ++ src/static_libs/libunibreak/LICENCE | 5 +- src/static_libs/libunibreak/NEWS | 7 + src/static_libs/libunibreak/README.md | 14 +- src/static_libs/libunibreak/graphemebreak.c | 283 ++++ src/static_libs/libunibreak/graphemebreak.h | 69 + .../libunibreak/graphemebreakdata.c | 1337 +++++++++++++++++ .../libunibreak/graphemebreakdef.h | 82 + src/static_libs/libunibreak/linebreak.c | 28 +- src/static_libs/libunibreak/linebreak.h | 1 - src/static_libs/libunibreak/linebreakdef.c | 1 - src/static_libs/libunibreak/linebreakdef.h | 1 - src/static_libs/libunibreak/unibreakbase.c | 3 +- src/static_libs/libunibreak/unibreakbase.h | 5 +- src/static_libs/libunibreak/unibreakdef.c | 3 +- src/static_libs/libunibreak/unibreakdef.h | 3 +- src/static_libs/libunibreak/wordbreak.c | 33 +- src/static_libs/libunibreak/wordbreak.h | 9 +- src/static_libs/libunibreak/wordbreakdef.h | 9 +- src/tests/evas/evas_test_textblock.c | 24 +- 26 files changed, 2295 insertions(+), 103 deletions(-) create mode 100644 src/static_libs/libunibreak/graphemebreak.c create mode 100644 src/static_libs/libunibreak/graphemebreak.h create mode 100644 src/static_libs/libunibreak/graphemebreakdata.c create mode 100644 src/static_libs/libunibreak/graphemebreakdef.h diff --git a/src/Makefile_Evas.am b/src/Makefile_Evas.am index 0699dde533..b09521cb72 100644 --- a/src/Makefile_Evas.am +++ b/src/Makefile_Evas.am @@ -193,7 +193,10 @@ static_libs/libunibreak/linebreak.h \ static_libs/libunibreak/linebreakdef.h \ static_libs/libunibreak/wordbreakdef.h \ static_libs/libunibreak/wordbreak.h \ -static_libs/libunibreak/wordbreakdata.c +static_libs/libunibreak/wordbreakdata.c \ +static_libs/libunibreak/graphemebreak.h \ +static_libs/libunibreak/graphemebreakdef.h \ +static_libs/libunibreak/graphemebreakdata.c # Linebreak lib_evas_libevas_la_SOURCES = \ @@ -202,7 +205,8 @@ static_libs/libunibreak/unibreakdef.c \ static_libs/libunibreak/linebreak.c \ static_libs/libunibreak/linebreakdata.c \ static_libs/libunibreak/linebreakdef.c \ -static_libs/libunibreak/wordbreak.c +static_libs/libunibreak/wordbreak.c \ +static_libs/libunibreak/graphemebreak.c # Main lib_evas_libevas_la_SOURCES += \ diff --git a/src/lib/edje/edje_entry.c b/src/lib/edje/edje_entry.c index 6c1f8960ff..1ad3c30b65 100644 --- a/src/lib/edje/edje_entry.c +++ b/src/lib/edje/edje_entry.c @@ -536,7 +536,7 @@ _curs_jump_line(Evas_Textblock_Cursor *c, Evas_Object *o, Entry *en, int ln) if (!evas_object_textblock_line_number_geometry_get(o, ln, &lx, &ly, &lw, &lh)) return EINA_FALSE; - if (evas_textblock_cursor_char_coord_set(c, cx, ly + (lh / 2))) + if (evas_textblock_cursor_cluster_coord_set(c, cx, ly + (lh / 2))) return EINA_TRUE; evas_textblock_cursor_line_set(c, ln); if (cx < (lx + (lw / 2))) @@ -1607,24 +1607,33 @@ _delete_emit(Edje *ed, Evas_Textblock_Cursor *c, Entry *en, size_t pos, ERR("Running very low on memory"); return; } - char *tmp = evas_textblock_cursor_content_get(c); + char *tmp = NULL; info->insert = EINA_FALSE; if (backspace) { info->change.del.start = pos - 1; info->change.del.end = pos; + tmp = evas_textblock_cursor_content_get(c); + evas_textblock_cursor_char_delete(c); } else { - info->change.del.start = pos + 1; + Evas_Textblock_Cursor *cc = evas_object_textblock_cursor_new(en->rp->object); + evas_textblock_cursor_copy(c, cc); + evas_textblock_cursor_cluster_next(cc); + + info->change.del.start = evas_textblock_cursor_pos_get(cc); info->change.del.end = pos; + + tmp = evas_textblock_cursor_range_text_get(c, cc, EVAS_TEXTBLOCK_TEXT_MARKUP); + evas_textblock_cursor_range_delete(c, cc); + evas_textblock_cursor_free(cc); } info->change.del.content = eina_stringshare_add(tmp); if (tmp) free(tmp); - evas_textblock_cursor_char_delete(c); _edje_emit(ed, "entry,changed", en->rp->part->name); _edje_emit_full(ed, "entry,changed,user", en->rp->part->name, info, _free_entry_change_info); @@ -1855,7 +1864,7 @@ _edje_key_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, } } } - if (evas_textblock_cursor_char_prev(en->cursor)) + if (evas_textblock_cursor_cluster_prev(en->cursor)) ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD; #if defined(__APPLE__) && defined(__MACH__) if (altgr) evas_textblock_cursor_word_start(en->cursor); @@ -1903,7 +1912,7 @@ _edje_key_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, #else if (control) evas_textblock_cursor_word_end(en->cursor); #endif - if (evas_textblock_cursor_char_next(en->cursor)) + if (evas_textblock_cursor_cluster_next(en->cursor)) ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD; if (en->select_allow) { @@ -1921,7 +1930,7 @@ _edje_key_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, // del to start of previous word _sel_start(en->cursor, rp->object, en); - evas_textblock_cursor_char_prev(en->cursor); + evas_textblock_cursor_cluster_prev(en->cursor); evas_textblock_cursor_word_start(en->cursor); _sel_preextend(ed, en->cursor, rp->object, en); @@ -1961,7 +1970,7 @@ _edje_key_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, _sel_start(en->cursor, rp->object, en); evas_textblock_cursor_word_end(en->cursor); - evas_textblock_cursor_char_next(en->cursor); + evas_textblock_cursor_cluster_next(en->cursor); _sel_extend(ed, en->cursor, rp->object, en); @@ -2400,7 +2409,7 @@ _edje_key_up_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, v } static Evas_Textblock_Cursor * -_edje_cursor_char_coord_set(Edje_Real_Part *rp, Evas_Coord canvasx, Evas_Coord canvasy, Evas_Coord *cx, Evas_Coord *cy) +_edje_cursor_cluster_coord_set(Edje_Real_Part *rp, Evas_Coord canvasx, Evas_Coord canvasy, Evas_Coord *cx, Evas_Coord *cy) { Entry *en; Evas_Coord x, y, lh = 0, cly = 0; @@ -2432,7 +2441,7 @@ _edje_cursor_char_coord_set(Edje_Real_Part *rp, Evas_Coord canvasx, Evas_Coord c evas_textblock_cursor_free(line_cur); /* No need to check return value if not able to set the char coord Textblock * will take care */ - evas_textblock_cursor_char_coord_set(en->cursor, *cx, *cy); + evas_textblock_cursor_cluster_coord_set(en->cursor, *cx, *cy); return tc; } @@ -2530,7 +2539,7 @@ _edje_part_mouse_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_ else { evas_textblock_cursor_word_end(en->cursor); - evas_textblock_cursor_char_next(en->cursor); + evas_textblock_cursor_cluster_next(en->cursor); } _sel_extend(en->ed, en->cursor, rp->object, en); } @@ -2544,13 +2553,13 @@ _edje_part_mouse_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_ evas_textblock_cursor_word_start(en->cursor); _sel_start(en->cursor, rp->object, en); evas_textblock_cursor_word_end(en->cursor); - evas_textblock_cursor_char_next(en->cursor); + evas_textblock_cursor_cluster_next(en->cursor); _sel_extend(en->ed, en->cursor, rp->object, en); } goto end; } } - tc = _edje_cursor_char_coord_set(rp, ev->canvas.x, ev->canvas.y, &cx, &cy); + tc = _edje_cursor_cluster_coord_set(rp, ev->canvas.x, ev->canvas.y, &cx, &cy); if (dosel) { @@ -2670,7 +2679,7 @@ _edje_part_mouse_up_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UN #endif /* cx cy are unused but needed in mouse down, please bear with it */ - tc = _edje_cursor_char_coord_set(rp, ev->canvas.x, ev->canvas.y, &cx, &cy); + tc = _edje_cursor_cluster_coord_set(rp, ev->canvas.x, ev->canvas.y, &cx, &cy); if (en->select_allow) { @@ -2748,7 +2757,7 @@ _edje_part_mouse_move_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_ evas_object_geometry_get(rp->object, &x, &y, &w, &h); cx = ev->cur.canvas.x - x; cy = ev->cur.canvas.y - y; - if (!evas_textblock_cursor_char_coord_set(en->cursor, cx, cy)) + if (!evas_textblock_cursor_cluster_coord_set(en->cursor, cx, cy)) { Evas_Coord lx, ly, lw, lh; @@ -2760,7 +2769,7 @@ _edje_part_mouse_move_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_ { evas_textblock_cursor_paragraph_first(en->cursor); evas_textblock_cursor_line_geometry_get(en->cursor, &lx, &ly, &lw, &lh); - if (!evas_textblock_cursor_char_coord_set(en->cursor, cx, ly + (lh / 2))) + if (!evas_textblock_cursor_cluster_coord_set(en->cursor, cx, ly + (lh / 2))) _curs_end(en->cursor, rp->object, en); } } @@ -4029,7 +4038,7 @@ _edje_text_cursor_next(Edje_Real_Part *rp, Efl_Text_Cursor_Cursor *c) _edje_entry_imf_context_reset(rp); - if (!evas_textblock_cursor_char_next(c)) + if (!evas_textblock_cursor_cluster_next(c)) { return EINA_FALSE; } @@ -4062,7 +4071,7 @@ _edje_text_cursor_prev(Edje_Real_Part *rp, Efl_Text_Cursor_Cursor *c) _edje_entry_imf_context_reset(rp); - if (!evas_textblock_cursor_char_prev(c)) + if (!evas_textblock_cursor_cluster_prev(c)) { if (evas_textblock_cursor_paragraph_prev(c)) goto ok; else return EINA_FALSE; @@ -4106,7 +4115,7 @@ _edje_text_cursor_up(Edje_Real_Part *rp, Efl_Text_Cursor_Cursor *c) &lx, &ly, &lw, &lh)) return EINA_FALSE; evas_textblock_cursor_char_geometry_get(c, &cx, &cy, &cw, &ch); - if (!evas_textblock_cursor_char_coord_set(c, cx, ly + (lh / 2))) + if (!evas_textblock_cursor_cluster_coord_set(c, cx, ly + (lh / 2))) evas_textblock_cursor_line_char_last(c); _sel_update(en->ed, c, rp->object, rp->typedata.text->entry_data); @@ -4145,7 +4154,7 @@ _edje_text_cursor_down(Edje_Real_Part *rp, Efl_Text_Cursor_Cursor *c) &lx, &ly, &lw, &lh)) return EINA_FALSE; evas_textblock_cursor_char_geometry_get(c, &cx, &cy, &cw, &ch); - if (!evas_textblock_cursor_char_coord_set(c, cx, ly + (lh / 2))) + if (!evas_textblock_cursor_cluster_coord_set(c, cx, ly + (lh / 2))) evas_textblock_cursor_line_char_last(c); _sel_update(en->ed, c, rp->object, rp->typedata.text->entry_data); @@ -4347,7 +4356,7 @@ _edje_text_cursor_coord_set(Edje_Real_Part *rp, Efl_Text_Cursor_Cursor *c, _edje_emit(en->ed, "selection,changed", rp->part->name); } } - return evas_textblock_cursor_char_coord_set(c, x, y); + return evas_textblock_cursor_cluster_coord_set(c, x, y); } Eina_Bool diff --git a/src/lib/efl/interfaces/efl_text_cursor.eo b/src/lib/efl/interfaces/efl_text_cursor.eo index 973c7d85bd..b0e4c28b51 100644 --- a/src/lib/efl/interfaces/efl_text_cursor.eo +++ b/src/lib/efl/interfaces/efl_text_cursor.eo @@ -145,6 +145,20 @@ interface Efl.Text_Cursor { /* @inout */ cur: ptr(Efl.Text_Cursor_Cursor); [[Cursor object]] } } + cursor_cluster_next { + [[Advances to the next grapheme cluster]] + legacy: null; + params { + /* @inout */ cur: ptr(Efl.Text_Cursor_Cursor); [[Cursor object]] + } + } + cursor_cluster_prev { + [[Advances to the previous grapheme cluster]] + legacy: null; + params { + /* @inout */ cur: ptr(Efl.Text_Cursor_Cursor); [[Cursor object]] + } + } cursor_paragraph_char_first { [[Advances to the first character in this paragraph]] legacy: null; @@ -232,6 +246,17 @@ interface Efl.Text_Cursor { @in y: int; [[Y coord to set by.]] } } + cursor_cluster_coord_set { + [[Set cursor coordinates according to grapheme clusters. + It does not allow to put a cursor to the middle of a grapheme cluster. + ]] + legacy: null; + params { + /* @inout */ cur: ptr(Efl.Text_Cursor_Cursor); [[Cursor object]] + @in x: int; [[X coord to set by.]] + @in y: int; [[Y coord to set by.]] + } + } cursor_text_insert { [[Adds text to the current cursor position and set the cursor to *after* the start of the text just added. diff --git a/src/lib/evas/canvas/efl_canvas_text.eo b/src/lib/evas/canvas/efl_canvas_text.eo index b24139e34a..6c82adf82a 100644 --- a/src/lib/evas/canvas/efl_canvas_text.eo +++ b/src/lib/evas/canvas/efl_canvas_text.eo @@ -327,6 +327,8 @@ class Efl.Canvas.Text (Efl.Canvas.Object, Efl.Text, Efl.Text_Cursor.cursor_copy; Efl.Text_Cursor.cursor_char_next; Efl.Text_Cursor.cursor_char_prev; + Efl.Text_Cursor.cursor_cluster_next; + Efl.Text_Cursor.cursor_cluster_prev; Efl.Text_Cursor.cursor_paragraph_char_first; Efl.Text_Cursor.cursor_paragraph_char_last; Efl.Text_Cursor.cursor_word_start; @@ -339,6 +341,7 @@ class Efl.Canvas.Text (Efl.Canvas.Object, Efl.Text, Efl.Text_Cursor.cursor_paragraph_prev; Efl.Text_Cursor.cursor_line_jump_by; Efl.Text_Cursor.cursor_coord_set; + Efl.Text_Cursor.cursor_cluster_coord_set; Efl.Text_Cursor.cursor_text_insert; Efl.Text_Cursor.cursor_char_delete; Efl.Text_Annotate.annotation { set; get; } diff --git a/src/lib/evas/canvas/evas_object_textblock.c b/src/lib/evas/canvas/evas_object_textblock.c index f523f131e9..16f717ce41 100644 --- a/src/lib/evas/canvas/evas_object_textblock.c +++ b/src/lib/evas/canvas/evas_object_textblock.c @@ -77,6 +77,7 @@ #include "linebreak.h" #include "wordbreak.h" +#include "graphemebreak.h" #include "evas_filter.h" #include "efl_canvas_filter_internal.eo.h" @@ -9271,20 +9272,122 @@ _efl_canvas_text_efl_text_cursor_cursor_word_end(Eo *eo_obj, Efl_Canvas_Text_Dat efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); } -EAPI Eina_Bool -evas_textblock_cursor_char_next(Efl_Text_Cursor_Cursor *cur) +static char * +_evas_textblock_grapheme_breaks_new(Evas_Object_Textblock_Item *it, size_t len) { - int ind; + char *grapheme_breaks = NULL; + const char *lang = (it->format->font.fdesc) ? it->format->font.fdesc->lang : ""; + + grapheme_breaks = malloc(len); + if (!grapheme_breaks) return NULL; + + set_graphemebreaks_utf32((const utf32_t *) + eina_ustrbuf_string_get( + it->text_node->unicode), + len, lang, grapheme_breaks); + + return grapheme_breaks; +} + +static size_t +_evas_textblock_cursor_cluster_pos_get(Evas_Textblock_Cursor *cur, Eina_Bool inc) +{ + Evas_Object_Textblock_Paragraph *par; + Efl_Canvas_Text_Data *o; + size_t cur_pos = cur->pos; + size_t ret = cur->pos; + + if (!inc) cur_pos--; + + if (!cur->node->par) + { + o = efl_data_scope_get(cur->obj, MY_CLASS); + if (o) _relayout_if_needed(cur->obj, o); + } + + par = cur->node->par; + + if (par) + { + Eina_List *l; + Evas_Object_Textblock_Item *it, *last_it = NULL; + EINA_LIST_FOREACH(par->logical_items, l, it) + { + if (it->text_pos > cur_pos) + { + if (!last_it) last_it = it; + break; + } + last_it = it; + } + + if (last_it) + { + it = last_it; + if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) + { + size_t len = eina_ustrbuf_length_get(it->text_node->unicode); + char *grapheme_breaks = _evas_textblock_grapheme_breaks_new(it, len); + + if (grapheme_breaks) + { + size_t grapheme_breaks_index = cur_pos; + + if (inc) + { + while ((grapheme_breaks_index < len) && + (grapheme_breaks[grapheme_breaks_index] != GRAPHEMEBREAK_BREAK)) + { + grapheme_breaks_index++; + } + + ret = grapheme_breaks_index + 1; + } + else + { + while ((grapheme_breaks_index > 0) && + (grapheme_breaks[grapheme_breaks_index - 1] != GRAPHEMEBREAK_BREAK)) + { + grapheme_breaks_index--; + } + + ret = grapheme_breaks_index; + } + + free(grapheme_breaks); + } + } + } + } + + return ret; +} + +static Eina_Bool +_evas_textblock_cursor_next(Evas_Textblock_Cursor *cur, Eina_Bool per_cluster) +{ + Evas_Object_Protected_Data *obj; const Eina_Unicode *text; + int ind; if (!cur) return EINA_FALSE; - Evas_Object_Protected_Data *obj = efl_data_scope_get(cur->obj, EFL_CANVAS_OBJECT_CLASS); - evas_object_async_block(obj); TB_NULL_CHECK(cur->node, EINA_FALSE); + obj = efl_data_scope_get(cur->obj, EFL_CANVAS_OBJECT_CLASS); + evas_object_async_block(obj); + ind = cur->pos; text = eina_ustrbuf_string_get(cur->node->unicode); - if (text[ind]) ind++; + + if (text[ind]) + { + if (per_cluster) + ind = _evas_textblock_cursor_cluster_pos_get(cur, EINA_TRUE); + + if (ind <= (int)cur->pos) + ind = cur->pos + 1; + } + /* Only allow pointing a null if it's the last paragraph. * because we don't have a PS there. */ if (text[ind]) @@ -9311,42 +9414,90 @@ evas_textblock_cursor_char_next(Efl_Text_Cursor_Cursor *cur) } } -EOLIAN static void -_efl_canvas_text_efl_text_cursor_cursor_char_next(Eo *eo_obj, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) -{ - ASYNC_BLOCK; - evas_textblock_cursor_char_next(cur); - efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); -} - static Eina_Bool -_evas_textblock_cursor_char_prev(Efl_Text_Cursor_Cursor *cur) +_evas_textblock_cursor_prev(Evas_Textblock_Cursor *cur, Eina_Bool per_cluster) { + Evas_Object_Protected_Data *obj; if (!cur) return EINA_FALSE; TB_NULL_CHECK(cur->node, EINA_FALSE); + obj = efl_data_scope_get(cur->obj, EFL_CANVAS_OBJECT_CLASS); + evas_object_async_block(obj); + if (cur->pos != 0) { + if (per_cluster) + { + size_t ret = _evas_textblock_cursor_cluster_pos_get(cur, EINA_FALSE); + + if (ret != cur->pos) + { + cur->pos = ret; + return EINA_TRUE; + } + } + cur->pos--; return EINA_TRUE; } return evas_textblock_cursor_paragraph_prev(cur); } +EAPI Eina_Bool +evas_textblock_cursor_char_next(Efl_Text_Cursor_Cursor *cur) +{ + return _evas_textblock_cursor_next(cur, EINA_FALSE); +} + +EOLIAN static void +_efl_canvas_text_efl_text_cursor_cursor_char_next(Eo *eo_obj, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) +{ + ASYNC_BLOCK; + if (_evas_textblock_cursor_next(cur, EINA_FALSE)) + efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); +} + EAPI Eina_Bool evas_textblock_cursor_char_prev(Efl_Text_Cursor_Cursor *cur) { - if (!cur) return EINA_FALSE; - return _evas_textblock_cursor_char_prev(cur); + return _evas_textblock_cursor_prev(cur, EINA_FALSE); } EOLIAN static void _efl_canvas_text_efl_text_cursor_cursor_char_prev(Eo *eo_obj EINA_UNUSED, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) { ASYNC_BLOCK; - _evas_textblock_cursor_char_prev(cur); - efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); + if (_evas_textblock_cursor_prev(cur, EINA_FALSE)) + efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); +} + +EAPI Eina_Bool +evas_textblock_cursor_cluster_next(Efl_Text_Cursor_Cursor *cur) +{ + return _evas_textblock_cursor_next(cur, EINA_TRUE); +} + +EOLIAN static void +_efl_canvas_text_efl_text_cursor_cursor_cluster_next(Eo *eo_obj, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) +{ + ASYNC_BLOCK; + if (_evas_textblock_cursor_next(cur, EINA_TRUE)) + efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); +} + +EAPI Eina_Bool +evas_textblock_cursor_cluster_prev(Efl_Text_Cursor_Cursor *cur) +{ + return _evas_textblock_cursor_prev(cur, EINA_TRUE); +} + +EOLIAN static void +_efl_canvas_text_efl_text_cursor_cursor_cluster_prev(Eo *eo_obj, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur) +{ + ASYNC_BLOCK; + if (_evas_textblock_cursor_prev(cur, EINA_TRUE)) + efl_event_callback_legacy_call(eo_obj, EFL_CANVAS_TEXT_EVENT_CURSOR_CHANGED, NULL); } EAPI void @@ -12031,15 +12182,16 @@ _efl_canvas_text_visible_range_get(Eo *eo_obj EINA_UNUSED, return EINA_TRUE; } - -EAPI Eina_Bool -evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) +static Eina_Bool +_evas_textblock_cursor_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y, Eina_Bool per_cluster) { Evas_Object_Textblock_Paragraph *found_par; Evas_Object_Textblock_Line *ln; Evas_Object_Textblock_Item *it = NULL; Eina_Bool ret = EINA_FALSE; + if (!cur) return ret; + Evas_Object_Protected_Data *obj = efl_data_scope_get(cur->obj, EFL_CANVAS_OBJECT_CLASS); evas_object_async_block(obj); Efl_Canvas_Text_Data *o = efl_data_scope_get(cur->obj, MY_CLASS); @@ -12112,6 +12264,63 @@ evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, &cx, &cy, &cw, &ch); if (pos < 0) goto end; + + if ((pos > 0) && per_cluster) + { + size_t len = eina_ustrbuf_length_get(it->text_node->unicode); + char *grapheme_breaks = _evas_textblock_grapheme_breaks_new(it, len); + + /* If current position is not breakable, + * try to move cursor to a nearest breakable position. */ + if (grapheme_breaks && (grapheme_breaks[pos + it->text_pos - 1] != GRAPHEMEBREAK_BREAK)) + { + size_t left_index = pos + it->text_pos - 1; + size_t right_index = pos + it->text_pos - 1; + int lx, rx; + + /* To the left */ + while ((left_index > 0) && + (grapheme_breaks[left_index] != GRAPHEMEBREAK_BREAK)) + { + left_index--; + } + + ENFN->font_pen_coords_get(ENC, + ti->parent.format->font.font, + &ti->text_props, + left_index - it->text_pos + 1, + &lx, NULL, NULL, NULL); + + /* To the right */ + while ((right_index < len) && + (grapheme_breaks[right_index] != GRAPHEMEBREAK_BREAK)) + { + right_index++; + } + + ENFN->font_pen_coords_get(ENC, + ti->parent.format->font.font, + &ti->text_props, + right_index - it->text_pos + 1, + &rx, NULL, NULL, NULL); + + /* Decide a nearest position by checking its geometry. */ + if (((ti->text_props.bidi_dir != EVAS_BIDI_DIRECTION_RTL) && + ((ln->x + it->x + rx - x) >= (x - (lx + ln->x + it->x)))) || + ((ti->text_props.bidi_dir == EVAS_BIDI_DIRECTION_RTL) && + ((ln->x + it->x + lx - x) >= (x - (rx + ln->x + it->x))))) + { + pos = left_index - it->text_pos + 1; + } + else + { + pos = right_index - it->text_pos + 1; + } + } + + free(grapheme_breaks); + } + cur->pos = pos + it->text_pos; cur->node = it->text_node; ret = EINA_TRUE; @@ -12167,6 +12376,18 @@ end: return ret; } +EAPI Eina_Bool +evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) +{ + return _evas_textblock_cursor_coord_set(cur, x, y, EINA_FALSE); +} + +EAPI Eina_Bool +evas_textblock_cursor_cluster_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) +{ + return _evas_textblock_cursor_coord_set(cur, x, y, EINA_TRUE); +} + EOLIAN static void _efl_canvas_text_efl_text_cursor_cursor_coord_set(Eo *eo_obj EINA_UNUSED, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur EINA_UNUSED, Evas_Coord x, Evas_Coord y) @@ -12175,6 +12396,14 @@ _efl_canvas_text_efl_text_cursor_cursor_coord_set(Eo *eo_obj EINA_UNUSED, Efl_Ca evas_textblock_cursor_char_coord_set(cur, x, y); } +EOLIAN static void +_efl_canvas_text_efl_text_cursor_cursor_cluster_coord_set(Eo *eo_obj EINA_UNUSED, Efl_Canvas_Text_Data *o EINA_UNUSED, Efl_Text_Cursor_Cursor *cur EINA_UNUSED, + Evas_Coord x, Evas_Coord y) +{ + ASYNC_BLOCK; + evas_textblock_cursor_cluster_coord_set(cur, x, y); +} + EAPI int evas_textblock_cursor_line_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord y) { @@ -13279,6 +13508,7 @@ evas_object_textblock_init(Evas_Object *eo_obj) linebreak_init = EINA_TRUE; init_linebreak(); init_wordbreak(); + init_graphemebreak(); } o = obj->private_data; @@ -15194,7 +15424,7 @@ _efl_canvas_text_efl_text_annotate_range_annotations_get(const Eo *eo_obj, Efl_C if (!it->start_node || !it->end_node) continue; _textblock_cursor_pos_at_fnode_set(eo_obj, &start2, it->start_node); _textblock_cursor_pos_at_fnode_set(eo_obj, &end2, it->end_node); - _evas_textblock_cursor_char_prev(&end2); + evas_textblock_cursor_char_prev(&end2); if (!((evas_textblock_cursor_compare(&start2, end) > 0) || (evas_textblock_cursor_compare(&end2, start) < 0))) { diff --git a/src/lib/evas/canvas/evas_textblock_legacy.h b/src/lib/evas/canvas/evas_textblock_legacy.h index 0d84075fa0..2c8ee05f57 100644 --- a/src/lib/evas/canvas/evas_textblock_legacy.h +++ b/src/lib/evas/canvas/evas_textblock_legacy.h @@ -604,6 +604,22 @@ EAPI Eina_Bool evas_textblock_cursor_char_prev(Evas_Textblock_Cursor *obj); */ EAPI Eina_Bool evas_textblock_cursor_char_next(Evas_Textblock_Cursor *obj); +/** + * @brief Advances the cursor one grapheme cluster backwards. + * + * @return @c true on success, @c false otherwise. + */ +EAPI Eina_Bool evas_textblock_cursor_cluster_prev(Evas_Textblock_Cursor *obj); + +/** + * @brief Advances the cursor one grapheme cluster forward. + * + * @return @c true on success, @c false otherwise. + * + * @ingroup Evas_Textblock_Cursor + */ +EAPI Eina_Bool evas_textblock_cursor_cluster_next(Evas_Textblock_Cursor *obj); + /** * @brief Advances to the start of the next text node * @@ -859,6 +875,16 @@ EAPI Evas_Textblock_Cursor *evas_object_textblock_cursor_new(const Evas_Object * */ EAPI Eina_Bool evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *obj, Evas_Coord x, Evas_Coord y); +/** + * @brief Sets the position of the cursor according to the X and Y coordinates and + * grapheme clusters of text. + * + * @param[in] y y coord to set by. + * + * @return @c true on success, @c false otherwise. + */ +EAPI Eina_Bool evas_textblock_cursor_cluster_coord_set(Evas_Textblock_Cursor *obj, Evas_Coord x, Evas_Coord y); + /** * Free the cursor and unassociate it from the object. * @note do not use it to free unassociated cursors. diff --git a/src/static_libs/libunibreak/ChangeLog b/src/static_libs/libunibreak/ChangeLog index cad33cfd26..8df529f0c9 100644 --- a/src/static_libs/libunibreak/ChangeLog +++ b/src/static_libs/libunibreak/ChangeLog @@ -1,3 +1,95 @@ +2016-12-15 Wu Yongwei + + * src/Makefile.am (include_HEADERS): Move graphemebreakdef.h to + EXTRA_DIST. + (EXTRA_DIST): Add graphemebreakdef.h and test_skips.h. + +2016-12-14 Wu Yongwei + + * src/linebreak.c: Adjust documentation comment. + * src/wordbreak.c: Ditto. + * src/graphemebreak.c: Ditto. + +2016-12-14 Wu Yongwei + + * Doxyfile (FULL_PATH_NAMES): Set to `NO'. + (DOT_IMAGE_FORMAT): Set to `svg'. + (SEARCHENGINE): Set to `YES'. + +2016-12-10 Wu Yongwei + + Update for the libunibreak 4.0 release. + * NEWS: Add information about libunibreak 4.0. + * Doxyfile (PROJECT_NUMBER): Change to `4.0'. + * configure.ac (AC_INIT): Change the library version to `4.0'. + * src/Makefile.am (libunibreak_la_LDFLAGS): Set the version-info to + `4:0:1'. + * src/unibreakbase.h (UNIBREAK_VERSION): Set to 0x0400. + +2016-12-10 Wu Yongwei + + * src/Makefile.am (include_HEADERS): Add a missing file + graphemebreakdef.h. + +2016-12-10 Wu Yongwei + + * bootstrap: Add a missing `--copy' argument to glibtoolize. + +2016-12-10 Wu Yongwei + + * README.md: Update for grapheme break and links. + * LICENCE: Add Andreas Röver and update copyright information. + +2016-12-10 Wu Yongwei + + * Doxyfile (EXCLUDE): Add `src/tests.c'. + +2016-12-10 Wu Yongwei + + * src/wordbreak.c: Update Unicode version and link information. + * src/wordbreak.h: Ditto. + * src/wordbreakdef.h: Ditto. + * src/graphemebreak.c: Ditto. + * src/graphemebreak.h: Ditto. + * src/graphemebreakdef.h: Ditto. + +2016-12-10 Wu Yongwei + + * src/linebreak.c: Remove `@version' and update copyright header. + * src/linebreak.h: Ditto. + * src/linebreakdef.c: Ditto. + * src/linebreakdef.h: Ditto. + * src/wordbreak.c: Ditto. + * src/wordbreak.h: Ditto. + * src/wordbreakdef.h: Ditto. + * src/graphemebreak.c: Ditto. + * src/graphemebreak.h: Ditto. + * src/graphemebreakdef.h: Ditto. + * src/unibreakbase.c: Ditto. + * src/unibreakbase.h: Ditto. + * src/unibreakdef.c: Ditto. + * src/unibreakdef.h: Ditto. + +2016-12-10 Wu Yongwei + + * src/Makefile.msvc: Add graphemebreak.c. + * src/graphemebreak.c: Add a workaround of stdbool.h for MSVC + versions earlier than 2013. + * src/graphemebreak.h: Make include order consistent. + * src/linebreak.c (ends_with): Make the code compile under C89. + +2016-12-10 Wu Yongwei + + * src/Makefile.gcc (CFILES): Add graphemebreak.c. + (graphemebreakdata): New phony target. + (GraphemeBreakProperty.txt): New target. + (distclean): Add WordBreakProperty.txt and GraphemeBreakProperty.txt + as well. + +2016-12-05 Tom Hacohen + + * src/test_skips.h: New file. + 2016-12-04 Wu Yongwei Simpify implementation about RI pairing. diff --git a/src/static_libs/libunibreak/LICENCE b/src/static_libs/libunibreak/LICENCE index 3eda8d5061..3fba16ad53 100644 --- a/src/static_libs/libunibreak/LICENCE +++ b/src/static_libs/libunibreak/LICENCE @@ -1,6 +1,7 @@ -Copyright (C) 2008-2015 Wu Yongwei -Copyright (C) 2012-2015 Tom Hacohen +Copyright (C) 2008-2016 Wu Yongwei +Copyright (C) 2012-2016 Tom Hacohen Copyright (C) 2013 Petr Filipsky +Copyright (C) 2016 Andreas Röver This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages diff --git a/src/static_libs/libunibreak/NEWS b/src/static_libs/libunibreak/NEWS index 7de7feba18..d217628da8 100644 --- a/src/static_libs/libunibreak/NEWS +++ b/src/static_libs/libunibreak/NEWS @@ -1,3 +1,10 @@ +New in libunibreak 4.0 + +- Update the code and data to conform to Unicode 9.0.0 +- Add grapheme breaking support +- Tested and enhanced according to the Unicode test suite +- Make bug fixes + New in libunibreak 3.0 - Update the code and data to conform to Unicode 7.0.0 diff --git a/src/static_libs/libunibreak/README.md b/src/static_libs/libunibreak/README.md index 52cd7388b5..f37fd902aa 100644 --- a/src/static_libs/libunibreak/README.md +++ b/src/static_libs/libunibreak/README.md @@ -9,8 +9,8 @@ breaking and word breaking algorithms as described in [Unicode Standard Annex 14] [1] and [Unicode Standard Annex 29] [2]. Check the project's [home page] [3] for up-to-date information. - [1]: http://www.unicode.org/reports/tr14/tr14-30.html - [2]: http://www.unicode.org/reports/tr29/tr29-21.html + [1]: http://www.unicode.org/reports/tr14/tr14-37.html + [2]: http://www.unicode.org/reports/tr29/tr29-29.html [3]: https://github.com/adah1972/libunibreak @@ -46,6 +46,8 @@ There are three ways to build the library: *LineBreak.txt*. - type `make wordbreakdata` to regenerate *wordbreakdata.c* from *WordBreakProperty.txt*. + - type `make graphemebreakdata` to regenerate *graphemebreakdata.c* + from *GraphemeBreakProperty.txt*. 2. On systems where GCC and Binutils are supported, one can type @@ -61,6 +63,8 @@ There are three ways to build the library: *LineBreak.txt*. - type `make wordbreakdata` to regenerate *wordbreakdata.c* from *WordBreakProperty.txt*. + - type `make graphemebreakdata` to regenerate *graphemebreakdata.c* + from *GraphemeBreakProperty.txt*. 3. On Windows, apart from using method 1 (Cygwin/MSYS) and method 2 (MinGW), MSVC can also be used. Type @@ -77,9 +81,9 @@ There are three ways to build the library: Documentation ------------- -Check the generated document *doc/html/linebreak\_8h.html* and -*doc/html/wordbreak\_8h.html* in the downloaded file for the public -interfaces exposed to applications. +Check the generated document *doc/html/linebreak\_8h.html*, +*doc/html/wordbreak\_8h.html*, and *doc/html/graphemebreak\_8h.html* in +the downloaded file for the public interfaces exposed to applications.