Evas/Textblock: add support for ellipsis values

Summary:
This enables textblock to support more values other than 1.0.
For 0 <= ellipsis < 1.0, it splits the text such that it fits the
textblock's width. The ellipsis is relatively position according to the
ellipsis value, and characters are removed also relatively.
For example, a value of 0.5 will position the ellipsis right in the
center of the textblock's width, while removing characters equally right
and left from the center.

Basic approach to this feature was to do some work before the layout
process. We calculate the expected total width of the items, and by how
much we exceed from the textblock's width. Afterwards is it just some
careful work to set the boundaries of the width we want to cut, and
deciding which characters we need to removed to fulfill this requirement.
The rest is splitting the text and visually-removing the part we
need to cut.
This is all handled before any logical lines are created, so the
_layout_par function remains almost intact. A designated _ellip_prev_it
field in the Paragraph struct instructs after which item we place the
ellipsis item, if at all.

Note that we keep the fast path for ellipsis 1.0, as heavier work needs
to be done for the other values.

Added tests to evas_suite for a range of ellipsis values.

Also, multiline is unsupported at the moment.
@feature

Test Plan: Anything that uses Evas Textblock (single-line, please)

Reviewers: tasn, id213sin, raster

CC: cedric, JackDanielZ, raster

Differential Revision: https://phab.enlightenment.org/D905
This commit is contained in:
Daniel Hirt 2014-05-29 15:46:45 +01:00 committed by Tom Hacohen
parent dc3178404f
commit 177135ff0d
2 changed files with 204 additions and 9 deletions

View File

