Evas textblock: split the visual layouting to two stages:
authorTom Hacohen <tom@stosb.com>
Sun, 30 Jan 2011 10:41:42 +0000 (10:41 +0000)
committerTom Hacohen <tom@stosb.com>
Sun, 30 Jan 2011 10:41:42 +0000 (10:41 +0000)
1. Split to logical layout units.
2. Order the logical layout units in a visual way.
This lets us cache the results of the first part (unless the text changes), which makes everything a lot faster in the OT case, and quite faster in the regular case.
There are still some issues unresolved in this commit that will be resolved in future commits:
1. No support for async rendering - yes, I forgot I'm supposed to support that.
2. native_size should probably be calculated in another way because the current one is slow and if we already have the logical items it should be a piece of cake...

SVN revision: 56504

legacy/evas/src/lib/canvas/evas_object_textblock.c
legacy/evas/src/lib/engines/common/evas_font_ot.c
legacy/evas/src/lib/engines/common/evas_font_ot.h
legacy/evas/src/lib/engines/common/evas_text_utils.c
legacy/evas/src/lib/engines/common/evas_text_utils.h

index fbe96c2..de50942 100644 (file)
@@ -1857,10 +1857,10 @@ _layout_paragraph_new(Ctxt *c, Evas_Object_Textblock_Node_Text *n)
 
 /**
  * @internal
- * Free the layout paragraph and all of it's lines.
+ * Free the visual lines in the paragraph (logical items are kept)
  */
 static void
-_paragraph_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par)
+_paragraph_clear(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par)
 {
    while (par->lines)
      {
@@ -1870,6 +1870,16 @@ _paragraph_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par)
         par->lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(par->lines), EINA_INLIST_GET(par->lines));
         _line_free(obj, ln);
      }
