* @subsection textblock_layout The layout system
* @todo write @ref textblock_layout
*/
+#include <stdlib.h>
+
#include "evas_common.h"
#include "evas_private.h"
int x, w, h;
int inset, baseline;
size_t source_pos;
+#ifdef BIDI_SUPPORT
+ size_t visual_pos;
+#endif
Evas_BiDi_Props bidi_props;
};
EINA_INLIST;
const char *item;
Evas_Object_Textblock_Node_Format *source_node;
+ Evas_Object_Textblock_Node_Text *text_node;
+ size_t text_pos;
int x, w, h, y, ascent, descent;
unsigned char vsize : 2;
unsigned char size : 2;
Evas_Object_Textblock_Node_Text *n;
n = o->text_nodes;
- _evas_textblock_node_text_remove(o, n);
+ o->text_nodes = _NODE_TEXT(eina_inlist_remove(
+ EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n)));
+ _evas_textblock_node_text_free(n);
}
while (o->format_nodes)
{
_layout_line_align_get(Ctxt *c)
{
#ifdef BIDI_SUPPORT
- if (c->align_auto && c->ln && c->ln->items)
+ if (c->align_auto && c->ln)
{
- if (c->ln->items->source_node &&
-
+ if ((c->ln->items && c->ln->items->source_node &&
EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(
- c->ln->items->source_node->bidi_props))
+ c->ln->items->source_node->bidi_props)) ||
+ (c->ln->format_items && c->ln->format_items->text_node &&
+ EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(
+ c->ln->format_items->text_node->bidi_props)))
{
/* Align right*/
return 1.0;
return c->align;
}
+#ifdef BIDI_SUPPORT
+/**
+ * @internal
+ * Reorder the items in a line.
+ *
+ * @param line the line to reorder
+ */
+static void
+_layout_line_reorder(Ctxt *c, Evas_Object_Textblock_Line *line)
+{
+ /*FIXME: do it a bit more efficient - not very efficient ATM. */
+ Evas_Object_Textblock_Item *it;
+ Evas_Object_Textblock_Format_Item *fi;
+ Evas_BiDi_Paragraph_Props *props;
+ EvasBiDiStrIndex *v_to_l = NULL;
+ size_t start, end;
+ size_t len;
+
+ if (line->items && line->items->source_node &&
+ line->items->source_node->bidi_props)
+ {
+ props = line->items->source_node->bidi_props;
+ start = end = line->items->source_pos;
+ }
+ else if (line->format_items && line->format_items->text_node &&
+ line->format_items->text_node->bidi_props)
+ {
+ props = line->format_items->text_node->bidi_props;
+ start = end = line->format_items->text_pos;
+ }
+ else
+ {
+ return;
+ }
+
+ /* Find the first and last positions in the line */
+
+ EINA_INLIST_FOREACH(line->items, it)
+ {
+ if (it->source_pos < start)
+ {
+ start = it->source_pos;
+ }
+ else
+ {
+ int tlen;
+ tlen = eina_unicode_strlen(it->text);
+ if (it->source_pos + tlen > end)
+ {
+ end = it->source_pos + tlen;
+ }
+ }
+ }
+ EINA_INLIST_FOREACH(line->format_items, fi)
+ {
+ if (fi->text_pos < start)
+ {
+ start = fi->text_pos;
+ }
+ else
+ {
+ if (fi->text_pos + 1 > end)
+ {
+ end = fi->text_pos + 1;
+ }
+ }
+ }
+
+ 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->source_pos - start);
+ i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(i)->next;
+ }
+ }
+
+ /*FIXME: not very efficient, sort the items arrays. */
+ {
+ 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;
+ }
+ }
+ {
+ Evas_Object_Textblock_Format_Item *i, *j, *min;
+ i = line->format_items;
+ while (i)
+ {
+ min = i;
+ EINA_INLIST_FOREACH(i, j)
+ {
+ if (evas_bidi_position_logical_to_visual(
+ v_to_l, len, j->text_pos - start) <
+ evas_bidi_position_logical_to_visual(
+ v_to_l, len, min->text_pos - start))
+ {
+ min = j;
+ }
+ }
+ if (min != i)
+ {
+ line->format_items = (Evas_Object_Textblock_Format_Item *)
+ eina_inlist_remove(EINA_INLIST_GET(line->format_items),
+ EINA_INLIST_GET(min));
+ line->format_items = (Evas_Object_Textblock_Format_Item *)
+ eina_inlist_prepend_relative(EINA_INLIST_GET(line->format_items), EINA_INLIST_GET(min), EINA_INLIST_GET(i));
+ }
+
+ i = (Evas_Object_Textblock_Format_Item *) EINA_INLIST_GET(min)->next;
+ }
+ }
+
+ /* Recalculate the positions according to the new order. */
+ {
+ Evas_Object_Textblock_Format_Item *fi = line->format_items;
+ Evas_Object_Textblock_Item *it = line->items;
+ Evas_Coord x = 0;
+
+ while (it || fi)
+ {
+ if (it && (!fi || ((int) it->visual_pos <
+ evas_bidi_position_logical_to_visual(v_to_l, len,
+ fi->text_pos - start))))
+ {
+ int adv = 0;
+ it->x = x;
+ if (it->format->font.font)
+ adv = c->ENFN->font_h_advance_get(c->ENDT,
+ it->format->font.font, it->text, &it->bidi_props);
+
+ x += adv;
+ it = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(it)->next;
+ }
+ else if (fi)
+ {
+ fi->x = x;
+ x += fi->w;
+ fi = (Evas_Object_Textblock_Format_Item *)
+ EINA_INLIST_GET(fi)->next;
+ }
+ }
+ }
+
+ /* Fix the order in each line correctly */
+ if (v_to_l) free(v_to_l);
+}
+#endif
+
/**
* @internal
* Create a new line and append it to the lines in the context.
Evas_Object_Textblock_Item *it;
Evas_Object_Textblock_Format_Item *fi;
+#ifdef BIDI_SUPPORT
+ _layout_line_reorder(c, c->ln);
+#endif
+
c->maxascent = c->maxdescent = 0;
if (!c->ln->items)
_layout_format_ascent_descent_adjust(c, fmt);
* @param c the context to work on - Not NULL.
* @param fmt the format to use. - Not NULL.
* @param it the item to check - Not null.
- * @return -1 on error, cutoff index on success.
+ * @return -1 if there is no cutoff (either because there is really none,
+ * or because of an error), cutoff index on success.
*/
static int
_layout_text_cutoff_get(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Item *it)
/**
* @internal
+ * Adds the item to the list, updates the item's properties (e.g, x,w,h)
+ * and splits the items to text runs, i.e splits each item to rtl/ltr runs of
+ * text.
+ *
+ * @param c the context
+ * @param fmt the format of the item.
+ * @param it the item itself.
+ */
+static void
+_layout_text_add_and_split_item(Ctxt *c, Evas_Object_Textblock_Format *fmt,
+ Evas_Object_Textblock_Item *it)
+{
+ int tw, th, adv, inset;
+
+#ifdef BIDI_SUPPORT
+ int cutoff, len;
+
+
+ len = eina_unicode_strlen(it->text);
+ do
+ {
+ Evas_Object_Textblock_Item *new_it;
+ cutoff = evas_bidi_end_of_run_get(&it->bidi_props, len);
+ if (cutoff > 0)
+ {
+ new_it = _layout_item_new(c, fmt, it->text + cutoff);
+ _layout_item_text_cutoff(c, it, cutoff);
+ }
+#endif
+
+ tw = th = 0;
+ if (fmt->font.font)
+ c->ENFN->font_string_size_get(c->ENDT, fmt->font.font, it->text,
+ &it->bidi_props, &tw, &th);
+ it->w = tw;
+ it->h = th;
+ inset = 0;
+ if (fmt->font.font)
+ inset = c->ENFN->font_inset_get(c->ENDT, fmt->font.font,
+ it->text);
+ it->inset = inset;
+ it->x = c->x;
+ adv = 0;
+ if (fmt->font.font)
+ adv = c->ENFN->font_h_advance_get(c->ENDT, fmt->font.font,
+ it->text, &it->bidi_props);
+ c->x += adv;
+ c->ln->items = (Evas_Object_Textblock_Item *)
+ eina_inlist_append(EINA_INLIST_GET(c->ln->items),
+ EINA_INLIST_GET(it));
+
+#ifdef BIDI_SUPPORT
+ if (cutoff > 0)
+ {
+ new_it->source_node = it->source_node;
+ new_it->source_pos = it->source_pos + cutoff;
+ new_it->bidi_props.start = new_it->source_pos;
+ new_it->bidi_props.props = new_it->source_node->bidi_props;
+ it = new_it;
+ len -= cutoff;
+ }
+ }
+ while (cutoff > 0);
+#endif
+}
+
+/**
+ * @internal
* Appends the text from node n starting at start ending at off to the layout.
* It uses the fmt for the formatting.
*
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 adv, inset, tw, th, new_line, empty_item;
+ int tw, th, new_line, empty_item;
int wrap, twrap, ch, index, white_stripped;
Eina_Unicode *alloc_str = NULL;
const Eina_Unicode *str = EINA_UNICODE_EMPTY_STRING;
str = alloc_str;
}
}
+
skip:
tbase = str;
- // printf("add: wrap: %i|%i, width: %i '%s'\n", fmt->wrap_word, fmt->wrap_char, c->w, str);
new_line = 0;
empty_item = 0;
tw = th = 0;
if (fmt->font.font)
c->ENFN->font_string_size_get(c->ENDT, fmt->font.font, it->text, &it->bidi_props, &tw, &th);
+ /* 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->marginl - c->marginr)))
{
wrap = _layout_text_cutoff_get(c, fmt, it);
+ /* 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)
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;
{
_layout_item_text_cutoff(c, it, wrap);
twrap = wrap;
- /*we don't want to move next, that's why it's
- * commented out.
- * ch = evas_common_font_utf8_get_next((unsigned char *)str, &twrap);
- */
str += twrap;
}
/* intersects a word */
else
{
empty_item = 1;
+ /* FIXME: use proper cleaning here */
if (it->text) free(it->text);
_format_unref_free(c->obj, it->format);
free(it);
}
}
}
+ /* 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);
+ /* 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, it);
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;
_layout_item_text_cutoff(c, it, 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;
new_line = 1;
}
}
- if (!empty_item)
- {
- tw = th = 0;
- if (fmt->font.font)
- c->ENFN->font_string_size_get(c->ENDT, fmt->font.font, it->text, &it->bidi_props, &tw, &th);
- }
}
else
str = NULL;
- if (empty_item) empty_item = 0;
+
+ /* Set item properties */
+ if (empty_item)
+ {
+ empty_item = 0;
+ }
else
{
- it->w = tw;
- it->h = th;
- inset = 0;
- if (fmt->font.font)
- inset = c->ENFN->font_inset_get(c->ENDT, fmt->font.font, it->text);
- it->inset = inset;
- it->x = c->x;
- adv = 0;
- if (fmt->font.font)
- adv = c->ENFN->font_h_advance_get(c->ENDT, fmt->font.font, it->text, &it->bidi_props);
- c->x += adv;
- c->ln->items = (Evas_Object_Textblock_Item *)eina_inlist_append(EINA_INLIST_GET(c->ln->items), EINA_INLIST_GET(it));
+ _layout_text_add_and_split_item(c, fmt, it);
}
+
+
if (new_line)
{
if (str)
_layout_line_advance(c, fmt);
}
}
+
if (alloc_str) free(alloc_str);
}
fi->source_node = n;
c->ln->format_items = (Evas_Object_Textblock_Format_Item *)eina_inlist_append(EINA_INLIST_GET(c->ln->format_items),
EINA_INLIST_GET(fi));
+ if (n)
+ {
+ fi->text_node = n->text_node;
+ /* FIXME: make it more efficient */
+ fi->text_pos = _evas_textblock_node_format_pos_get(n);
+ }
return fi;
}
}
_layout_text_append(c, fmt, n, start, -1, o->repch);
}
+
/* Advance the line so it'll calculate the size */
if ((c->ln) && (c->ln->items || c->ln->format_items) && (fmt))
_layout_line_advance(c, fmt);
}
if (!ln) return;
+ if (ln->items)
+ {
+ Evas_Object_Textblock_Item *i;
+ it = ln->items;
+ EINA_INLIST_FOREACH(ln->items, i)
+ {
+ if (it->source_pos > i->source_pos)
+ {
+ it = i;
+ }
+ }
+ }
+ else
+ it = NULL;
- it = (Evas_Object_Textblock_Item *)ln->items;
- fi = (Evas_Object_Textblock_Format_Item *)ln->format_items;
+ if (ln->format_items)
+ {
+ Evas_Object_Textblock_Format_Item *i;
+ fi = ln->format_items;
+ EINA_INLIST_FOREACH(ln->format_items, i)
+ {
+ if (fi->text_pos > i->text_pos)
+ {
+ fi = i;
+ }
+ }
+ }
+ else
+ fi = NULL;
if ((it) && (fi))
{
- if (it->x < fi->x) fi = NULL;
+ if (it->source_pos < fi->text_pos) fi = NULL;
else it = NULL;
}
if (it)
if (!ln) return;
if (ln->items)
- it = (Evas_Object_Textblock_Item *)((EINA_INLIST_GET(ln->items))->last);
+ {
+ Evas_Object_Textblock_Item *i;
+ it = ln->items;
+ EINA_INLIST_FOREACH(ln->items, i)
+ {
+ if (it->source_pos < i->source_pos)
+ {
+ it = i;
+ }
+ }
+ }
else
it = NULL;
if (ln->format_items)
- fi = (Evas_Object_Textblock_Format_Item *)((EINA_INLIST_GET(ln->format_items))->last);
+ {
+ Evas_Object_Textblock_Format_Item *i;
+ fi = ln->format_items;
+ EINA_INLIST_FOREACH(ln->format_items, i)
+ {
+ if (fi->text_pos < i->text_pos)
+ {
+ fi = i;
+ }
+ }
+ }
else
fi = NULL;
if ((it) && (fi))
{
- if ((it->x + it->w) > (fi->x + fi->w)) fi = NULL;
+ if (it->source_pos > fi->text_pos) fi = NULL;
else it = NULL;
}
if (it)
/* Advance all the cursors after our cursor */
_evas_textblock_cursors_update_offset(cur, cur->node, cur->pos, 1);
-
if (_IS_PARAGRAPH_SEPARATOR(format))
{
_evas_textblock_cursor_break_paragraph(cur, n);
eina_ustrbuf_string_get(cur->node->unicode));
#endif
}
-
}
_evas_textblock_changed(o, cur->obj);
{
_find_layout_item_line_match(cur->obj, cur->node, cur->pos, &ln, &it);
}
+
if (ln && it)
{
pos = cur->pos - it->source_pos;
pos,
&x, &y, &w, &h);
}
- x += ln->x + it->x;
- if (x < ln->x)
- {
- x = ln->x;
- }
+
+ /* Handle bidi adjustments */
+ if (_evas_textblock_cursor_is_at_the_end(cur))
+ {
+#ifdef BIDI_SUPPORT
+ if (EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(it->bidi_props.props))
+ {
+ x = ln->x;
+ }
+ else
+#endif
+ {
+ x = ln->x + ln->w;
+ }
+ }
+ else
+ {
+ x += ln->x + it->x - it->inset;
+ }
+ if (x < ln->x)
+ {
+ x = ln->x;
+ }
y = ln->y;
h = ln->h;
}
}
else
{
- x = ln->x + fi->x + fi->w;
+#ifdef BIDI_SUPPORT
+ if (EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(
+ fi->text_node->bidi_props))
+ {
+ x = ln->x;
+ }
+ else
+#endif
+ {
+ x = ln->x + ln->w;
+ }
y = ln->y;
}
w = 0;
*/
-#if 0
+#if 1
/* Good for debugging */
void
pfnode(Evas_Object_Textblock_Node_Format *n)
printf("Format Node: %p\n", 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("text_node = %p, offset = %u, visible = %d\n", n->text_node, n->offset, n->visible);
- printf("%s\n", eina_strbuf_string_get(n->format));
+ printf("'%s'\n", eina_strbuf_string_get(n->format));
}
void
printf("Text Node: %p\n", 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));
+ printf("'%ls'\n", eina_ustrbuf_string_get(n->unicode));
}
#endif
return NULL;
+ if (!evas_bidi_is_rtl_str(eina_ustr)) /* No need to handle bidi */
+ {
+ len = -1;
+ goto cleanup;
+ }
len = eina_unicode_strlen(eina_ustr);
/* The size of fribidichar s different than eina_unicode, convert */
ustr = (const FriBidiChar *) eina_ustr;
}
-
- if (!evas_bidi_is_rtl_str(eina_ustr)) /* No need to handle bidi */
- {
- len = -1;
- goto cleanup;
- }
-
bidi_props = evas_bidi_paragraph_props_new();
/* Prep work for reordering */
* Reorders ustr according to the bidi props.
*
* @param ustr the string to reorder. - Null is ok, will just populate the map.
- * @param intl_props the intl properties to rerorder according to.
+ * @param start the start of the line
+ * @param len the length of the line
+ * @param props the paragraph props to reorder according to
* @param _v_to_l The visual to logical map to populate - if NULL it won't populate it.
* @return #EINA_FALSE on success, #EINA_TRUE on error.
*/
Eina_Bool
-evas_bidi_props_reorder_line(Eina_Unicode *eina_ustr, const Evas_BiDi_Props *intl_props, EvasBiDiStrIndex **_v_to_l)
+evas_bidi_props_reorder_line(Eina_Unicode *eina_ustr, size_t start, size_t len, const Evas_BiDi_Paragraph_Props *props, EvasBiDiStrIndex **_v_to_l)
{
EvasBiDiStrIndex *v_to_l = NULL;
- FriBidiChar *ustr, *base_ustr = NULL;
- size_t len;
+ FriBidiChar *ustr = NULL, *base_ustr = NULL;
- if (!intl_props->props)
+ if (!props)
return EINA_FALSE;
- len = eina_unicode_strlen(eina_ustr);
- /* The size of fribidichar is different than eina_unicode, convert */
- if (sizeof(Eina_Unicode) != sizeof(FriBidiChar))
+ if (eina_ustr)
{
- base_ustr = ustr = calloc(len + 1, sizeof(FriBidiChar));
- ustr = _evas_bidi_unicode_to_fribidichar(ustr, eina_ustr);
- }
- else
- {
- ustr = (FriBidiChar *) eina_ustr;
+ /* The size of fribidichar is different than eina_unicode, convert */
+ if (sizeof(Eina_Unicode) != sizeof(FriBidiChar))
+ {
+ base_ustr = ustr = calloc(len + 1, sizeof(FriBidiChar));
+ ustr = _evas_bidi_unicode_to_fribidichar(ustr, eina_ustr);
+ }
+ else
+ {
+ ustr = (FriBidiChar *) eina_ustr;
+ }
}
/* Shaping must be done *BEFORE* breaking to lines so there's no choice but
doing it in textblock. */
if (!fribidi_reorder_line (FRIBIDI_FLAGS_DEFAULT,
- intl_props->props->char_types + intl_props->start,
- len, 0, intl_props->props->direction,
- intl_props->props->embedding_levels + intl_props->start,
+ props->char_types + start,
+ len, 0, props->direction,
+ props->embedding_levels + start,
ustr, v_to_l))
{
goto error;
/* The size of fribidichar is different than eina_unicode, convert */
if (sizeof(Eina_Unicode) != sizeof(FriBidiChar))
{
- eina_ustr = _evas_bidi_fribidichar_to_unicode(eina_ustr, base_ustr);
+ _evas_bidi_fribidichar_to_unicode(eina_ustr, base_ustr);
free(base_ustr);
}
return EINA_FALSE;
/**
* @internal
+ * Returns the end of the current run of text
+ *
+ * @param bidi_props the properties
+ * @param len the length of the string
+ * @return the position of the end of the run (offset from
+ * bidi_props->props->start), 0 when there is no end (i.e all the text)
+ */
+int
+evas_bidi_end_of_run_get(const Evas_BiDi_Props *bidi_props, int len)
+{
+ EvasBiDiLevel *i;
+ EvasBiDiLevel base;
+
+ if (!bidi_props || !bidi_props->props || (len <= 0))
+ return 0;
+
+ i = bidi_props->props->embedding_levels + bidi_props->start;
+ base = *i;
+ for ( ; (len > 0) && (base == *i) ; len--, i++)
+ ;
+
+ if (len == 0)
+ {
+ return 0;
+ }
+ return i - (bidi_props->props->embedding_levels + bidi_props->start);
+}
+
+/**
+ * @internal
* Returns the visual string index from the logical string index.
*
* @param v_to_l the visual to logical map