From 91456d3198f5b205bb73b6e59d3dea291528d8d7 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Sun, 30 Jan 2011 10:40:14 +0000 Subject: [PATCH] Evas textblock: Started splitting the layouting to two stages, logical item creating and visual ordering of the items. This will let us to cache the logical items and just relayout the visual items when needed thus making everything a lot faster. SVN revision: 56490 --- legacy/evas/src/lib/canvas/evas_object_text.c | 2 + .../src/lib/canvas/evas_object_textblock.c | 454 ++++++------------ 2 files changed, 158 insertions(+), 298 deletions(-) diff --git a/legacy/evas/src/lib/canvas/evas_object_text.c b/legacy/evas/src/lib/canvas/evas_object_text.c index 170648abd4..b2f0ea6fbe 100644 --- a/legacy/evas/src/lib/canvas/evas_object_text.c +++ b/legacy/evas/src/lib/canvas/evas_object_text.c @@ -149,6 +149,7 @@ _evas_object_text_items_clear(Evas_Object_Text *o) } } +#ifdef BIDI_SUPPORT static int _evas_object_text_it_compare_logical(const void *_it1, const void *_it2) { @@ -161,6 +162,7 @@ _evas_object_text_it_compare_logical(const void *_it1, const void *_it2) return 1; } +#endif /* FIXME: doc */ static int _evas_object_text_last_up_to_pos(const Evas_Object *obj, diff --git a/legacy/evas/src/lib/canvas/evas_object_textblock.c b/legacy/evas/src/lib/canvas/evas_object_textblock.c index e5d987e24b..ab015d4dec 100644 --- a/legacy/evas/src/lib/canvas/evas_object_textblock.c +++ b/legacy/evas/src/lib/canvas/evas_object_textblock.c @@ -243,6 +243,7 @@ struct _Evas_Object_Textblock_Node_Text char *utf8; Evas_Object_Textblock_Node_Format *format_node; Evas_BiDi_Paragraph_Props *bidi_props; + Eina_Bool dirty : 1; }; struct _Evas_Object_Textblock_Node_Format @@ -290,6 +291,7 @@ struct _Evas_Object_Textblock_Paragraph EINA_INLIST; Evas_Object_Textblock_Line *lines; Evas_Object_Textblock_Node_Text *text_node; + Eina_List *logical_items; int x, y, w, h; int line_no; }; @@ -671,13 +673,9 @@ _item_free(const Evas_Object *obj, Evas_Object_Textblock_Line *ln, Evas_Object_T * @param ln the layout line to be freed, must not be NULL. */ static void -_line_free(const Evas_Object *obj, Evas_Object_Textblock_Line *ln) +_line_free(const Evas_Object *obj __UNUSED__, Evas_Object_Textblock_Line *ln) { - while (ln->items) - { - Evas_Object_Textblock_Item *it = ln->items; - _item_free(obj, ln, it); - } + /* Items are freed from the logical list */ if (ln) free(ln); } @@ -1746,6 +1744,7 @@ struct _Ctxt Evas_Object_Textblock_Line *ln; Eina_List *format_stack; + Evas_Object_Textblock_Format *fmt; int x, y; int w, h; @@ -1842,13 +1841,6 @@ static void _layout_paragraph_new(Ctxt *c, Evas_Object_Textblock_Node_Text *n) { c->par = calloc(1, sizeof(Evas_Object_Textblock_Paragraph)); - if (c->paragraphs) - { - Evas_Object_Textblock_Paragraph *last_par; - last_par = (Evas_Object_Textblock_Paragraph *) - EINA_INLIST_GET(c->paragraphs)->last; - c->par->y = last_par->y + last_par->h; - } c->paragraphs = (Evas_Object_Textblock_Paragraph *)eina_inlist_append(EINA_INLIST_GET(c->paragraphs), EINA_INLIST_GET(c->par)); c->ln = NULL; c->par->text_node = n; @@ -1870,6 +1862,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); } + + { + Eina_List *i, *i_prev; + Evas_Object_Textblock_Item *it; + EINA_LIST_FOREACH_SAFE(par->logical_items, i, i_prev, it) + { + _item_free(obj, NULL, it); + } + eina_list_free(par->logical_items); + } free(par); } @@ -2012,7 +2014,6 @@ _layout_line_align_get(Ctxt *c) return c->align; } -#ifdef BIDI_SUPPORT /** * @internal * Reorder the items in a line. @@ -2020,11 +2021,11 @@ _layout_line_align_get(Ctxt *c) * @param line the line to reorder */ static void -_layout_line_reorder(Ctxt *c __UNUSED__, Evas_Object_Textblock_Line *line) +_layout_line_order(Ctxt *c __UNUSED__, Evas_Object_Textblock_Line *line) { /*FIXME: do it a bit more efficient - not very efficient ATM. */ +#ifdef BIDI_SUPPORT Evas_Object_Textblock_Item *it; - Evas_BiDi_Paragraph_Props *props; EvasBiDiStrIndex *v_to_l = NULL; size_t start, end; size_t len; @@ -2032,73 +2033,71 @@ _layout_line_reorder(Ctxt *c __UNUSED__, Evas_Object_Textblock_Line *line) if (line->items && line->items->text_node && line->items->text_node->bidi_props) { + Evas_BiDi_Paragraph_Props *props; props = line->items->text_node->bidi_props; start = end = line->items->text_pos; - } - else - { - return; - } - /* Find the first and last positions in the line */ + /* Find the first and last positions in the line */ - EINA_INLIST_FOREACH(line->items, it) - { - if (it->text_pos < start) + EINA_INLIST_FOREACH(line->items, it) { - start = it->text_pos; - } - else - { - int tlen; - tlen = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? - eina_unicode_strlen(_ITEM_TEXT(it)->text) : 1; - if (it->text_pos + tlen > end) + if (it->text_pos < start) { - end = it->text_pos + tlen; + start = it->text_pos; } - } - } - - len = end - start; - evas_bidi_props_reorder_line(NULL, start, len, props, &v_to_l); - - /* Update visual pos */ - { - Evas_Object_Textblock_Item *i; - i = line->items; - while (i) - { - i->visual_pos = evas_bidi_position_logical_to_visual( - v_to_l, len, i->text_pos - start); - i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(i)->next; - } - } - - /*FIXME: not very efficient, sort the items arrays. Anyhow, should only - * reorder if it's a bidi paragraph */ - { - Evas_Object_Textblock_Item *i, *j, *min; - i = line->items; - while (i) - { - min = i; - EINA_INLIST_FOREACH(i, j) + else { - if (j->visual_pos < min->visual_pos) + int tlen; + tlen = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? + eina_unicode_strlen(_ITEM_TEXT(it)->text) : 1; + if (it->text_pos + tlen > end) { - min = j; + end = it->text_pos + tlen; } } - if (min != i) - { - line->items = (Evas_Object_Textblock_Item *) eina_inlist_remove(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min)); - line->items = (Evas_Object_Textblock_Item *) eina_inlist_prepend_relative(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min), EINA_INLIST_GET(i)); - } + } - i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(min)->next; + len = end - start; + evas_bidi_props_reorder_line(NULL, start, len, props, &v_to_l); + + /* Update visual pos */ + { + Evas_Object_Textblock_Item *i; + i = line->items; + while (i) + { + i->visual_pos = evas_bidi_position_logical_to_visual( + v_to_l, len, i->text_pos - start); + i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(i)->next; + } + } + + /*FIXME: not very efficient, sort the items arrays. Anyhow, should only + * reorder if it's a bidi paragraph */ + { + Evas_Object_Textblock_Item *i, *j, *min; + i = line->items; + while (i) + { + min = i; + EINA_INLIST_FOREACH(i, j) + { + if (j->visual_pos < min->visual_pos) + { + min = j; + } + } + if (min != i) + { + line->items = (Evas_Object_Textblock_Item *) eina_inlist_remove(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min)); + line->items = (Evas_Object_Textblock_Item *) eina_inlist_prepend_relative(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min), EINA_INLIST_GET(i)); + } + + i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(min)->next; + } } } +#endif /* Recalculate the positions according to the new order. */ { @@ -2113,10 +2112,10 @@ _layout_line_reorder(Ctxt *c __UNUSED__, Evas_Object_Textblock_Line *line) } } - /* Fix the order in each line correctly */ +#ifdef BIDI_SUPPORT if (v_to_l) free(v_to_l); -} #endif +} /** * @internal @@ -2133,9 +2132,7 @@ _layout_line_advance(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Item *it; Eina_Bool no_text = EINA_TRUE; -#ifdef BIDI_SUPPORT - _layout_line_reorder(c, c->ln); -#endif + _layout_line_order(c, c->ln); c->maxascent = c->maxdescent = 0; EINA_INLIST_FOREACH(c->ln->items, it) @@ -2491,7 +2488,7 @@ _layout_word_next(Eina_Unicode *str, int p) * @param it the item itself. */ static void -_layout_text_add_item(Ctxt *c, Evas_Object_Textblock_Format *fmt, +_layout_text_add_logical_item(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Text_Item *ti) { int tw, th, adv, inset; @@ -2507,16 +2504,13 @@ _layout_text_add_item(Ctxt *c, Evas_Object_Textblock_Format *fmt, inset = c->ENFN->font_inset_get(c->ENDT, fmt->font.font, ti->text); ti->inset = inset; - ti->parent.x = c->x; adv = 0; if (fmt->font.font) adv = c->ENFN->font_h_advance_get(c->ENDT, fmt->font.font, ti->text, &ti->parent.text_props); ti->parent.adv = adv; - c->x += adv; - c->ln->items = (Evas_Object_Textblock_Item *) - eina_inlist_append(EINA_INLIST_GET(c->ln->items), - EINA_INLIST_GET(_ITEM(ti))); + ti->parent.x = 0; + c->par->logical_items = eina_list_append(c->par->logical_items, ti); } /** @@ -2534,8 +2528,7 @@ _layout_text_add_item(Ctxt *c, Evas_Object_Textblock_Format *fmt, static void _layout_text_append(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Node_Text *n, int start, int off, const char *repch) { - int tw, new_line, empty_item; - int wrap, twrap, ch, index, white_stripped; + int new_line, empty_item; Eina_Unicode *alloc_str = NULL; const Eina_Unicode *str = EINA_UNICODE_EMPTY_STRING; const Eina_Unicode *tbase; @@ -2610,8 +2603,6 @@ skip: { /* if this is the first line item and it starts with spaces - remove them */ int tmp_len; - wrap = 0; - white_stripped = 0; ti = _layout_text_item_new(c, fmt, str); ti->parent.text_node = n; @@ -2637,186 +2628,9 @@ skip: &ti->parent.text_props, ti->parent.text_node->bidi_props, ti->parent.text_pos, tmp_len); } - tw = 0; - if (fmt->font.font) - tw = c->ENFN->font_h_advance_get(c->ENDT, ti->format->font.font, - ti->text, &ti->parent.text_props); - /* Check if we need to wrap, i.e the text is bigger than the width - * Only calculate wrapping if the width of the object is > 0 */ - if ((c->w >= 0) && - ((fmt->wrap_word) || (fmt->wrap_char)) && - ((c->x + tw) > - (c->w - c->o->style_pad.l - c->o->style_pad.r - - c->marginl - c->marginr))) - { - wrap = _layout_text_cutoff_get(c, fmt, ti); - /* Avoiding too small textblocks to even contain one char */ - if (wrap == 0) - GET_NEXT(str, wrap); - /* We need to wrap and found the position that overflows */ - if (wrap > 0) - { - if (fmt->wrap_word) - { - index = wrap; - ch = GET_NEXT(str, index); - if (!_is_white(ch)) - wrap = _layout_word_start(str, wrap); - /* If we found where to cut the text at, i.e the start - * of the word we were pointing at */ - if (wrap > 0) - { - twrap = wrap; - ch = GET_PREV(str, twrap); - /* the text intersects the wrap point on a whitespace char */ - if (_is_white(ch)) - { - _layout_item_text_cutoff(c, ti, wrap); - twrap = wrap; - str += twrap; - } - /* intersects a word */ - else - { - /* walk back to start of word */ - twrap = _layout_word_start(str, wrap); - if (twrap != 0) - { - wrap = twrap; - ch = GET_PREV(str, twrap); - _layout_item_text_cutoff(c, ti, twrap); - str += wrap; - } - else - { - empty_item = 1; - _item_free(c->obj, NULL, _ITEM(ti)); - if (c->ln->items) - { - ti = _ITEM_TEXT((EINA_INLIST_GET(c->ln->items))->last); - _layout_strip_trailing_whitespace(c, fmt, _ITEM(ti)); - twrap = _layout_word_end(str, wrap); - if (twrap >= 0) - { - ch = GET_NEXT(str, twrap); - str += twrap; - } - else - str = NULL; - } - } - } - } - /* If we weren't able to find the start of the word we - * are currently pointing at, or we were able but it's - * the first word */ - else - { - /* wrap now is the index of the word START */ - index = wrap; - ch = GET_NEXT(str, index); + str += tmp_len; - /* If there are already items in this line, we - * should just try creating a new line for it */ - if (c->ln->items) - { - white_stripped = _layout_item_abort(c, fmt, _ITEM(ti)); - empty_item = 1; - } - /* If there were no items in this line, try to do - * our best wrapping possible since it's the middle - * of the word */ - else - { - wrap = 0; - twrap = _layout_word_end(ti->text, wrap); - wrap = twrap; - if (twrap >= 0) - { - ch = GET_NEXT(str, wrap); - _layout_item_text_cutoff(c, ti, twrap); - } - if (wrap > 0) - str += wrap; - else - str = NULL; - } - } - } - else if (fmt->wrap_char) - { - _layout_item_text_cutoff(c, ti, wrap); - str += wrap; - } - /* Marked we wrapped and we want to start a new line */ - new_line = 1; - } - /* We need to wrap, but for some reason we failed obatining the - * overflow position. */ - else - { - /*FIXME: sanitize this error handling - should probably - * never get here anyway unless something really bad - * has happend */ - /* wrap now is the index of the word START */ - if (wrap < 0) wrap = 0; - index = wrap; - ch = GET_NEXT(str, index); - - if (c->ln->items) - { - white_stripped = _layout_item_abort(c, fmt, _ITEM(ti)); - empty_item = 1; - new_line = 1; - } - else - { - if (wrap <= 0) - { - wrap = 0; - twrap = _layout_word_end(ti->text, wrap); - wrap = _layout_word_next(ti->text, wrap); - if (twrap >= 0) - _layout_item_text_cutoff(c, ti, twrap); - if (wrap >= 0) - str += wrap; - else - str += tmp_len; - } - else - str += tmp_len; - new_line = 1; - } - } - } - else - str += tmp_len; - - /* Set item properties */ - if (empty_item) - { - empty_item = 0; - } - else - { - _layout_text_add_item(c, fmt, ti); - } - - - if (new_line) - { - if (str) - { - if (!white_stripped) - { - index = 0; - ch = GET_NEXT(str, index); - if (_is_white(ch)) str += index; - } - } - new_line = 0; - _layout_line_advance(c, fmt, EINA_TRUE); - } + _layout_text_add_logical_item(c, fmt, ti); } if (alloc_str) free(alloc_str); @@ -2841,9 +2655,7 @@ _layout_format_item_add(Ctxt *c, Evas_Object_Textblock_Node_Format *n, const cha fi->item = eina_stringshare_add(item); fi->source_node = n; fi->parent.type = EVAS_TEXTBLOCK_ITEM_FORMAT; - c->ln->items = _ITEM(eina_inlist_append( - EINA_INLIST_GET(c->ln->items), - EINA_INLIST_GET(_ITEM(fi)))); + c->par->logical_items = eina_list_append(c->par->logical_items, fi); if (n) { fi->parent.text_node = n->text_node; @@ -2980,16 +2792,7 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c, x2 = c->x + w; - if (x2 > - (c->w - c->o->style_pad.l - - c->o->style_pad.r - - c->marginl - c->marginr)) - { - _layout_line_advance(c, fmt, EINA_TRUE); - x2 = w; - } fi = _layout_format_item_add(c, n, NULL); - fi->parent.x = c->x; fi->vsize = vsize; fi->size = size; fi->formatme = 1; @@ -2999,7 +2802,6 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c, fi->parent.h = h; fi->ascent = c->maxascent; fi->descent = c->maxdescent; - c->x = x2; handled = 1; } if (!handled) @@ -3031,9 +2833,7 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c, Evas_Object_Textblock_Format_Item *fi; fi = _layout_format_item_add(c, n, item); - fi->parent.x = c->x; fi->parent.w = fi->parent.adv = 0; - _layout_line_advance(c, fmt, EINA_TRUE); } else if ((!strcmp(item, "\t")) || (!strcmp(item, "\\t"))) { @@ -3041,24 +2841,7 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c, int x2; x2 = c->x + fmt->tabstops; - /* Wrap lines if there's a size */ - if ((c->w > 0) && (x2 > - (c->w - c->o->style_pad.l - - c->o->style_pad.r - - c->marginl - c->marginr))) - { - _layout_line_advance(c, fmt, EINA_TRUE); - x2 = c->x + fmt->tabstops; - } - if (c->ln->items) - { - Evas_Object_Textblock_Item *it; - - it = (Evas_Object_Textblock_Item *)(EINA_INLIST_GET(c->ln->items))->last; - _layout_strip_trailing_whitespace(c, fmt, it); - } fi = _layout_format_item_add(c, n, item); - fi->parent.x = c->x; fi->parent.w = fi->parent.adv = x2 - c->x; fi->formatme = 1; c->x = x2; @@ -3076,6 +2859,51 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c, *_fmt = fmt; } +static void +_layout_update_par(Ctxt *c) +{ + Evas_Object_Textblock_Paragraph *last_par; + last_par = (Evas_Object_Textblock_Paragraph *) + EINA_INLIST_GET(c->par)->prev; + if (last_par) + { + c->par->y = last_par->y + last_par->h; + } +} +static void +_layout_visualize_par(Ctxt *c) +{ + Evas_Object_Textblock_Item *it; + Eina_List *i; + if (!c->par->logical_items) + return; + + _layout_line_new(c, c->fmt); + EINA_LIST_FOREACH(c->par->logical_items, i, it) + { + c->ln->items = (Evas_Object_Textblock_Item *) + eina_inlist_append(EINA_INLIST_GET(c->ln->items), + EINA_INLIST_GET(it)); + /* FIXME: Add word wrappnig here */ + if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) + { + Evas_Object_Textblock_Format_Item *fi; + fi = _ITEM_FORMAT(it); + fi->y = c->y; + if (_IS_LINE_SEPARATOR( + eina_strbuf_string_get(fi->source_node->format))) + { + _layout_line_advance(c, c->fmt, EINA_TRUE); + } + } + else + { + c->fmt = _ITEM_TEXT(it)->format; + } + } + _layout_line_advance(c, c->fmt, EINA_FALSE); +} + /** * @internal * Create the layout from the nodes. @@ -3152,7 +2980,6 @@ _layout(const Evas_Object *obj, int calc_only, int w, int h, int *w_ret, int *h_ int off; _layout_paragraph_new(c, n); /* Each node is a paragraph */ - _layout_line_new(c, fmt); /* For each text node to thorugh all of it's format nodes * append text from the start to the offset of the next format @@ -3191,9 +3018,22 @@ _layout(const Evas_Object *obj, int calc_only, int w, int h, int *w_ret, int *h_ _layout_text_append(c, fmt, n, start, -1, o->repch); } + /* FIXME: move away? */ + { + /* FIXME: is this the right format? or maybe it can change with pops? */ + c->fmt = c->format_stack->data; + Evas_Object_Textblock_Paragraph *par; + EINA_INLIST_FOREACH(c->paragraphs, par) + { + c->par = par; + _layout_update_par(c); + _layout_visualize_par(c); + } + } + /* Advance the line so it'll calculate the size */ if ((c->ln) && (c->ln->items) && (fmt)) - _layout_line_advance(c, fmt, EINA_TRUE); + //_layout_line_advance(c, fmt, EINA_TRUE); /* Clean the rest of the format stack */ while (c->format_stack) @@ -5061,7 +4901,7 @@ evas_textblock_cursor_line_char_last(Evas_Textblock_Cursor *cur) GET_NEXT(_ITEM_TEXT(it)->text, index); cur->pos += index; } - else if (!EINA_INLIST_GET(ln)->next) + else if (!EINA_INLIST_GET(ln)->next && !EINA_INLIST_GET(ln->par)->next) { cur->pos++; } @@ -5732,6 +5572,8 @@ _evas_textblock_node_text_new(void) n = calloc(1, sizeof(Evas_Object_Textblock_Node_Text)); n->unicode = eina_ustrbuf_new(); + /* We want to layout each paragraph at least once. */ + n->dirty = EINA_TRUE; #ifdef BIDI_SUPPORT n->bidi_props = evas_bidi_paragraph_props_new(); n->bidi_props->direction = EVAS_BIDI_PARAGRAPH_NATURAL; @@ -5927,6 +5769,22 @@ _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj) evas_object_change(obj); } +/** + * @internal + * Mark and notifiy that the textblock, and specifically a node has changed. + * + * @param o the textblock object. + * @param obj the evas object. + * @param n the paragraph that changed. + */ +static void +_evas_textblock_content_changed(Evas_Object_Textblock *o, Evas_Object *obj, + Evas_Object_Textblock_Node_Text *n) +{ + n->dirty = EINA_TRUE; + _evas_textblock_changed(o, obj); +} + /** * Adds text to the current cursor position and set the cursor to *before* * the start of the text just added.