Evas/Textblock: add support for ellipsis values
authorDaniel Hirt <daniel.hirt@samsung.com>
Thu, 29 May 2014 14:46:45 +0000 (15:46 +0100)
committerTom Hacohen <tom@stosb.com>
Thu, 29 May 2014 14:52:01 +0000 (15:52 +0100)
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

src/lib/evas/canvas/evas_object_textblock.c
src/tests/evas/evas_test_textblock.c

index 427a1ad..c6d9770 100644 (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)
index 8b23393..4c48b60 100644 (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);