From: tasn Date: Sun, 30 Jan 2011 10:41:42 +0000 (+0000) Subject: Evas textblock: split the visual layouting to two stages: X-Git-Tag: accepted/2.0/20130306.225542~242^2~1150 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=ad360b70d59f2774003841ea78e2671f28ca7c65;p=profile%2Fivi%2Fevas.git Evas textblock: split the visual layouting to two stages: 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... git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/trunk/evas@56504 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33 --- diff --git a/src/lib/canvas/evas_object_textblock.c b/src/lib/canvas/evas_object_textblock.c index fbe96c2..de50942 100644 --- a/src/lib/canvas/evas_object_textblock.c +++ b/src/lib/canvas/evas_object_textblock.c @@ -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 diff --git a/src/lib/engines/common/evas_font_ot.c b/src/lib/engines/common/evas_font_ot.c index 38a2dc8..795d7ca 100644 --- a/src/lib/engines/common/evas_font_ot.c +++ b/src/lib/engines/common/evas_font_ot.c @@ -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) diff --git a/src/lib/engines/common/evas_font_ot.h b/src/lib/engines/common/evas_font_ot.h index ab4a146..59413cd 100644 --- a/src/lib/engines/common/evas_font_ot.h +++ b/src/lib/engines/common/evas_font_ot.h @@ -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 diff --git a/src/lib/engines/common/evas_text_utils.c b/src/lib/engines/common/evas_text_utils.c index e9eee31..abb3d10 100644 --- a/src/lib/engines/common/evas_text_utils.c +++ b/src/lib/engines/common/evas_text_utils.c @@ -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 +} + diff --git a/src/lib/engines/common/evas_text_utils.h b/src/lib/engines/common/evas_text_utils.h index 6a36a19..560518e 100644 --- a/src/lib/engines/common/evas_text_utils.h +++ b/src/lib/engines/common/evas_text_utils.h @@ -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