+}
+
+/**
+ * @internal
+ * Free the layout paragraph and all of it's lines and logical items.
+ */
+static void
+_paragraph_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par)
+{
+   _paragraph_clear(obj, par);
 
      {
         Eina_List *i, *i_prev;
@@ -1894,6 +1904,28 @@ static void
 _paragraphs_clear(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars)
 {
    Evas_Object_Textblock *o;
+   Evas_Object_Textblock_Paragraph *par;
+   o = (Evas_Object_Textblock *)(obj->object_data);
+
+   EINA_INLIST_FOREACH(EINA_INLIST_GET(pars), par)
+     {
+        _paragraph_clear(obj, par);
+     }
+}
+
+/**
+ * @internal
+ * Free the paragraphs from the inlist pars, the difference between this and
+ * _paragraphs_clear is that the latter keeps the logical items and the par
+ * items, while the former frees them as well.
+ *
+ * @param obj the evas object - Not NULL.
+ * @param pars the paragraphs to clean - Not NULL.
+ */
+static void
+_paragraphs_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars)
+{
+   Evas_Object_Textblock *o;
    o = (Evas_Object_Textblock *)(obj->object_data);
    while (pars)
      {
@@ -2373,7 +2405,8 @@ _layout_item_text_split_strip_white(Ctxt *c,
    _layout_text_add_logical_item(c, ti->parent.format, new_ti, _ITEM(ti));
 
    /* FIXME: Will break with kerning and a bunch of other stuff, should
-    * maybe adjust the last adv of the prev and the offset of the cur*/
+    * maybe adjust the last adv of the prev and the offset of the cur
+    * There's also another similar fixme below (same case, not marked) */
    ti->parent.w -= new_ti->parent.w;
    ti->parent.adv -= new_ti->parent.adv;
 
@@ -2401,6 +2434,38 @@ _layout_item_text_split_strip_white(Ctxt *c,
 
 /**
  * @internal
+ * Merge item2 into item1 and free item2.
+ *
+ * @param c the context to work on - Not NULL.
+ * @param item1 the item to copy to
+ * @param item2 the item to copy from
+ */
+static void
+_layout_item_merge_and_free(Ctxt *c,
+      Evas_Object_Textblock_Text_Item *item1,
+      Evas_Object_Textblock_Text_Item *item2)
+{
+   Eina_Unicode *tmp;
+   size_t len1, len2;
+   item1->parent.w += item2->parent.w;
+   item1->parent.adv += item2->parent.adv;
+   evas_common_text_props_merge(&item1->parent.text_props,
+         &item2->parent.text_props);
+   len1 = eina_unicode_strlen(item1->text);
+   len2 = eina_unicode_strlen(item2->text);
+   tmp = realloc(item1->text, (len1 + len2 + 1) * sizeof(Eina_Unicode));
+   eina_unicode_strcpy(tmp + len1, item2->text);
+   item1->text = tmp;
+   item1->text[len1 + len2] = 0;
+
+   item1->parent.merge = EINA_FALSE;
+   item1->parent.visually_deleted = EINA_FALSE;
+
+   _item_free(c->obj, NULL, _ITEM(item2));
+}
+
+/**
+ * @internal
  * Return the start of the last word up until start.
  *
  * @param str the string to work on.
@@ -3183,61 +3248,100 @@ _layout(const Evas_Object *obj, int calc_only, int w, int h, int *w_ret, int *h_
      }
 
 
-   /* If there are no nodes and lines, do the initial creation. */
-   if (!c->o->text_nodes)
+   if (o->content_changed)
      {
-        _layout_paragraph_new(c, NULL);
-        _layout_line_new(c, fmt);
-        _layout_text_append(c, fmt, NULL, 0, 0, NULL);
-        _layout_line_finalize(c, fmt);
-     }
-
+        _paragraphs_free(obj, o->paragraphs);
    /* Go through all the text nodes to create the logical layout */
-   EINA_INLIST_FOREACH(c->o->text_nodes, n)
-     {
-        Evas_Object_Textblock_Node_Format *fnode;
-        size_t start;
-        int off;
-
-        _layout_paragraph_new(c, n); /* Each node is a paragraph */
-
-        /* For each text node to thorugh all of it's format nodes
-         * append text from the start to the offset of the next format
-         * using the last format got. if needed it also creates format items
-         * this is the core algorithm of the layout mechanism.
-         * Skip the unicode replacement chars when there are because
-         * we don't want to print them. */
-        fnode = n->format_node;
-        start = off = 0;
-        while (fnode && (fnode->text_node == n))
+        EINA_INLIST_FOREACH(c->o->text_nodes, n)
           {
-             off += fnode->offset;
-             /* No need to skip on the first run, or a non-visible one */
-             _layout_text_append(c, fmt, n, start, off, o->repch);
-             _layout_do_format(obj, c, &fmt, fnode, &style_pad_l, &style_pad_r, &style_pad_t, &style_pad_b);
-             if ((c->have_underline2) || (c->have_underline))
-               {
-                  if (style_pad_b < c->underline_extend)
-                    style_pad_b = c->underline_extend;
-                  c->have_underline = 0;
-                  c->have_underline2 = 0;
-                  c->underline_extend = 0;
-               }
-             start += off;
-             if (fnode->visible)
+             Evas_Object_Textblock_Node_Format *fnode;
+             size_t start;
+             int off;
+
+             n->dirty = 0; /* Mark as if we cleaned the paragraph, although
+                              we should really use it to fine tune the
+                              changes here, and not just blindly mark */
+             _layout_paragraph_new(c, n); /* Each node is a paragraph */
+
+             /* For each text node to thorugh all of it's format nodes
+              * append text from the start to the offset of the next format
+              * using the last format got. if needed it also creates format items
+              * this is the core algorithm of the layout mechanism.
+              * Skip the unicode replacement chars when there are because
+              * we don't want to print them. */
+             fnode = n->format_node;
+             start = off = 0;
+             while (fnode && (fnode->text_node == n))
                {
-                  off = -1;
-                  start++;
+                  off += fnode->offset;
+                  /* No need to skip on the first run, or a non-visible one */
+                  _layout_text_append(c, fmt, n, start, off, o->repch);
+                  _layout_do_format(obj, c, &fmt, fnode, &style_pad_l,
+                        &style_pad_r, &style_pad_t, &style_pad_b);
+                  if ((c->have_underline2) || (c->have_underline))
+                    {
+                       if (style_pad_b < c->underline_extend)
+                         style_pad_b = c->underline_extend;
+                       c->have_underline = 0;
+                       c->have_underline2 = 0;
+                       c->underline_extend = 0;
+                    }
+                  start += off;
+                  if (fnode->visible)
+                    {
+                       off = -1;
+                       start++;
+                    }
+                  else
+                    {
+                       off = 0;
+                    }
+                  fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
                }
-             else
+             _layout_text_append(c, fmt, n, start, -1, o->repch);
+          }
+     }
+   else
+     {
+        _paragraphs_clear(obj, o->paragraphs);
+        c->paragraphs = o->paragraphs;
+        /* Merge the ones that need merging. */
+        /* Go through all the paragraphs, lines, items and merge if should be
+         * merged we merge backwards!!! */
+        Evas_Object_Textblock_Paragraph *par;
+        EINA_INLIST_FOREACH(EINA_INLIST_GET(c->paragraphs), par)
+          {
+             Eina_List *itr, *itr_next;
+             Evas_Object_Textblock_Item *it, *prev_it = NULL;
+             EINA_LIST_FOREACH_SAFE(par->logical_items, itr, itr_next, it)
                {
-                  off = 0;
+                  if (it->merge && prev_it &&
+                        (prev_it->type == EVAS_TEXTBLOCK_ITEM_TEXT) &&
+                        (it->type == EVAS_TEXTBLOCK_ITEM_TEXT))
+                    {
+                       _layout_item_merge_and_free(c, _ITEM_TEXT(prev_it),
+                             _ITEM_TEXT(it));
+                       par->logical_items =
+                          eina_list_remove_list(par->logical_items, itr);
+                    }
+                  else
+                    {
+                       prev_it = it;
+                    }
                }
-             fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
           }
-        _layout_text_append(c, fmt, n, start, -1, o->repch);
      }
 
+   /* If there are no paragraphs, create the minimum needed */
+   if (!c->paragraphs)
+     {
+        _layout_paragraph_new(c, NULL);
+        _layout_line_new(c, fmt);
+        _layout_text_append(c, fmt, NULL, 0, 0, NULL);
+        _layout_line_finalize(c, fmt);
+     }
+
+
    /* End of logical layout creation */
 
    /* Start of visual layout creation */
@@ -3271,24 +3375,21 @@ _layout(const Evas_Object *obj, int calc_only, int w, int h, int *w_ret, int *h_
 
    if (w_ret) *w_ret = c->wmax;
    if (h_ret) *h_ret = c->hmax;
+   o->paragraphs = c->paragraphs;
+
    if ((o->style_pad.l != style_pad_l) || (o->style_pad.r != style_pad_r) ||
        (o->style_pad.t != style_pad_t) || (o->style_pad.b != style_pad_b))
      {
-        c->par->lines = NULL;
         o->style_pad.l = style_pad_l;
         o->style_pad.r = style_pad_r;
         o->style_pad.t = style_pad_t;
         o->style_pad.b = style_pad_b;
-        _layout(obj, calc_only, w, h, w_ret, h_ret);
         _paragraphs_clear(obj, c->paragraphs);
+        _layout(obj, calc_only, w, h, w_ret, h_ret);
         return;
      }
-   if (!calc_only)
-     {
-        o->paragraphs = c->paragraphs;
-        return;
-     }
-   if (c->paragraphs) _paragraphs_clear(obj, c->paragraphs);
+
+   return;
 }
 
 /*
@@ -3301,17 +3402,13 @@ static void
 _relayout(const Evas_Object *obj)
 {
    Evas_Object_Textblock *o;
-   Evas_Object_Textblock_Paragraph *paragraphs;
 
    o = (Evas_Object_Textblock *)(obj->object_data);
-   paragraphs = o->paragraphs;
-   o->paragraphs = NULL;
    _layout(obj,
          0,
          obj->cur.geometry.w, obj->cur.geometry.h,
          &o->formatted.w, &o->formatted.h);
    o->formatted.valid = 1;
-   if (paragraphs) _paragraphs_clear(obj, paragraphs);
    o->last_w = obj->cur.geometry.w;
    o->changed = 0;
    o->content_changed = 0;
@@ -7670,7 +7767,7 @@ evas_object_textblock_clear(Evas_Object *obj)
      }
    if (o->paragraphs)
      {
-       _paragraphs_clear(obj, o->paragraphs);
+       _paragraphs_free(obj, o->paragraphs);
        o->paragraphs = NULL;
      }
    _evas_textblock_text_node_changed(o, obj, NULL);
@@ -8146,20 +8243,12 @@ evas_object_textblock_render_pre(Evas_Object *obj)
    if ((o->changed) || (o->content_changed) ||
        (o->last_w != obj->cur.geometry.w))
      {
-       Evas_Object_Textblock_Paragraph *paragraphs;
-
-       paragraphs = o->paragraphs;
-       o->paragraphs = NULL;
        o->formatted.valid = 0;
        _layout(obj,
                0,
                obj->cur.geometry.w, obj->cur.geometry.h,
                &o->formatted.w, &o->formatted.h);
        o->formatted.valid = 1;
-        if (paragraphs)
-          {
-             _paragraphs_clear(obj, paragraphs);
-          }
        o->last_w = obj->cur.geometry.w;
        o->redraw = 0;
        evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj);
@@ -8378,15 +8467,6 @@ ptnode(Evas_Object_Textblock_Node_Text *n)
    printf("next = %p, prev = %p, last = %p\n", EINA_INLIST_GET(n)->next, EINA_INLIST_GET(n)->prev, EINA_INLIST_GET(n)->last);
    printf("format_node = %p\n", n->format_node);
    printf("'%ls'\n", eina_ustrbuf_string_get(n->unicode));
-   if (n->bidi_props)
-     {
-        size_t i;
-        for (i = 0 ; i < eina_ustrbuf_length_get(n->unicode) ; i++)
-          {
-             printf("%d", n->bidi_props->embedding_levels[i]);
-          }
-        printf("\n");
-     }
 }
 
 void
index 38a2dc8..795d7ca 100644 (file)
@@ -135,7 +135,7 @@ evas_common_font_ot_cutoff_text_props(Evas_Text_Props *props, int cutoff)
 }
 
 /* Won't work in the middle of ligatures
- * assumes ext doesn't have an already init ot_data
+ * aissumes ext doesn't have an already init ot_data
  * we assume there's at least one char in each part */
 EAPI void
 evas_common_font_ot_split_text_props(Evas_Text_Props *base,
@@ -181,6 +181,7 @@ evas_common_font_ot_split_text_props(Evas_Text_Props *base,
           {
              ext->ot_data->items[i].source_cluster -= min;
           }
+        ext->ot_data->offset = base->ot_data->offset + min;
      }
    tmp = realloc(base->ot_data->items,
          cutoff * sizeof(Evas_Font_OT_Data_Item));
@@ -189,6 +190,53 @@ evas_common_font_ot_split_text_props(Evas_Text_Props *base,
 
 }
 
+/* Won't work in the middle of ligatures
+ * assumes both are init correctly and that both are from the
+ * same origin item, i.e both have the same script + direction.
+ * assume item1 is logically first */
+EAPI void
+evas_common_font_ot_merge_text_props(Evas_Text_Props *item1,
+      const Evas_Text_Props *item2)
+{
+   Evas_Font_OT_Data_Item *tmp, *itr; /* Itr will be used for adding back
+                                         the offsets */
+   size_t len;
+   if (!item1->ot_data || !item2->ot_data)
+     return;
+   len = item1->ot_data->len + item2->ot_data->len;
+   tmp = calloc(len, sizeof(Evas_Font_OT_Data_Item));
+   if (item1->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
+     {
+        memcpy(tmp, item2->ot_data->items,
+              item2->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
+        memcpy(tmp + item2->ot_data->len, item1->ot_data->items,
+              item1->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
+        itr = tmp;
+     }
+   else
+     {
+        memcpy(tmp, item1->ot_data->items,
+              item1->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
+        memcpy(tmp + item1->ot_data->len, item2->ot_data->items,
+              item2->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
+        itr = tmp + item1->ot_data->len;
+     }
+   free(item1->ot_data->items);
+   item1->ot_data->items = tmp;
+   item1->ot_data->len = len;
+   /* Add back the offset of item2 to the newly created */
+   if (item2->ot_data->offset > 0)
+     {
+        int i;
+        for (i = 0 ; i < (int) item2->ot_data->len ; i++, itr++)
+          {
+             /* This must be > 0, just because this is how it works */
+             itr->source_cluster += item2->ot_data->offset -
+                item1->ot_data->offset;
+          }
+     }
+}
+
 EAPI Eina_Bool
 evas_common_font_ot_populate_text_props(void *_fn, const Eina_Unicode *text,
       Evas_Text_Props *props, int len)
index ab4a146..59413cd 100644 (file)
@@ -19,6 +19,8 @@ struct _Evas_Font_OT_Data
    int refcount;
    size_t len;
    Evas_Font_OT_Data_Item *items;
+   size_t offset; /* The offset from the start of the script segment,
+                     this is useful when it's a split item */
 };
 # else
 typedef void *Evas_Font_OT_Data;
@@ -81,5 +83,8 @@ evas_common_font_ot_cutoff_text_props(Evas_Text_Props *props, int cutoff);
 
 EAPI void
 evas_common_font_ot_split_text_props(Evas_Text_Props *base, Evas_Text_Props *ext, int cutoff);
+
+EAPI void
+evas_common_font_ot_merge_text_props(Evas_Text_Props *item1, const Evas_Text_Props *item2);
 #endif
 
index e9eee31..abb3d10 100644 (file)
@@ -79,4 +79,14 @@ evas_common_text_props_split(Evas_Text_Props *base,
 #endif
 }
 
+/* Won't work in the middle of ligatures */
+EAPI void
+evas_common_text_props_merge(Evas_Text_Props *item1,
+      const Evas_Text_Props *item2)
+{
+#ifdef OT_SUPPORT
+   evas_common_font_ot_merge_text_props(item1, item2);
+#endif
+}
+
 
index 6a36a19..560518e 100644 (file)
@@ -35,4 +35,6 @@ evas_common_text_props_cutoff(Evas_Text_Props *props, int cutoff);
 EAPI void
 evas_common_text_props_split(Evas_Text_Props *base, Evas_Text_Props *ext,
       int cutoff);
+EAPI void
+evas_common_text_props_merge(Evas_Text_Props *item1, const Evas_Text_Props *item2);
 #endif