@ -487,6 +487,7 @@ struct _Evas_Object_Textblock
Evas_Object_Textblock_Paragraph *par_index[TEXTBLOCK_PAR_INDEX_SIZE];
Evas_Object_Textblock_Text_Item *ellip_ti;
Eina_List *ellip_prev_it; /* item that is placed before ellipsis item (0.0 <= ellipsis < 1.0), if required */
Eina_List *anchors_a;
Eina_List *anchors_item;
int last_w, last_h;
@ -2910,6 +2911,7 @@ _layout_format_push(Ctxt *c, Evas_Object_Textblock_Format *fmt,
fmt->underline_dash_gap = 2;
fmt->linerelgap = 0.0;
fmt->password = 1;
fmt->ellipsis = -1;
}
return fmt;
}
@ -4471,6 +4473,142 @@ _layout_paragraph_render(Evas_Textblock_Data *o,
#endif
}
/* calculates items width in current paragraph */
static inline Evas_Coord
_calc_items_width(Ctxt *c)
{
Evas_Object_Textblock_Item *it, *last_it = NULL;
Eina_List *i;
Evas_Coord w = 0;
if (!c->par->logical_items)
return 0;
EINA_LIST_FOREACH(c->par->logical_items, i, it)
{
w += it->adv;
last_it = it;
}
//reaching this point when it is the last item
if (last_it)
w += last_it->w - last_it->adv;
return w;
}
static inline int
_item_get_cutoff(Ctxt *c, Evas_Object_Textblock_Item *it, Evas_Coord x)
{
int pos = -1;
Evas_Object_Textblock_Text_Item *ti;
Evas_Object_Protected_Data *obj = eo_data_scope_get(c->obj, EVAS_OBJ_CLASS);
ti = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(it) : NULL;
if (ti && ti->parent.format->font.font)
{
pos = ENFN->font_last_up_to_pos(ENDT, ti->parent.format->font.font,
&ti->text_props, x, 0);
}
return pos;
}
/**
* @internal
* This handles ellipsis prior most of the work in _layout_par.
* Currently it is here to handle all value in the range of 0.0 to 0.9999 (<1).
* It starts by getting the total width of items, and calculates the 'block' of
* text that needs to be removed i.e. sets low and high boundaries
* of that block.
* All text items that intersect this block will be cut: the edge items (ones
* that don't intersect in whole) will be split, and the rest are set to be
* visually-deleted.
* Note that a special case for visible format items does not
* split them, but instead just visually-deletes them (because there are no
* characters to split).
*/
static inline void
_layout_par_ellipsis_items(Ctxt *c, double ellip)
{
Evas_Object_Textblock_Item *it;
Evas_Object_Textblock_Text_Item *ellip_ti;
Eina_List *i, *j;
Evas_Coord items_width, exceed, items_cut;
Evas_Coord l, h, off;
int pos;
c->o->ellip_prev_it = NULL;
/* calc exceed amount */
items_width = _calc_items_width(c);
exceed = items_width - (c->w - c->o->style_pad.l - c->o->style_pad.r
- c->marginl - c->marginr);
if (exceed <= 0)
return;
{
Evas_Object_Textblock_Item *first_it =
_ITEM(eina_list_data_get(c->par->logical_items));
ellip_ti = _layout_ellipsis_item_new(c, first_it);
}
exceed += ellip_ti->parent.adv;
items_cut = items_width * ellip;
l = items_cut - (exceed * ellip);
h = l + exceed; //h = items_cut - (exceed * (1 - ellip))
off = 0;
/* look for the item that is being cut by the lower boundary */
i = c->par->logical_items;
EINA_LIST_FOREACH(c->par->logical_items, i, it)
{
if (it->w > (l - off))
break;
off += it->adv;
}
c->o->ellip_prev_it = i;
if (it) _layout_ellipsis_item_new(c, it);
pos = (it && it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ?
(_item_get_cutoff(c, it, l - off)) : -1;
if (pos >= 0)
{
_layout_item_text_split_strip_white(c, _ITEM_TEXT(it), i, pos);
off += it->adv;
i = eina_list_next(i);
}
/* look for the item that is being cut by the upper boundary */
EINA_LIST_FOREACH(i, j, it)
{
if (it->w > (h - off))
break;
off += it->adv;
/* if item is not being cut by the upper boundary, then
* it is contained in the area that we are supposed to
* visually remove */
it->visually_deleted = EINA_TRUE;
}
pos = (it && it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ?
(_item_get_cutoff(c, it, h - off)) : -1;
if (pos >= 0)
_layout_item_text_split_strip_white(c, _ITEM_TEXT(it), j, pos + 1);
if (it)
it->visually_deleted = EINA_TRUE;
}
static inline void
_layout_par_append_ellipsis(Ctxt *c)
{
Evas_Object_Textblock_Text_Item *ellip_ti = c->o->ellip_ti;
c->ln->items = (Evas_Object_Textblock_Item *)
eina_inlist_append(EINA_INLIST_GET(c->ln->items),
EINA_INLIST_GET(_ITEM(ellip_ti)));
ellip_ti->parent.ln = c->ln;
c->x += ellip_ti->parent.adv;
}
/* 0 means go ahead, 1 means break without an error, 2 means
* break with an error, should probably clean this a bit (enum/macro)
* FIXME ^ */
@ -4550,6 +4688,18 @@ _layout_par(Ctxt *c)
_layout_line_new(c, it->format);
/* We walk on our own because we want to be able to add items from
* inside the list and then walk them on the next iteration. */
/* TODO: We need to consider where ellipsis is used in the current text.
Currently, we assume that ellipsis is at the beginning of the
paragraph. This is a safe assumption for now, as other usages
seem a bit unnatural.*/
{
double ellip;
ellip = it->format->ellipsis;
if ((0 <= ellip) && (ellip < 1.0))
_layout_par_ellipsis_items(c, ellip);
}
for (i = c->par->logical_items ; i ; )
{
Evas_Coord prevdescent = 0, prevascent = 0;
@ -4559,6 +4709,10 @@ _layout_par(Ctxt *c)
/* Skip visually deleted items */
if (it->visually_deleted)
{
//one more chance for ellipsis special cases
if (c->o->ellip_prev_it == i)
_layout_par_append_ellipsis(c);
i = eina_list_next(i);
continue;
}
@ -4596,7 +4750,11 @@ _layout_par(Ctxt *c)
c->marginl - c->marginr)) || (wrap > 0)))
{
/* Handle ellipsis here. If we don't have more width left
* and no height left, or no more width left and no wrapping. */
* and no height left, or no more width left and no wrapping.
* Note that this is only for ellipsis == 1.0, and is treated in a
* fast path.
* Other values of 0.0 <= ellipsis < 1.0 are handled in
* _layout_par_ellipsis_items */
if ((it->format->ellipsis == 1.0) && (c->h >= 0) &&
((2 * it->h + c->y >
c->h - c->o->style_pad.t - c->o->style_pad.b) ||
@ -4766,6 +4924,8 @@ _layout_par(Ctxt *c)
}
}
c->x += it->adv;
if (c->o->ellip_prev_it == i)
_layout_par_append_ellipsis(c);
i = eina_list_next(i);
}
if (adv_line)

