forked from enlightenment/efl
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
This commit is contained in:
parent
89ef4b70b8
commit
40dfc4a45d
34
configure.ac
34
configure.ac
|
@ -878,6 +878,9 @@ EINA_CONFIG([MAGIC_DEBUG], [test "x${have_magic_debug}" = "xyes"])
|
||||||
AC_ARG_WITH([xattr-tests-path],
|
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])])
|
[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 programs
|
||||||
|
|
||||||
### Checks for libraries
|
### Checks for libraries
|
||||||
|
@ -1609,6 +1612,18 @@ AC_ARG_ENABLE([harfbuzz],
|
||||||
],
|
],
|
||||||
[want_harfbuzz="no"])
|
[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
|
# Egl
|
||||||
AC_ARG_ENABLE([egl],
|
AC_ARG_ENABLE([egl],
|
||||||
[AS_HELP_STRING([--enable-egl],[enable EGL rendering. @<:@default=disabled@:>@])],
|
[AS_HELP_STRING([--enable-egl],[enable EGL rendering. @<:@default=disabled@:>@])],
|
||||||
|
@ -2066,6 +2081,25 @@ EFL_EVAL_PKGS([EVAS])
|
||||||
|
|
||||||
### Checks for header files
|
### Checks for header files
|
||||||
|
|
||||||
|
if test "x$want_hyphen" = "xyes" ; then
|
||||||
|
|
||||||
|
EFL_CHECK_LIB_CODE([EVAS], [-lhyphen], [have_fct], [[
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <hyphen.h>
|
||||||
|
]], [[
|
||||||
|
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
|
if test "x$have_harfbuzz" = "xyes" ; then
|
||||||
|
|
||||||
CPPFLAGS_SAVE="$CPPFLAGS"
|
CPPFLAGS_SAVE="$CPPFLAGS"
|
||||||
|
|
|
@ -148,6 +148,7 @@ lib/evas/canvas/evas_object_box.c \
|
||||||
lib/evas/canvas/evas_object_table.c \
|
lib/evas/canvas/evas_object_table.c \
|
||||||
lib/evas/canvas/evas_object_text.c \
|
lib/evas/canvas/evas_object_text.c \
|
||||||
lib/evas/canvas/evas_object_textblock.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_textgrid.c \
|
||||||
lib/evas/canvas/evas_object_grid.c \
|
lib/evas/canvas/evas_object_grid.c \
|
||||||
lib/evas/canvas/evas_font_dir.c \
|
lib/evas/canvas/evas_font_dir.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_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_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_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 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 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 */
|
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_a;
|
||||||
Eina_List *anchors_item;
|
Eina_List *anchors_item;
|
||||||
Eina_List *obstacles;
|
Eina_List *obstacles;
|
||||||
|
Eina_List *hyphen_items; /* Hyphen items storage to free when clearing lines */
|
||||||
int last_w, last_h;
|
int last_w, last_h;
|
||||||
struct {
|
struct {
|
||||||
int l, r, t, b;
|
int l, r, t, b;
|
||||||
|
@ -518,6 +520,7 @@ struct _Evas_Object_Textblock
|
||||||
Eina_Bool content_changed : 1;
|
Eina_Bool content_changed : 1;
|
||||||
Eina_Bool format_changed : 1;
|
Eina_Bool format_changed : 1;
|
||||||
Eina_Bool have_ellipsis : 1;
|
Eina_Bool have_ellipsis : 1;
|
||||||
|
Eina_Bool hyphenating : 1;
|
||||||
Eina_Bool legacy_newline : 1;
|
Eina_Bool legacy_newline : 1;
|
||||||
Eina_Bool inherit_paragraph_direction : 1;
|
Eina_Bool inherit_paragraph_direction : 1;
|
||||||
Eina_Bool changed_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_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);
|
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 */
|
/** selection iterator */
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
@ -900,6 +909,7 @@ static const char escape_strings[] =
|
||||||
"ª\0" "\xc2\xaa\0"
|
"ª\0" "\xc2\xaa\0"
|
||||||
"«\0" "\xc2\xab\0"
|
"«\0" "\xc2\xab\0"
|
||||||
"¬\0" "\xc2\xac\0"
|
"¬\0" "\xc2\xac\0"
|
||||||
|
"­\0" "\xc2\xad\0"
|
||||||
"®\0" "\xc2\xae\0"
|
"®\0" "\xc2\xae\0"
|
||||||
"¯\0" "\xc2\xaf\0"
|
"¯\0" "\xc2\xaf\0"
|
||||||
"°\0" "\xc2\xb0\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 "word" - Only wraps lines at word boundaries
|
||||||
* @li "char" - Wraps at any character
|
* @li "char" - Wraps at any character
|
||||||
* @li "mixed" - Wrap at words if possible, if not 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
|
* @li "" - Don't wrap
|
||||||
* @code
|
* @code
|
||||||
* wrap=<value or preset>
|
* wrap=<value or preset>
|
||||||
|
@ -1849,15 +1860,18 @@ _format_command(Evas_Object *eo_obj, Evas_Object_Textblock_Format *fmt, const ch
|
||||||
Eina_Bool wrap_word;
|
Eina_Bool wrap_word;
|
||||||
Eina_Bool wrap_char;
|
Eina_Bool wrap_char;
|
||||||
Eina_Bool wrap_mixed;
|
Eina_Bool wrap_mixed;
|
||||||
|
Eina_Bool wrap_hyphenation;
|
||||||
} wrap_named[] = {
|
} wrap_named[] = {
|
||||||
{ "word", 4, 1, 0, 0 },
|
{ "word", 4, 1, 0, 0, 0 },
|
||||||
{ "char", 4, 0, 1, 0 },
|
{ "char", 4, 0, 1, 0, 0 },
|
||||||
{ "mixed", 5, 0, 0, 1 },
|
{ "mixed", 5, 0, 0, 1, 0 },
|
||||||
{ NULL, 0, 0, 0, 0 }
|
{ "hyphenation", 11, 0, 0, 0, 1 },
|
||||||
|
{ NULL, 0, 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
unsigned int i;
|
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++)
|
for (i = 0; wrap_named[i].param; i++)
|
||||||
if (wrap_named[i].len == len &&
|
if (wrap_named[i].len == len &&
|
||||||
!strcmp(wrap_named[i].param, param))
|
!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_word = wrap_named[i].wrap_word;
|
||||||
fmt->wrap_char = wrap_named[i].wrap_char;
|
fmt->wrap_char = wrap_named[i].wrap_char;
|
||||||
fmt->wrap_mixed = wrap_named[i].wrap_mixed;
|
fmt->wrap_mixed = wrap_named[i].wrap_mixed;
|
||||||
|
fmt->wrap_hyphenation = wrap_named[i].wrap_hyphenation;
|
||||||
break;
|
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)
|
else if (cmd == left_marginstr)
|
||||||
{
|
{
|
||||||
|
@ -2562,6 +2587,7 @@ struct _Ctxt
|
||||||
Evas_Object_Textblock_Paragraph *paragraphs;
|
Evas_Object_Textblock_Paragraph *paragraphs;
|
||||||
Evas_Object_Textblock_Paragraph *par;
|
Evas_Object_Textblock_Paragraph *par;
|
||||||
Evas_Object_Textblock_Line *ln;
|
Evas_Object_Textblock_Line *ln;
|
||||||
|
Evas_Object_Textblock_Text_Item *hyphen_ti;
|
||||||
|
|
||||||
|
|
||||||
Eina_List *format_stack;
|
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)
|
* Free the visual lines in the paragraph (logical items are kept)
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
_paragraph_clear(const Evas_Object *obj EINA_UNUSED,
|
_paragraph_clear(const Evas_Object *obj,
|
||||||
Evas_Object_Textblock_Paragraph *par)
|
Evas_Object_Textblock_Paragraph *par)
|
||||||
{
|
{
|
||||||
|
Evas_Textblock_Data *o = eo_data_scope_get(obj, MY_CLASS);
|
||||||
|
|
||||||
while (par->lines)
|
while (par->lines)
|
||||||
{
|
{
|
||||||
Evas_Object_Textblock_Line *ln;
|
Evas_Object_Textblock_Line *ln;
|
||||||
|
|
||||||
ln = (Evas_Object_Textblock_Line *) par->lines;
|
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));
|
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);
|
_line_free(ln);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3594,6 +3640,16 @@ loop_advance:
|
||||||
static void
|
static void
|
||||||
_layout_line_advance(Ctxt *c, Evas_Object_Textblock_Format *fmt)
|
_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_finalize(c, fmt);
|
||||||
_layout_line_new(c, fmt);
|
_layout_line_new(c, fmt);
|
||||||
}
|
}
|
||||||
|
@ -3647,6 +3703,8 @@ _layout_text_cutoff_get(Ctxt *c, Evas_Object_Textblock_Format *fmt,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Evas_Object_Textblock_Text_Item * _layout_hyphen_item_new(Ctxt *c, const Evas_Object_Textblock_Text_Item *cur_ti);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
* Split before cut, and strip if str[cut - 1] is a whitespace.
|
* 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) \
|
#define ALLOW_BREAK(i) \
|
||||||
(breaks[i] <= LINEBREAK_ALLOWBREAK)
|
(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
|
static int
|
||||||
_layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt,
|
_layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt,
|
||||||
const Evas_Object_Textblock_Item *it, Eina_Bool mixed_wrap,
|
const Evas_Object_Textblock_Item *it, Eina_Bool mixed_wrap,
|
||||||
|
@ -4816,6 +5043,7 @@ _layout_par(Ctxt *c)
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
int wrap = -1;
|
int wrap = -1;
|
||||||
char *line_breaks = NULL;
|
char *line_breaks = NULL;
|
||||||
|
char *word_breaks = NULL;
|
||||||
|
|
||||||
/* Obstacles logic */
|
/* Obstacles logic */
|
||||||
Eina_Bool handle_obstacles = EINA_FALSE;
|
Eina_Bool handle_obstacles = EINA_FALSE;
|
||||||
|
@ -4983,7 +5211,7 @@ _layout_par(Ctxt *c)
|
||||||
((2 * it->h + c->y >
|
((2 * it->h + c->y >
|
||||||
c->h - c->o->style_pad.t - c->o->style_pad.b) ||
|
c->h - c->o->style_pad.t - c->o->style_pad.b) ||
|
||||||
(!it->format->wrap_word && !it->format->wrap_char &&
|
(!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);
|
_layout_handle_ellipsis(c, it, i);
|
||||||
ret = 1;
|
ret = 1;
|
||||||
|
@ -4992,7 +5220,7 @@ _layout_par(Ctxt *c)
|
||||||
/* If we want to wrap and it's worth checking for wrapping
|
/* If we want to wrap and it's worth checking for wrapping
|
||||||
* (i.e there's actually text). */
|
* (i.e there's actually text). */
|
||||||
else if (((wrap > 0) || it->format->wrap_word || it->format->wrap_char ||
|
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 line_start;
|
||||||
size_t it_len;
|
size_t it_len;
|
||||||
|
@ -5006,7 +5234,8 @@ _layout_par(Ctxt *c)
|
||||||
if (!line_breaks)
|
if (!line_breaks)
|
||||||
{
|
{
|
||||||
/* Only relevant in those cases */
|
/* 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;
|
const char *lang;
|
||||||
lang = (it->format->font.fdesc) ?
|
lang = (it->format->font.fdesc) ?
|
||||||
|
@ -5021,6 +5250,22 @@ _layout_par(Ctxt *c)
|
||||||
len, lang, line_breaks);
|
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)
|
if (c->ln->items)
|
||||||
line_start = c->ln->items->text_pos;
|
line_start = c->ln->items->text_pos;
|
||||||
else
|
else
|
||||||
|
@ -5059,6 +5304,9 @@ _layout_par(Ctxt *c)
|
||||||
else if (it->format->wrap_mixed)
|
else if (it->format->wrap_mixed)
|
||||||
wrap = _layout_get_mixedwrap(c, it->format, it,
|
wrap = _layout_get_mixedwrap(c, it->format, it,
|
||||||
line_start, line_breaks, allow_scan_fwd);
|
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
|
else
|
||||||
wrap = -1;
|
wrap = -1;
|
||||||
c->w = save_cw;
|
c->w = save_cw;
|
||||||
|
@ -5233,6 +5481,8 @@ _layout_par(Ctxt *c)
|
||||||
end:
|
end:
|
||||||
if (line_breaks)
|
if (line_breaks)
|
||||||
free(line_breaks);
|
free(line_breaks);
|
||||||
|
if (word_breaks)
|
||||||
|
free(word_breaks);
|
||||||
|
|
||||||
#ifdef BIDI_SUPPORT
|
#ifdef BIDI_SUPPORT
|
||||||
if (c->par->bidi_props)
|
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->ln = NULL;
|
||||||
c->width_changed = (obj->cur->geometry.w != o->last_w);
|
c->width_changed = (obj->cur->geometry.w != o->last_w);
|
||||||
c->obs_infos = NULL;
|
c->obs_infos = NULL;
|
||||||
|
c->hyphen_ti = NULL;
|
||||||
|
|
||||||
/* Start of logical layout creation */
|
/* Start of logical layout creation */
|
||||||
/* setup default base style */
|
/* setup default base style */
|
||||||
|
@ -7200,6 +7451,47 @@ _layout_item_obstacle_get(Ctxt *c, Evas_Object_Textblock_Item *it)
|
||||||
return min_obs;
|
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 */
|
/* cursors */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11566,6 +11858,14 @@ evas_object_textblock_free(Evas_Object *eo_obj)
|
||||||
|
|
||||||
/* remove obstacles */
|
/* remove obstacles */
|
||||||
_obstacles_free(eo_obj, o);
|
_obstacles_free(eo_obj, o);
|
||||||
|
|
||||||
|
#ifdef HAVE_HYPHEN
|
||||||
|
/* Hyphenation */
|
||||||
|
if (o->hyphenating)
|
||||||
|
{
|
||||||
|
_dicts_hyphen_detach();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
#ifndef EVAS_TEXTBLOCK_HYPHENATION_H
|
||||||
|
#define EVAS_TEXTBLOCK_HYPHENATION_H
|
||||||
|
#ifdef HAVE_HYPHEN
|
||||||
|
#include <hyphen.h>
|
||||||
|
|
||||||
|
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_
|
Loading…
Reference in New Issue