From 40dfc4a45dd076465ea36cfb9e7229da58a907f4 Mon Sep 17 00:00:00 2001 From: Daniel Hirt Date: Tue, 13 Oct 2015 10:59:19 +0300 Subject: [PATCH] Evas textblock: add support for hyphenation wrap style We now support hyphenation in style. Use "wrap=hyphenation" to use this wrap option. It will hyphenate based on explicit SOFT HYPHEN (­) placement in the text, and with the (optional) assistance of dictionaries compatible with Hunspell's "hyphen" library. This wrap mode favors breaking at hyphen positions in a word, over moving the whole word to the next line. It will put an additional "-" at the break position if it was hyphened. Enabling the hyphen dictionaries is done by adding these configure options: --enable-hyphen (requires Hunspell's "hyphen" library installed) --with-dictionaries-hyphen-dir=DIR (specifies the install location of the actual .dic dictionary files e.g. /usr/share/hyphen) Note that dictionary files are expected to be in the form of "en_US.dic" or anything that ends with it e.g. "hyph_en_US.dic" (this how they are named in Arch Linux). @feature --- configure.ac | 34 ++ src/Makefile_Evas.am | 1 + src/lib/evas/canvas/evas_object_textblock.c | 318 +++++++++++++++++- .../evas/canvas/evas_textblock_hyphenation.x | 148 ++++++++ 4 files changed, 492 insertions(+), 9 deletions(-) create mode 100644 src/lib/evas/canvas/evas_textblock_hyphenation.x diff --git a/configure.ac b/configure.ac index 5c46f8ff5b..d6933beb26 100644 --- a/configure.ac +++ b/configure.ac @@ -878,6 +878,9 @@ EINA_CONFIG([MAGIC_DEBUG], [test "x${have_magic_debug}" = "xyes"]) AC_ARG_WITH([xattr-tests-path], [AS_HELP_STRING([--with-xattr-tests-path=DIR],[path of xattr enabled directory to create test files])],[XATTR_TEST_DIR=${withval}][AC_DEFINE_UNQUOTED([XATTR_TEST_DIR],["$withval"], [xattr enabled directory])]) +AC_ARG_WITH([dictionaries-hyphen-dir], + [AS_HELP_STRING([--with-dictionaries-hyphen-dir=DIR],[path of hunspell-compatible hyphen dictionaries])],[EVAS_DICTS_HYPHEN_DIR=${withval}][AC_DEFINE_UNQUOTED([EVAS_DICTS_HYPHEN_DIR],["$withval"], [Hunspell-compatible hyphen dictionaries install directory])]) + ### Checks for programs ### Checks for libraries @@ -1609,6 +1612,18 @@ AC_ARG_ENABLE([harfbuzz], ], [want_harfbuzz="no"]) +# Hyphenation +AC_ARG_ENABLE([hyphen], + [AS_HELP_STRING([--enable-hyphen],[enable text hyphenation support. @<:@default=disabled@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + want_hyphen="yes" + else + want_hyphen="no" + fi + ], + [want_hyphen="no"]) + # Egl AC_ARG_ENABLE([egl], [AS_HELP_STRING([--enable-egl],[enable EGL rendering. @<:@default=disabled@:>@])], @@ -2066,6 +2081,25 @@ EFL_EVAL_PKGS([EVAS]) ### Checks for header files +if test "x$want_hyphen" = "xyes" ; then + + EFL_CHECK_LIB_CODE([EVAS], [-lhyphen], [have_fct], [[ + #include + #include + #include + #include + #include + ]], [[ + HyphenDict *dict; + ]]) + + if test "${have_fct}" = "no"; then + AC_MSG_ERROR([Cannot find the hyphen library.]) + else + AC_DEFINE([HAVE_HYPHEN], [1], [have hunspell hyphen support]) + fi +fi + if test "x$have_harfbuzz" = "xyes" ; then CPPFLAGS_SAVE="$CPPFLAGS" diff --git a/src/Makefile_Evas.am b/src/Makefile_Evas.am index e5abebc3d8..9cc57a5a81 100644 --- a/src/Makefile_Evas.am +++ b/src/Makefile_Evas.am @@ -148,6 +148,7 @@ lib/evas/canvas/evas_object_box.c \ lib/evas/canvas/evas_object_table.c \ lib/evas/canvas/evas_object_text.c \ lib/evas/canvas/evas_object_textblock.c \ +lib/evas/canvas/evas_textblock_hyphenation.x \ lib/evas/canvas/evas_object_textgrid.c \ lib/evas/canvas/evas_object_grid.c \ lib/evas/canvas/evas_font_dir.c \ diff --git a/src/lib/evas/canvas/evas_object_textblock.c b/src/lib/evas/canvas/evas_object_textblock.c index c559ed2fa1..7a9aafcb2f 100644 --- a/src/lib/evas/canvas/evas_object_textblock.c +++ b/src/lib/evas/canvas/evas_object_textblock.c @@ -453,6 +453,7 @@ struct _Evas_Object_Textblock_Format Eina_Bool wrap_word : 1; /**< EINA_TRUE if only wraps lines at word boundaries, else EINA_FALSE. */ Eina_Bool wrap_char : 1; /**< EINA_TRUE if wraps at any character, else EINA_FALSE. */ Eina_Bool wrap_mixed : 1; /**< EINA_TRUE if wrap at words if possible, else EINA_FALSE. */ + Eina_Bool wrap_hyphenation : 1; /**< EINA_TRUE if wrap at mixed and hyphenate if possible, else EINA_FALSE. */ Eina_Bool underline : 1; /**< EINA_TRUE if a single line under the text, else EINA_FALSE */ Eina_Bool underline2 : 1; /**< EINA_TRUE if two lines under the text, else EINA_FALSE */ Eina_Bool underline_dash : 1; /**< EINA_TRUE if a dashed line under the text, else EINA_FALSE */ @@ -498,6 +499,7 @@ struct _Evas_Object_Textblock Eina_List *anchors_a; Eina_List *anchors_item; Eina_List *obstacles; + Eina_List *hyphen_items; /* Hyphen items storage to free when clearing lines */ int last_w, last_h; struct { int l, r, t, b; @@ -518,6 +520,7 @@ struct _Evas_Object_Textblock Eina_Bool content_changed : 1; Eina_Bool format_changed : 1; Eina_Bool have_ellipsis : 1; + Eina_Bool hyphenating : 1; Eina_Bool legacy_newline : 1; Eina_Bool inherit_paragraph_direction : 1; Eina_Bool changed_paragraph_direction : 1; @@ -615,6 +618,12 @@ static void _evas_textblock_invalidate_all(Evas_Textblock_Data *o); static void _evas_textblock_cursors_update_offset(const Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Text *n, size_t start, int offset); static void _evas_textblock_cursors_set_node(Evas_Textblock_Data *o, const Evas_Object_Textblock_Node_Text *n, Evas_Object_Textblock_Node_Text *new_node); + +#ifdef HAVE_HYPHEN +/* Hyphenation */ +#include "evas_textblock_hyphenation.x" +#endif + /** selection iterator */ /** * @internal @@ -900,6 +909,7 @@ static const char escape_strings[] = "ª\0" "\xc2\xaa\0" "«\0" "\xc2\xab\0" "¬\0" "\xc2\xac\0" +"­\0" "\xc2\xad\0" "®\0" "\xc2\xae\0" "¯\0" "\xc2\xaf\0" "°\0" "\xc2\xb0\0" @@ -1838,6 +1848,7 @@ _format_command(Evas_Object *eo_obj, Evas_Object_Textblock_Format *fmt, const ch * @li "word" - Only wraps lines at word boundaries * @li "char" - Wraps at any character * @li "mixed" - Wrap at words if possible, if not at any character + * @li "hyphenation" - Hyphenate if possible, if not wrap at words if possible, if not at any character * @li "" - Don't wrap * @code * wrap= @@ -1849,15 +1860,18 @@ _format_command(Evas_Object *eo_obj, Evas_Object_Textblock_Format *fmt, const ch Eina_Bool wrap_word; Eina_Bool wrap_char; Eina_Bool wrap_mixed; + Eina_Bool wrap_hyphenation; } wrap_named[] = { - { "word", 4, 1, 0, 0 }, - { "char", 4, 0, 1, 0 }, - { "mixed", 5, 0, 0, 1 }, - { NULL, 0, 0, 0, 0 } + { "word", 4, 1, 0, 0, 0 }, + { "char", 4, 0, 1, 0, 0 }, + { "mixed", 5, 0, 0, 1, 0 }, + { "hyphenation", 11, 0, 0, 0, 1 }, + { NULL, 0, 0, 0, 0, 0 } }; unsigned int i; - fmt->wrap_word = fmt->wrap_mixed = fmt->wrap_char = 0; + fmt->wrap_word = fmt->wrap_mixed = fmt->wrap_char = + fmt->wrap_hyphenation = 0; for (i = 0; wrap_named[i].param; i++) if (wrap_named[i].len == len && !strcmp(wrap_named[i].param, param)) @@ -1865,8 +1879,19 @@ _format_command(Evas_Object *eo_obj, Evas_Object_Textblock_Format *fmt, const ch fmt->wrap_word = wrap_named[i].wrap_word; fmt->wrap_char = wrap_named[i].wrap_char; fmt->wrap_mixed = wrap_named[i].wrap_mixed; + fmt->wrap_hyphenation = wrap_named[i].wrap_hyphenation; break; } + +#ifdef HAVE_HYPHEN + /* Hyphenating textblocks are registered as "clients", so we load/unload + * the hyphenation dictionaries on-demand. */ + if (fmt->wrap_hyphenation) + { + _dicts_hyphen_update(eo_obj); + } +#endif + } else if (cmd == left_marginstr) { @@ -2562,6 +2587,7 @@ struct _Ctxt Evas_Object_Textblock_Paragraph *paragraphs; Evas_Object_Textblock_Paragraph *par; Evas_Object_Textblock_Line *ln; + Evas_Object_Textblock_Text_Item *hyphen_ti; Eina_List *format_stack; @@ -2927,15 +2953,35 @@ _layout_update_bidi_props(const Evas_Textblock_Data *o, * Free the visual lines in the paragraph (logical items are kept) */ static void -_paragraph_clear(const Evas_Object *obj EINA_UNUSED, +_paragraph_clear(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par) { + Evas_Textblock_Data *o = eo_data_scope_get(obj, MY_CLASS); + while (par->lines) { Evas_Object_Textblock_Line *ln; ln = (Evas_Object_Textblock_Line *) par->lines; par->lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(par->lines), EINA_INLIST_GET(par->lines)); + + /* Could be done better, but it's only when hyphenating and limited + * to number of hyphens created */ + if (o->hyphenating) + { + Evas_Object_Textblock_Text_Item *ti; + Eina_List *i, *i_next; + + EINA_LIST_FOREACH_SAFE(o->hyphen_items, i, i_next, ti) + { + if (ti->parent.ln == ln) + { + o->hyphen_items = eina_list_remove_list(o->hyphen_items, i); + _item_free(obj, NULL, _ITEM(ti)); + } + } + } + _line_free(ln); } } @@ -3594,6 +3640,16 @@ loop_advance: static void _layout_line_advance(Ctxt *c, Evas_Object_Textblock_Format *fmt) { + if (c->hyphen_ti) + { + c->ln->items = (Evas_Object_Textblock_Item *) + eina_inlist_append(EINA_INLIST_GET(c->ln->items), + EINA_INLIST_GET(_ITEM(c->hyphen_ti))); + c->hyphen_ti->parent.ln = c->ln; + c->o->hyphen_items = + eina_list_append(c->o->hyphen_items, c->hyphen_ti); + c->hyphen_ti = NULL; + } _layout_line_finalize(c, fmt); _layout_line_new(c, fmt); } @@ -3647,6 +3703,8 @@ _layout_text_cutoff_get(Ctxt *c, Evas_Object_Textblock_Format *fmt, return -1; } +static Evas_Object_Textblock_Text_Item * _layout_hyphen_item_new(Ctxt *c, const Evas_Object_Textblock_Text_Item *cur_ti); + /** * @internal * Split before cut, and strip if str[cut - 1] is a whitespace. @@ -4364,6 +4422,175 @@ _layout_get_charwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, #define ALLOW_BREAK(i) \ (breaks[i] <= LINEBREAK_ALLOWBREAK) +/* Give a position in text, find the end of word by using Unicode word + * boundary rules */ +static inline size_t +_layout_word_end(const char *breaks, size_t pos, size_t len) +{ + for ( ; (pos < len - 1) && (breaks[pos] != WORDBREAK_BREAK) ; pos++) + ; + return pos; +} + +#define SHY_HYPHEN 0xad + +static int +_layout_get_hyphenationwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, + const Evas_Object_Textblock_Item *it, size_t line_start, + const char *breaks, const char *wordbreaks) +{ + size_t wrap; + size_t orig_wrap; + const Eina_Unicode *str = eina_ustrbuf_string_get( + it->text_node->unicode); + int item_start = it->text_pos; + size_t len = eina_ustrbuf_length_get(it->text_node->unicode); + Eina_Bool try_hyphenate = EINA_FALSE; + + { + int swrap = -1; + int hyphen_swrap = -1; + + if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) + swrap = 0; + else + { + Evas_Coord cw; + + /* Get cutoff */ + swrap = _layout_text_cutoff_get(c, fmt, _ITEM_TEXT(it)); + + /* Get cutoff considering an additional hyphen item */ + cw = c->w; + c->hyphen_ti = _layout_hyphen_item_new(c, _ITEM_TEXT(it)); + c->w -= c->hyphen_ti->parent.w; + hyphen_swrap = _layout_text_cutoff_get(c, fmt, _ITEM_TEXT(it)); + c->w = cw; + + /* Stronger condition than '< 0' for hyphenations */ + if (hyphen_swrap >= 2) + { + try_hyphenate = EINA_TRUE; + } + else + { + _item_free(c->obj, NULL, _ITEM(c->hyphen_ti)); + c->hyphen_ti = NULL; + } + } + + if (swrap < 0) + return -1; + + orig_wrap = wrap = swrap + item_start; + if (try_hyphenate) + { + orig_wrap = wrap = hyphen_swrap + item_start; + } + } + + if (wrap > line_start) + { + Eina_Bool found_hyphen = EINA_FALSE; + size_t word_end; + + if (!_is_white(str[wrap]) || (wrap + 1 == len)) + MOVE_PREV_UNTIL(line_start, wrap); + + /* If there's a breakable point inside the text, scan backwards until + * we find it */ + while (wrap > line_start) + { + /* When iterating back, 'wrap - 1' is the word delimiter, + * but isn't the word's start. The word's start is 'wrap'. */ + if (try_hyphenate && ((wordbreaks[wrap - 1] == WORDBREAK_BREAK) || + (wrap - 1 == line_start))) + { + size_t word_start, word_len; + + word_start = wrap; /* easier to understand if we tag this */ + word_end = _layout_word_end(wordbreaks, wrap, len); + word_len = word_end - word_start + 1; + + if (word_len >= 4) + { + char *hyphens = NULL; + size_t hyphen_off; + size_t i = 0; + size_t pos = 0; + +#ifdef HAVE_HYPHEN + hyphens = _layout_wrap_hyphens_get(str, it->format->font.fdesc->lang, word_start, word_len); +#endif + + /* This only happens one time, if the cutoff is in + * the middle of this word */ + if (word_end > orig_wrap - 1) + { + word_end = orig_wrap - 1; + } + + hyphen_off = word_end - word_start; + + /* We limit our search to the start of the line */ + if (word_start < line_start) + { + word_start = line_start; + } + + for (i = hyphen_off, pos = word_end ; pos > word_start ; i--, pos--) + { + if ((hyphens && (hyphens[i] & 1)) || str[pos] == SHY_HYPHEN) + { + found_hyphen = EINA_TRUE; + break; + } + } + + if (hyphens) + { + free(hyphens); + hyphens = NULL; + } + + /* Rejecting sequences smaller than 2 characters. + * This also works with 'i' initialized to 0 */ + if (found_hyphen) + { + wrap = pos; + break; + } + } + } + + /* SHY-HYPHEN is considered a wordbreak. We don't block it + * internally in ALLOW_BREAK, just here. */ + if (ALLOW_BREAK(wrap) && (str[wrap] != SHY_HYPHEN)) + break; + wrap--; + } + + /* hyphen item cleanup */ + if (!found_hyphen && c->hyphen_ti) + { + _item_free(c->obj, NULL, _ITEM(c->hyphen_ti)); + c->hyphen_ti = NULL; + } + + if ((wrap > line_start) || + ((wrap == line_start) && (ALLOW_BREAK(wrap)) && (wrap < len))) + { + /* We found a suitable wrapping point, break here. */ + MOVE_NEXT_UNTIL(len, wrap); + return wrap; + } + } + + /* Hyphenation falls-back to char wrapping at start of line */ + return _layout_get_charwrap(c, fmt, it, + line_start, breaks); +} + static int _layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt, const Evas_Object_Textblock_Item *it, Eina_Bool mixed_wrap, @@ -4816,6 +5043,7 @@ _layout_par(Ctxt *c) int ret = 0; int wrap = -1; char *line_breaks = NULL; + char *word_breaks = NULL; /* Obstacles logic */ Eina_Bool handle_obstacles = EINA_FALSE; @@ -4983,7 +5211,7 @@ _layout_par(Ctxt *c) ((2 * it->h + c->y > c->h - c->o->style_pad.t - c->o->style_pad.b) || (!it->format->wrap_word && !it->format->wrap_char && - !it->format->wrap_mixed))) + !it->format->wrap_mixed && !it->format->wrap_hyphenation))) { _layout_handle_ellipsis(c, it, i); ret = 1; @@ -4992,7 +5220,7 @@ _layout_par(Ctxt *c) /* If we want to wrap and it's worth checking for wrapping * (i.e there's actually text). */ else if (((wrap > 0) || it->format->wrap_word || it->format->wrap_char || - it->format->wrap_mixed) && it->text_node) + it->format->wrap_mixed || it->format->wrap_hyphenation) && it->text_node) { size_t line_start; size_t it_len; @@ -5006,7 +5234,8 @@ _layout_par(Ctxt *c) if (!line_breaks) { /* Only relevant in those cases */ - if (it->format->wrap_word || it->format->wrap_mixed) + if (it->format->wrap_word || it->format->wrap_mixed || + it->format->wrap_hyphenation) { const char *lang; lang = (it->format->font.fdesc) ? @@ -5021,6 +5250,22 @@ _layout_par(Ctxt *c) len, lang, line_breaks); } } + + if (!word_breaks && it->format->wrap_hyphenation) + { + const char *lang; + lang = (it->format->font.fdesc) ? + it->format->font.fdesc->lang : ""; + size_t len = + eina_ustrbuf_length_get( + it->text_node->unicode); + word_breaks = malloc(len); + set_wordbreaks_utf32((const utf32_t *) + eina_ustrbuf_string_get( + it->text_node->unicode), + len, lang, word_breaks); + } + if (c->ln->items) line_start = c->ln->items->text_pos; else @@ -5059,6 +5304,9 @@ _layout_par(Ctxt *c) else if (it->format->wrap_mixed) wrap = _layout_get_mixedwrap(c, it->format, it, line_start, line_breaks, allow_scan_fwd); + else if (it->format->wrap_hyphenation) + wrap = _layout_get_hyphenationwrap(c, it->format, it, + line_start, line_breaks, word_breaks); else wrap = -1; c->w = save_cw; @@ -5233,6 +5481,8 @@ _layout_par(Ctxt *c) end: if (line_breaks) free(line_breaks); + if (word_breaks) + free(word_breaks); #ifdef BIDI_SUPPORT if (c->par->bidi_props) @@ -5641,6 +5891,7 @@ _layout(const Evas_Object *eo_obj, int w, int h, int *w_ret, int *h_ret) c->ln = NULL; c->width_changed = (obj->cur->geometry.w != o->last_w); c->obs_infos = NULL; + c->hyphen_ti = NULL; /* Start of logical layout creation */ /* setup default base style */ @@ -7200,6 +7451,47 @@ _layout_item_obstacle_get(Ctxt *c, Evas_Object_Textblock_Item *it) return min_obs; } +/* Hyphenation (since 1.17) */ +static Evas_Object_Textblock_Text_Item * +_layout_hyphen_item_new(Ctxt *c, const Evas_Object_Textblock_Text_Item *cur_ti) +{ + /* U+2010 - Unicode HYPHEN */ + const Eina_Unicode _hyphen_str[2] = { 0x2010, '\0' }; + Evas_Object_Textblock_Text_Item *hyphen_ti; + Evas_Script_Type script; + Evas_Font_Instance *script_fi = NULL, *cur_fi; + size_t len = 1; /* The length of _hyphen_str */ + + if (c->hyphen_ti) + { + _item_free(c->obj, NULL, _ITEM(c->hyphen_ti)); + } + c->hyphen_ti = hyphen_ti = _layout_text_item_new(c, cur_ti->parent.format); + hyphen_ti->parent.text_node = cur_ti->parent.text_node; + hyphen_ti->parent.text_pos = cur_ti->parent.text_pos + cur_ti->text_props.text_len - 1; + script = evas_common_language_script_type_get(_hyphen_str, len); + + evas_common_text_props_bidi_set(&hyphen_ti->text_props, + c->par->bidi_props, hyphen_ti->parent.text_pos); + evas_common_text_props_script_set (&hyphen_ti->text_props, script); + + if (hyphen_ti->parent.format->font.font) + { + Evas_Object_Protected_Data *obj = eo_data_scope_get(c->obj, EVAS_OBJECT_CLASS); + /* It's only 1 char anyway, we don't need the run end. */ + (void) ENFN->font_run_end_get(ENDT, + hyphen_ti->parent.format->font.font, &script_fi, &cur_fi, + script, _hyphen_str, len); + + ENFN->font_text_props_info_create(ENDT, + cur_fi, _hyphen_str, &hyphen_ti->text_props, + c->par->bidi_props, hyphen_ti->parent.text_pos, len, EVAS_TEXT_PROPS_MODE_SHAPE); + } + + _text_item_update_sizes(c, hyphen_ti); + return hyphen_ti; +} + /* cursors */ /** @@ -11566,6 +11858,14 @@ evas_object_textblock_free(Evas_Object *eo_obj) /* remove obstacles */ _obstacles_free(eo_obj, o); + +#ifdef HAVE_HYPHEN + /* Hyphenation */ + if (o->hyphenating) + { + _dicts_hyphen_detach(); + } +#endif } static void diff --git a/src/lib/evas/canvas/evas_textblock_hyphenation.x b/src/lib/evas/canvas/evas_textblock_hyphenation.x new file mode 100644 index 0000000000..930b5afa7a --- /dev/null +++ b/src/lib/evas/canvas/evas_textblock_hyphenation.x @@ -0,0 +1,148 @@ +#ifndef EVAS_TEXTBLOCK_HYPHENATION_H +#define EVAS_TEXTBLOCK_HYPHENATION_H +#ifdef HAVE_HYPHEN +#include + +typedef struct +{ + const char *lang; + HyphenDict *dict; +} Dict_Hyphen; + +/* Hyphenation dictionaries */ +static Dict_Hyphen _dicts_hyphen[64]; +static Eina_Bool _dicts_hyphen_init = EINA_FALSE; +static size_t _hyphens_num = 0; +static size_t _hyphen_clients = 0; + +static void +_dicts_hyphen_update(Eo *eo_obj) +{ + Eina_Iterator *it; + Eina_File_Direct_Info *dir; + + Evas_Textblock_Data *o = eo_data_scope_get(eo_obj, MY_CLASS); + + if (!o->hyphenating) + { + _hyphen_clients++; + o->hyphenating = EINA_TRUE; + } + + if (_dicts_hyphen_init) return; + + it = eina_file_direct_ls(EVAS_DICTS_HYPHEN_DIR); + if (!it) + { + ERR("Couldn't list files in hyphens path: %s\n", EVAS_DICTS_HYPHEN_DIR); + return; + } + + _dicts_hyphen_init = EINA_TRUE; + + /* The following is based on how files are installed in arch linux: + * the files are in the pattern of "hyph_xx_XX.dic" (e.g. hyph_en_US.dic). + * We are actually trying a bit more in case these are installed in another + * name. We assume that they probably end in "xx_XX.dic" anyway. */ + EINA_ITERATOR_FOREACH(it, dir) + { + const char *file = dir->path + dir->name_start; + char *prefix_off; /* 'hyph_' prefix (may be in some distros) */ + char *dic_off; /* '.dic' file extension offset */ + void *dict; + + /* Check a few assumptions and reject if aren't met. */ + prefix_off = strstr(file, "hyph_"); + dic_off = strrchr(file, '.'); + if (!dic_off || ((size_t) (dic_off - file) + 4 != dir->name_length) || + (dic_off - file < 5) || + ((dic_off - file > 0) && !prefix_off) || + strncmp(dic_off, ".dic", 4)) + { + continue; + } + + dict = hnj_hyphen_load(dir->path); + if (!dict) + { + ERR("Couldn't load hyphen dictionary: %s\n", dic_off - 5); + continue; + } + _dicts_hyphen[_hyphens_num].lang = strndup(dic_off - 5, 5); + _dicts_hyphen[_hyphens_num++].dict = dict; + } + + if (it) eina_iterator_free(it); +} + +static void +_dicts_hyphen_free(void) +{ + if (_hyphens_num == 0) return; + + for (size_t i = 0; i < _hyphens_num; i++) + { + hnj_hyphen_free(_dicts_hyphen[i].dict); + } + + _hyphens_num = 0; + _dicts_hyphen_init = EINA_FALSE; +} + +static inline void +_dicts_hyphen_detach(void) +{ + _hyphen_clients--; + if (_hyphen_clients == 0) _dicts_hyphen_free(); +} + +/* Returns the hyphen dictionary that matches the given language + * string. The string should be in the format xx_XX e.g. en_US */ +static inline void * +_hyphen_dict_get_from_lang(const char *lang) +{ + if (!lang || !(*lang)) + { + if (!lang) lang = evas_common_language_from_locale_full_get(); + if (!lang || !(*lang)) return NULL; + } + + for (size_t i = 0; i < _hyphens_num; i++) + { + if (!strcmp(_dicts_hyphen[i].lang, lang)) + { + return _dicts_hyphen[i].dict; + } + } + return NULL; +} + +static char * +_layout_wrap_hyphens_get(const Eina_Unicode *text, const char *lang, + int word_start, int word_len) +{ + char *utf8; + int utf8_len; /* length of word */ + char *hyphens; + char **rep = NULL; + int *pos = NULL; + int *cut = NULL; + void *dict; + + dict = _hyphen_dict_get_from_lang(lang); + if (!dict) + { + ERR("Couldn't find matching dictionary and couldn't fallback to locale %s\n", lang); + return NULL; + } + + utf8 = eina_unicode_unicode_to_utf8_range( + text + word_start, word_len, &utf8_len); + hyphens = malloc(sizeof(char) * (word_len + 5)); + hnj_hyphen_hyphenate2(dict, utf8, word_len, hyphens, NULL, &rep, &pos, &cut); + free(utf8); + return hyphens; +} + +#endif //HAVE_HYPHEN +#endif //EVAS_TEXTBLOCK_HYPHENATION_H_