View File

@ -1733,19 +1733,54 @@ START_TEST(evas_textblock_wrapping)
/* Ellipsis */
evas_object_textblock_text_markup_set(tb, "aaaaaaaaaa");
evas_textblock_cursor_format_prepend(cur, "+ ellipsis=1.0");
evas_object_textblock_size_native_get(tb, &nw, &nh);
evas_object_resize(tb, nw / 2, nh);
evas_object_textblock_size_formatted_get(tb, &w, &h);
fail_if((w > (nw / 2)) || (h != nh));
evas_object_textblock_text_markup_set(tb, "aaaaaaaaaaaaaaaaaa<br/>b");
evas_textblock_cursor_format_prepend(cur, "+ ellipsis=1.0 wrap=word");
evas_object_textblock_size_native_get(tb, &nw, &nh);
evas_object_resize(tb, nw / 2, nh * 2);
evas_object_textblock_size_formatted_get(tb, &w, &h);
fail_if(w > (nw / 2));
ck_assert_int_le(w, (nw / 2));
{
double ellip;
for(ellip = 0.0; ellip <= 1.0; ellip = ellip + 0.1)
{
char buf[128];
Evas_Coord w1, h1, w2, h2;
sprintf(buf, "+ ellipsis=%f", ellip);
evas_object_textblock_text_markup_set(tb, "aaaaaaaaaa");
evas_textblock_cursor_format_prepend(cur, buf);
evas_object_textblock_size_native_get(tb, &nw, &nh);
evas_object_resize(tb, nw / 2, nh);
evas_object_textblock_size_formatted_get(tb, &w, &h);
ck_assert_int_le(w, (nw / 2));
ck_assert_int_eq(h, nh);
evas_object_textblock_text_markup_set(tb, "aaaaaaaaaa");
evas_textblock_cursor_format_prepend(cur, buf);
evas_object_textblock_size_native_get(tb, &nw, &nh);
evas_object_resize(tb, nw, nh);
evas_object_textblock_size_formatted_get(tb, &w, &h);
evas_object_resize(tb, nw / 2, nh);
evas_object_textblock_size_formatted_get(tb, &w1, &h1);
evas_object_resize(tb, nw, nh);
evas_object_textblock_size_formatted_get(tb, &w2, &h2);
ck_assert_int_eq(w, w2);
ck_assert_int_eq(h, h2);
sprintf(buf, "+ ellipsis=%f", ellip);
evas_object_textblock_text_markup_set(tb,
"the<tab>quick brown fox"
"jumps<tab> over the<tab> lazy dog"
);
evas_textblock_cursor_format_prepend(cur, buf);
evas_object_textblock_size_native_get(tb, &nw, &nh);
evas_object_resize(tb, nw / 2, nh);
evas_object_textblock_size_formatted_get(tb, &w, &h);
ck_assert_int_le(w, (nw / 2));
ck_assert_int_eq(h, nh);
}
}
/* Word wrap ending with whites. */
evas_object_resize(tb, 322, 400);