/* FIXME: Write an intro + explanation about all of this object, including * the internal types and functions. It's time consuming to understand what's * going on here without a reasonable amount of help. */ #include "evas_common.h" #include "evas_private.h" /* save typing */ #define ENFN obj->layer->evas->engine.func #define ENDT obj->layer->evas->engine.data.output /* private magic number for textblock objects */ static const char o_type[] = "textblock"; #define EVAS_TEXTBLOCK_REPLACEMENT_CHAR 0xFFFD /* private struct for textblock object internal data */ typedef struct _Evas_Object_Textblock Evas_Object_Textblock; typedef struct _Evas_Object_Style_Tag Evas_Object_Style_Tag; typedef enum _Evas_Object_Textblock_Node_Type Evas_Object_Textblock_Node_Type; typedef struct _Evas_Object_Textblock_Node_Text Evas_Object_Textblock_Node_Text; /* * Defined in Evas.h typedef struct _Evas_Object_Textblock_Node_Format Evas_Object_Textblock_Node_Format; */ typedef struct _Evas_Object_Textblock_Paragraph Evas_Object_Textblock_Paragraph; typedef struct _Evas_Object_Textblock_Line Evas_Object_Textblock_Line; typedef struct _Evas_Object_Textblock_Item Evas_Object_Textblock_Item; typedef struct _Evas_Object_Textblock_Format_Item Evas_Object_Textblock_Format_Item; typedef struct _Evas_Object_Textblock_Format Evas_Object_Textblock_Format; /* the current state of the formatting */ #define GET_PREV(text, ind) (((ind) > 0) ? (text[(ind)--]) : (text[ind])) #define GET_NEXT(text, ind) ((text[ind]) ? (text[(ind)++]) : (text[ind])) enum _Evas_Object_Textblock_Node_Type { NODE_TEXT, NODE_FORMAT }; struct _Evas_Object_Style_Tag { EINA_INLIST; char *tag; char *replace; size_t tag_len; size_t replace_len; }; struct _Evas_Object_Textblock_Node_Text { EINA_INLIST; Eina_UStrbuf *unicode; char * utf8; Evas_Object_Textblock_Node_Format *format_node; Evas_BiDi_Paragraph_Props bidi_props; }; struct _Evas_Object_Textblock_Node_Format { EINA_INLIST; Eina_Strbuf *format; Evas_Object_Textblock_Node_Text *text_node; size_t offset; Eina_Bool visible; }; #define _NODE_TEXT(x) ((Evas_Object_Textblock_Node_Text *) (x)) #define _NODE_FORMAT(x) ((Evas_Object_Textblock_Node_Format *) (x)) struct _Evas_Object_Textblock_Paragraph { EINA_INLIST; Evas_Object_Textblock_Line *lines; int x, y, w, h; int par_no; }; struct _Evas_Object_Textblock_Line { EINA_INLIST; Evas_Object_Textblock_Item *items; Evas_Object_Textblock_Format_Item *format_items; int x, y, w, h; int baseline; int line_no; }; struct _Evas_Object_Textblock_Item { EINA_INLIST; Eina_Unicode *text; Evas_Object_Textblock_Format *format; Evas_Object_Textblock_Node_Text *source_node; int x, w, h; int inset, baseline; int source_pos; unsigned char type; Evas_BiDi_Props bidi_props; }; struct _Evas_Object_Textblock_Format_Item { EINA_INLIST; const char *item; Evas_Object_Textblock_Node_Format *source_node; int x, w, h, y, ascent, descent; unsigned char vsize : 2; unsigned char size : 2; unsigned char formatme : 1; unsigned char ___padding___ : 3; }; struct _Evas_Object_Textblock_Format { int ref; double halign; double valign; struct { const char *name; const char *source; const char *fallbacks; int size; void *font; } font; struct { struct { unsigned char r, g, b, a; } normal, underline, underline2, outline, shadow, glow, glow2, backing, strikethrough; } color; struct { int l, r; } margin; int tabstops; int linesize; double linerelsize; int linegap; double linerelgap; double linefill; unsigned char style; unsigned char wrap_word : 1; unsigned char wrap_char : 1; unsigned char underline : 1; unsigned char underline2 : 1; unsigned char strikethrough : 1; unsigned char backing : 1; }; struct _Evas_Textblock_Style { char *style_text; char *default_tag; Evas_Object_Style_Tag *tags; Eina_List *objects; unsigned char delete_me : 1; }; struct _Evas_Textblock_Cursor { Evas_Object *obj; int pos; Evas_Object_Textblock_Node_Text *node; }; struct _Evas_Object_Textblock { DATA32 magic; Evas_Textblock_Style *style; Evas_Textblock_Cursor *cursor; Eina_List *cursors; Evas_Object_Textblock_Node_Text *text_nodes; Evas_Object_Textblock_Node_Format *format_nodes; Evas_Object_Textblock_Paragraph *paragraphs; Evas_Object_Textblock_Line *lines; int last_w; struct { int l, r, t, b; } style_pad; char *markup_text; void *engine_data; const char *repch; struct { int w, h; unsigned char valid : 1; } formatted, native; unsigned char redraw : 1; unsigned char changed : 1; }; /* private methods for textblock objects */ static void evas_object_textblock_init(Evas_Object *obj); static void *evas_object_textblock_new(void); static void evas_object_textblock_render(Evas_Object *obj, void *output, void *context, void *surface, int x, int y); static void evas_object_textblock_free(Evas_Object *obj); static void evas_object_textblock_render_pre(Evas_Object *obj); static void evas_object_textblock_render_post(Evas_Object *obj); static unsigned int evas_object_textblock_id_get(Evas_Object *obj); static unsigned int evas_object_textblock_visual_id_get(Evas_Object *obj); static void *evas_object_textblock_engine_data_get(Evas_Object *obj); static int evas_object_textblock_is_opaque(Evas_Object *obj); static int evas_object_textblock_was_opaque(Evas_Object *obj); static void evas_object_textblock_coords_recalc(Evas_Object *obj); static void evas_object_textblock_scale_update(Evas_Object *obj); static const Evas_Object_Func object_func = { /* methods (compulsory) */ evas_object_textblock_free, evas_object_textblock_render, evas_object_textblock_render_pre, evas_object_textblock_render_post, evas_object_textblock_id_get, evas_object_textblock_visual_id_get, evas_object_textblock_engine_data_get, /* these are optional. NULL = nothing */ NULL, NULL, NULL, NULL, evas_object_textblock_is_opaque, evas_object_textblock_was_opaque, NULL, NULL, evas_object_textblock_coords_recalc, evas_object_textblock_scale_update, NULL, NULL, NULL }; /* the actual api call to add a textblock */ #define TB_HEAD() \ Evas_Object_Textblock *o; \ MAGIC_CHECK(obj, Evas_Object, MAGIC_OBJ); \ return; \ MAGIC_CHECK_END(); \ o = (Evas_Object_Textblock *)(obj->object_data); \ MAGIC_CHECK(o, Evas_Object_Textblock, MAGIC_OBJ_TEXTBLOCK); \ return; \ MAGIC_CHECK_END(); #define TB_HEAD_RETURN(x) \ Evas_Object_Textblock *o; \ MAGIC_CHECK(obj, Evas_Object, MAGIC_OBJ); \ return (x); \ MAGIC_CHECK_END(); \ o = (Evas_Object_Textblock *)(obj->object_data); \ MAGIC_CHECK(o, Evas_Object_Textblock, MAGIC_OBJ_TEXTBLOCK); \ return (x); \ MAGIC_CHECK_END(); /** * @addtogroup Evas_Object_Textblock * @{ */ static Evas_Object_Textblock_Node_Format *_evas_textblock_cursor_node_format_before_or_at_pos_get(const Evas_Textblock_Cursor *cur); static size_t _evas_textblock_node_format_pos_get(const Evas_Object_Textblock_Node_Format *fmt); static Eina_Bool _evas_textblock_format_is_visible(const char *s); static void _evas_textblock_node_format_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n); static void _evas_textblock_node_format_free(Evas_Object_Textblock_Node_Format *n); static void _evas_textblock_node_text_free(Evas_Object_Textblock_Node_Text *n); static void _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj); /* styles */ static void _style_clear(Evas_Textblock_Style *ts) { if (ts->style_text) free(ts->style_text); if (ts->default_tag) free(ts->default_tag); while (ts->tags) { Evas_Object_Style_Tag *tag; tag = (Evas_Object_Style_Tag *)ts->tags; ts->tags = (Evas_Object_Style_Tag *)eina_inlist_remove(EINA_INLIST_GET(ts->tags), EINA_INLIST_GET(tag)); free(tag->tag); free(tag->replace); free(tag); } ts->style_text = NULL; ts->default_tag = NULL; ts->tags = NULL; } static inline const char * _style_match_replace(Evas_Textblock_Style *ts, const char *s, size_t replace_len, size_t *tag_len) { Evas_Object_Style_Tag *tag; EINA_INLIST_FOREACH(ts->tags, tag) { if (tag->replace_len != replace_len) continue; if (!strcmp(tag->replace, s)) { *tag_len = tag->tag_len; return tag->tag; } } *tag_len = 0; return NULL; } static inline const char * _style_match_tag(Evas_Textblock_Style *ts, const char *s, size_t tag_len, size_t *replace_len) { Evas_Object_Style_Tag *tag; EINA_INLIST_FOREACH(ts->tags, tag) { if (tag->tag_len != tag_len) continue; if (!strcmp(tag->tag, s)) { *replace_len = tag->replace_len; return tag->replace; } } *replace_len = 0; return NULL; } static void _nodes_clear(const Evas_Object *obj) { Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); while (o->text_nodes) { Evas_Object_Textblock_Node_Text *n; n = o->text_nodes; 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) { Evas_Object_Textblock_Node_Format *n; n = o->format_nodes; o->format_nodes = _NODE_FORMAT(eina_inlist_remove(EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n))); _evas_textblock_node_format_free(n); } } static void _format_unref_free(const Evas_Object *obj, Evas_Object_Textblock_Format *fmt) { fmt->ref--; if (fmt->ref > 0) return; if (fmt->font.name) eina_stringshare_del(fmt->font.name); if (fmt->font.fallbacks) eina_stringshare_del(fmt->font.fallbacks); if (fmt->font.source) eina_stringshare_del(fmt->font.source); evas_font_free(obj->layer->evas, fmt->font.font); free(fmt); } static void _line_free(const Evas_Object *obj, Evas_Object_Textblock_Line *ln) { while (ln->items) { Evas_Object_Textblock_Item *it; it = (Evas_Object_Textblock_Item *)ln->items; ln->items = (Evas_Object_Textblock_Item *)eina_inlist_remove(EINA_INLIST_GET(ln->items), EINA_INLIST_GET(ln->items)); if (it->text) free(it->text); _format_unref_free(obj, it->format); free(it); } while (ln->format_items) { Evas_Object_Textblock_Format_Item *fi; fi = (Evas_Object_Textblock_Format_Item *)ln->format_items; ln->format_items = (Evas_Object_Textblock_Format_Item *)eina_inlist_remove(EINA_INLIST_GET(ln->format_items), EINA_INLIST_GET(ln->format_items)); if (fi->item) eina_stringshare_del(fi->item); free(fi); } if (ln) free(ln); } static void _lines_clear(const Evas_Object *obj, Evas_Object_Textblock_Line *lines) { while (lines) { Evas_Object_Textblock_Line *ln; ln = (Evas_Object_Textblock_Line *)lines; lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(lines), EINA_INLIST_GET(ln)); _line_free(obj, ln); } } /* table of html escapes (that i can find) this should be ordered with the * most common first as it's a linear search to match - no hash for this. * * these are stored as one large string and one additional array that * contains the offsets to the tokens for space efficiency. */ static const char escape_strings[] = /* most common escaped stuff */ " \0" "\x20\0" /* NOTE: this here to avoid escaping to   */ " \0" "\x20\0" /* NOTE: we allow nsbp's to break as we map early - maybe map to ascii 0x01 and then make the rendering code think 0x01 -> 0x20 */ ""\0" "\x22\0" "&\0" "\x26\0" "<\0" "\x3c\0" ">\0" "\x3e\0" /* all the rest */ "¡\0" "\xc2\xa1\0" "¢\0" "\xc2\xa2\0" "£\0" "\xc2\xa3\0" "¤\0" "\xc2\xa4\0" "¥\0" "\xc2\xa5\0" "¦\0" "\xc2\xa6\0" "§\0" "\xc2\xa7\0" "¨\0" "\xc2\xa8\0" "©\0" "\xc2\xa9\0" "ª\0" "\xc2\xaa\0" "«\0" "\xc2\xab\0" "¬\0" "\xc2\xac\0" "®\0" "\xc2\xae\0" "¯\0" "\xc2\xaf\0" "°\0" "\xc2\xb0\0" "±\0" "\xc2\xb1\0" "²\0" "\xc2\xb2\0" "³\0" "\xc2\xb3\0" "´\0" "\xc2\xb4\0" "µ\0" "\xc2\xb5\0" "¶\0" "\xc2\xb6\0" "·\0" "\xc2\xb7\0" "¸\0" "\xc2\xb8\0" "¹\0" "\xc2\xb9\0" "º\0" "\xc2\xba\0" "»\0" "\xc2\xbb\0" "¼\0" "\xc2\xbc\0" "½\0" "\xc2\xbd\0" "¾\0" "\xc2\xbe\0" "¿\0" "\xc2\xbf\0" "À\0" "\xc3\x80\0" "Á\0" "\xc3\x81\0" "Â\0" "\xc3\x82\0" "Ã\0" "\xc3\x83\0" "Ä\0" "\xc3\x84\0" "Å\0" "\xc3\x85\0" "&Aelig;\0" "\xc3\x86\0" "Ç\0" "\xc3\x87\0" "È\0" "\xc3\x88\0" "É\0" "\xc3\x89\0" "Ê\0" "\xc3\x8a\0" "Ë\0" "\xc3\x8b\0" "Ì\0" "\xc3\x8c\0" "Í\0" "\xc3\x8d\0" "Î\0" "\xc3\x8e\0" "Ï\0" "\xc3\x8f\0" "&Eth;\0" "\xc3\x90\0" "Ñ\0" "\xc3\x91\0" "Ò\0" "\xc3\x92\0" "Ó\0" "\xc3\x93\0" "Ô\0" "\xc3\x94\0" "Õ\0" "\xc3\x95\0" "Ö\0" "\xc3\x96\0" "×\0" "\xc3\x97\0" "Ø\0" "\xc3\x98\0" "Ù\0" "\xc3\x99\0" "Ú\0" "\xc3\x9a\0" "Û\0" "\xc3\x9b\0" "Ý\0" "\xc3\x9d\0" "&Thorn;\0" "\xc3\x9e\0" "ß\0" "\xc3\x9f\0" "à\0" "\xc3\xa0\0" "á\0" "\xc3\xa1\0" "â\0" "\xc3\xa2\0" "ã\0" "\xc3\xa3\0" "ä\0" "\xc3\xa4\0" "å\0" "\xc3\xa5\0" "æ\0" "\xc3\xa6\0" "ç\0" "\xc3\xa7\0" "è\0" "\xc3\xa8\0" "é\0" "\xc3\xa9\0" "ê\0" "\xc3\xaa\0" "ë\0" "\xc3\xab\0" "ì\0" "\xc3\xac\0" "í\0" "\xc3\xad\0" "î\0" "\xc3\xae\0" "ï\0" "\xc3\xaf\0" "ð\0" "\xc3\xb0\0" "ñ\0" "\xc3\xb1\0" "ò\0" "\xc3\xb2\0" "ó\0" "\xc3\xb3\0" "ô\0" "\xc3\xb4\0" "õ\0" "\xc3\xb5\0" "ö\0" "\xc3\xb6\0" "÷\0" "\xc3\xb7\0" "ø\0" "\xc3\xb8\0" "ù\0" "\xc3\xb9\0" "ú\0" "\xc3\xba\0" "û\0" "\xc3\xbb\0" "ü\0" "\xc3\xbc\0" "ý\0" "\xc3\xbd\0" "þ\0" "\xc3\xbe\0" "ÿ\0" "\xc3\xbf\0" "α\0" "\xce\x91\0" "β\0" "\xce\x92\0" "γ\0" "\xce\x93\0" "δ\0" "\xce\x94\0" "ε\0" "\xce\x95\0" "ζ\0" "\xce\x96\0" "η\0" "\xce\x97\0" "θ\0" "\xce\x98\0" "ι\0" "\xce\x99\0" "κ\0" "\xce\x9a\0" "λ\0" "\xce\x9b\0" "μ\0" "\xce\x9c\0" "ν\0" "\xce\x9d\0" "ξ\0" "\xce\x9e\0" "ο\0" "\xce\x9f\0" "π\0" "\xce\xa0\0" "ρ\0" "\xce\xa1\0" "σ\0" "\xce\xa3\0" "τ\0" "\xce\xa4\0" "υ\0" "\xce\xa5\0" "φ\0" "\xce\xa6\0" "χ\0" "\xce\xa7\0" "ψ\0" "\xce\xa8\0" "ω\0" "\xce\xa9\0" "…\0" "\xe2\x80\xa6\0" "€\0" "\xe2\x82\xac\0" "←\0" "\xe2\x86\x90\0" "↑\0" "\xe2\x86\x91\0" "→\0" "\xe2\x86\x92\0" "↓\0" "\xe2\x86\x93\0" "↔\0" "\xe2\x86\x94\0" "←\0" "\xe2\x87\x90\0" "→\0" "\xe2\x87\x92\0" "∀\0" "\xe2\x88\x80\0" "∃\0" "\xe2\x88\x83\0" "∇\0" "\xe2\x88\x87\0" "∏\0" "\xe2\x88\x8f\0" "∑\0" "\xe2\x88\x91\0" "∧\0" "\xe2\x88\xa7\0" "∨\0" "\xe2\x88\xa8\0" "∫\0" "\xe2\x88\xab\0" "≠\0" "\xe2\x89\xa0\0" "≡\0" "\xe2\x89\xa1\0" "⊕\0" "\xe2\x8a\x95\0" "⊥\0" "\xe2\x8a\xa5\0" "†\0" "\xe2\x80\xa0\0" "‡\0" "\xe2\x80\xa1\0" "•\0" "\xe2\x80\xa2\0" ; static int _is_white(int c) { /* * unicode list of whitespace chars * * 0009..000D .. * 0020 SPACE * 0085 * 00A0 NO-BREAK SPACE * 1680 OGHAM SPACE MARK * 180E MONGOLIAN VOWEL SEPARATOR * 2000..200A EN QUAD..HAIR SPACE * 2028 LINE SEPARATOR * 2029 PARAGRAPH SEPARATOR * 202F NARROW NO-BREAK SPACE * 205F MEDIUM MATHEMATICAL SPACE * 3000 IDEOGRAPHIC SPACE */ if ( (c == 0x20) || ((c >= 0x9) && (c <= 0xd)) || (c == 0x85) || (c == 0xa0) || (c == 0x1680) || (c == 0x180e) || ((c >= 0x2000) && (c <= 0x200a)) || (c == 0x2028) || (c == 0x2029) || (c == 0x202f) || (c == 0x205f) || (c == 0x3000) ) return 1; return 0; } static char * _clean_white(int clean_start, int clean_end, char *str) { char *p, *p2, *str2 = NULL; int white, pwhite, start, ok; return str; str2 = malloc(strlen(str) + 2); p = str; p2 = str2; white = 0; start = 1; ok = 1; while (*p != 0) { pwhite = white; if (_is_white(*p)) white = 1; else white = 0; if ((pwhite) && (white)) ok = 0; else { if (!clean_start) { if ((start) && (pwhite) && (!white)) { // *p2 = ' '; // p2++; } } ok = 1; if (!white) start = 0; } if (clean_start) { if ((start) && (ok)) ok = 0; } if (ok) { *p2 = *p; p2++; } p++; } *p2 = 0; if (clean_end) { while (p2 > str2) { p2--; if (!(isspace(*p2) || _is_white(*p2))) break; *p2 = 0; } } free(str); return str2; } /* Appends the text between s and p to the current cursor */ static void _append_text_run(Evas_Object_Textblock *o, const char *s, const char *p) { if ((s) && (p > s)) { char *ts; ts = alloca(p - s + 1); strncpy(ts, s, p - s); ts[p - s] = 0; ts = _clean_white(0, 0, ts); evas_textblock_cursor_text_append(o->cursor, ts); } } static void _prepend_text_run(Evas_Object_Textblock *o, const char *s, const char *p) { if ((s) && (p > s)) { char *ts; ts = alloca(p - s + 1); strncpy(ts, s, p - s); ts[p - s] = 0; ts = _clean_white(0, 0, ts); evas_textblock_cursor_text_prepend(o->cursor, ts); } } static int _hex_string_get(char ch) { if ((ch >= '0') && (ch <= '9')) return (ch - '0'); else if ((ch >= 'A') && (ch <= 'F')) return (ch - 'A' + 10); else if ((ch >= 'a') && (ch <= 'f')) return (ch - 'a' + 10); return 0; } static void _format_color_parse(const char *str, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) { int slen; slen = strlen(str); *r = *g = *b = *a = 0; if (slen == 7) /* #RRGGBB */ { *r = (_hex_string_get(str[1]) << 4) | (_hex_string_get(str[2])); *g = (_hex_string_get(str[3]) << 4) | (_hex_string_get(str[4])); *b = (_hex_string_get(str[5]) << 4) | (_hex_string_get(str[6])); *a = 0xff; } else if (slen == 9) /* #RRGGBBAA */ { *r = (_hex_string_get(str[1]) << 4) | (_hex_string_get(str[2])); *g = (_hex_string_get(str[3]) << 4) | (_hex_string_get(str[4])); *b = (_hex_string_get(str[5]) << 4) | (_hex_string_get(str[6])); *a = (_hex_string_get(str[7]) << 4) | (_hex_string_get(str[8])); } else if (slen == 4) /* #RGB */ { *r = _hex_string_get(str[1]); *r = (*r << 4) | *r; *g = _hex_string_get(str[2]); *g = (*g << 4) | *g; *b = _hex_string_get(str[3]); *b = (*b << 4) | *b; *a = 0xff; } else if (slen == 5) /* #RGBA */ { *r = _hex_string_get(str[1]); *r = (*r << 4) | *r; *g = _hex_string_get(str[2]); *g = (*g << 4) | *g; *b = _hex_string_get(str[3]); *b = (*b << 4) | *b; *a = _hex_string_get(str[4]); *a = (*a << 4) | *a; } *r = (*r * *a) / 255; *g = (*g * *a) / 255; *b = (*b * *a) / 255; } static const char *fontstr = NULL; static const char *font_fallbacksstr = NULL; static const char *font_sizestr = NULL; static const char *font_sourcestr = NULL; static const char *colorstr = NULL; static const char *underline_colorstr = NULL; static const char *underline2_colorstr = NULL; static const char *outline_colorstr = NULL; static const char *shadow_colorstr = NULL; static const char *glow_colorstr = NULL; static const char *glow2_colorstr = NULL; static const char *backing_colorstr = NULL; static const char *strikethrough_colorstr = NULL; static const char *alignstr = NULL; static const char *valignstr = NULL; static const char *wrapstr = NULL; static const char *left_marginstr = NULL; static const char *right_marginstr = NULL; static const char *underlinestr = NULL; static const char *strikethroughstr = NULL; static const char *backingstr = NULL; static const char *stylestr = NULL; static const char *tabstopsstr = NULL; static const char *linesizestr = NULL; static const char *linerelsizestr = NULL; static const char *linegapstr = NULL; static const char *linerelgapstr = NULL; static const char *itemstr = NULL; static const char *linefillstr = NULL; static void _format_command_init(void) { if (!fontstr) { fontstr = eina_stringshare_add("font"); font_fallbacksstr = eina_stringshare_add("font_fallbacks"); font_sizestr = eina_stringshare_add("font_size"); font_sourcestr = eina_stringshare_add("font_source"); colorstr = eina_stringshare_add("color"); underline_colorstr = eina_stringshare_add("underline_color"); underline2_colorstr = eina_stringshare_add("underline2_color"); outline_colorstr = eina_stringshare_add("outline_color"); shadow_colorstr = eina_stringshare_add("shadow_color"); glow_colorstr = eina_stringshare_add("glow_color"); glow2_colorstr = eina_stringshare_add("glow2_color"); backing_colorstr = eina_stringshare_add("backing_color"); strikethrough_colorstr = eina_stringshare_add("strikethrough_color"); alignstr = eina_stringshare_add("align"); valignstr = eina_stringshare_add("valign"); wrapstr = eina_stringshare_add("wrap"); left_marginstr = eina_stringshare_add("left_margin"); right_marginstr = eina_stringshare_add("right_margin"); underlinestr = eina_stringshare_add("underline"); strikethroughstr = eina_stringshare_add("strikethrough"); backingstr = eina_stringshare_add("backing"); stylestr = eina_stringshare_add("style"); tabstopsstr = eina_stringshare_add("tabstops"); linesizestr = eina_stringshare_add("linesize"); linerelsizestr = eina_stringshare_add("linerelsize"); linegapstr = eina_stringshare_add("linegap"); linerelgapstr = eina_stringshare_add("linerelgap"); itemstr = eina_stringshare_add("item"); linefillstr = eina_stringshare_add("linefill"); } else { eina_stringshare_ref(fontstr); eina_stringshare_ref(font_fallbacksstr); eina_stringshare_ref(font_sizestr); eina_stringshare_ref(font_sourcestr); eina_stringshare_ref(colorstr); eina_stringshare_ref(underline_colorstr); eina_stringshare_ref(underline2_colorstr); eina_stringshare_ref(outline_colorstr); eina_stringshare_ref(shadow_colorstr); eina_stringshare_ref(glow_colorstr); eina_stringshare_ref(glow2_colorstr); eina_stringshare_ref(backing_colorstr); eina_stringshare_ref(strikethrough_colorstr); eina_stringshare_ref(alignstr); eina_stringshare_ref(valignstr); eina_stringshare_ref(wrapstr); eina_stringshare_ref(left_marginstr); eina_stringshare_ref(right_marginstr); eina_stringshare_ref(underlinestr); eina_stringshare_ref(strikethroughstr); eina_stringshare_ref(backingstr); eina_stringshare_ref(stylestr); eina_stringshare_ref(tabstopsstr); eina_stringshare_ref(linesizestr); eina_stringshare_ref(linerelsizestr); eina_stringshare_ref(linegapstr); eina_stringshare_ref(linerelgapstr); eina_stringshare_ref(itemstr); eina_stringshare_ref(linefillstr); } } static void _format_command_shutdown(void) { return; /*FIXME: should del, the problem is that it's not possible to know the ref * count so it's not possible to know when the last textblock finished. * Should probably just add a refcount here */ eina_stringshare_del(fontstr); eina_stringshare_del(font_fallbacksstr); eina_stringshare_del(font_sizestr); eina_stringshare_del(font_sourcestr); eina_stringshare_del(colorstr); eina_stringshare_del(underline_colorstr); eina_stringshare_del(underline2_colorstr); eina_stringshare_del(outline_colorstr); eina_stringshare_del(shadow_colorstr); eina_stringshare_del(glow_colorstr); eina_stringshare_del(glow2_colorstr); eina_stringshare_del(backing_colorstr); eina_stringshare_del(strikethrough_colorstr); eina_stringshare_del(alignstr); eina_stringshare_del(valignstr); eina_stringshare_del(wrapstr); eina_stringshare_del(left_marginstr); eina_stringshare_del(right_marginstr); eina_stringshare_del(underlinestr); eina_stringshare_del(strikethroughstr); eina_stringshare_del(backingstr); eina_stringshare_del(stylestr); eina_stringshare_del(tabstopsstr); eina_stringshare_del(linesizestr); eina_stringshare_del(linerelsizestr); eina_stringshare_del(linegapstr); eina_stringshare_del(linerelgapstr); eina_stringshare_del(itemstr); eina_stringshare_del(linefillstr); } static void _format_clean_param(char *dst, const char *src) { const char *ss; char *ds; ds = dst; for (ss = src; *ss; ss++, ds++) { if ((*ss == '\\') && *(ss + 1)) ss++; *ds = *ss; } *ds = 0; } static void _format_command(Evas_Object *obj, Evas_Object_Textblock_Format *fmt, const char *cmd, const char *param) { int new_font = 0; char *tmp_param; tmp_param = alloca(strlen(param) + 1); _format_clean_param(tmp_param, param); if (cmd == fontstr) { if ((!fmt->font.name) || ((fmt->font.name) && (strcmp(fmt->font.name, tmp_param)))) { if (fmt->font.name) eina_stringshare_del(fmt->font.name); fmt->font.name = eina_stringshare_add(tmp_param); new_font = 1; } } else if (cmd == font_fallbacksstr) { if ((!fmt->font.fallbacks) || ((fmt->font.fallbacks) && (strcmp(fmt->font.fallbacks, tmp_param)))) { /* policy - when we say "fallbacks" do we prepend and use prior * fallbacks... or should we replace. for now we replace */ if (fmt->font.fallbacks) eina_stringshare_del(fmt->font.fallbacks); fmt->font.fallbacks = eina_stringshare_add(tmp_param); new_font = 1; } } else if (cmd == font_sizestr) { int v; v = atoi(tmp_param); if (v != fmt->font.size) { fmt->font.size = v; new_font = 1; } } else if (cmd == font_sourcestr) { if ((!fmt->font.source) || ((fmt->font.source) && (strcmp(fmt->font.source, tmp_param)))) { if (fmt->font.source) eina_stringshare_del(fmt->font.source); fmt->font.source = eina_stringshare_add(tmp_param); new_font = 1; } } else if (cmd == colorstr) _format_color_parse(tmp_param, &(fmt->color.normal.r), &(fmt->color.normal.g), &(fmt->color.normal.b), &(fmt->color.normal.a)); else if (cmd == underline_colorstr) _format_color_parse(tmp_param, &(fmt->color.underline.r), &(fmt->color.underline.g), &(fmt->color.underline.b), &(fmt->color.underline.a)); else if (cmd == underline2_colorstr) _format_color_parse(tmp_param, &(fmt->color.underline2.r), &(fmt->color.underline2.g), &(fmt->color.underline2.b), &(fmt->color.underline2.a)); else if (cmd == outline_colorstr) _format_color_parse(tmp_param, &(fmt->color.outline.r), &(fmt->color.outline.g), &(fmt->color.outline.b), &(fmt->color.outline.a)); else if (cmd == shadow_colorstr) _format_color_parse(tmp_param, &(fmt->color.shadow.r), &(fmt->color.shadow.g), &(fmt->color.shadow.b), &(fmt->color.shadow.a)); else if (cmd == glow_colorstr) _format_color_parse(tmp_param, &(fmt->color.glow.r), &(fmt->color.glow.g), &(fmt->color.glow.b), &(fmt->color.glow.a)); else if (cmd == glow2_colorstr) _format_color_parse(tmp_param, &(fmt->color.glow2.r), &(fmt->color.glow2.g), &(fmt->color.glow2.b), &(fmt->color.glow2.a)); else if (cmd == backing_colorstr) _format_color_parse(tmp_param, &(fmt->color.backing.r), &(fmt->color.backing.g), &(fmt->color.backing.b), &(fmt->color.backing.a)); else if (cmd == strikethrough_colorstr) _format_color_parse(tmp_param, &(fmt->color.strikethrough.r), &(fmt->color.strikethrough.g), &(fmt->color.strikethrough.b), &(fmt->color.strikethrough.a)); else if (cmd == alignstr) { if (!strcmp(tmp_param, "middle")) fmt->halign = 0.5; else if (!strcmp(tmp_param, "center")) fmt->halign = 0.5; else if (!strcmp(tmp_param, "left")) fmt->halign = 0.0; else if (!strcmp(tmp_param, "right")) fmt->halign = 1.0; else { char *endptr = NULL; double val = strtod(tmp_param, &endptr); if (endptr) { while (*endptr && _is_white(*endptr)) endptr++; if (*endptr == '%') val /= 100.0; } fmt->halign = val;; if (fmt->halign < 0.0) fmt->halign = 0.0; else if (fmt->halign > 1.0) fmt->halign = 1.0; } } else if (cmd == valignstr) { if (!strcmp(tmp_param, "top")) fmt->valign = 0.0; else if (!strcmp(tmp_param, "middle")) fmt->valign = 0.5; else if (!strcmp(tmp_param, "center")) fmt->valign = 0.5; else if (!strcmp(tmp_param, "bottom")) fmt->valign = 1.0; else if (!strcmp(tmp_param, "baseline")) fmt->valign = -1.0; else if (!strcmp(tmp_param, "base")) fmt->valign = -1.0; else { char *endptr = NULL; double val = strtod(tmp_param, &endptr); if (endptr) { while (*endptr && _is_white(*endptr)) endptr++; if (*endptr == '%') val /= 100.0; } fmt->valign = val; if (fmt->valign < 0.0) fmt->valign = 0.0; else if (fmt->valign > 1.0) fmt->valign = 1.0; } } else if (cmd == wrapstr) { if (!strcmp(tmp_param, "word")) { fmt->wrap_word = 1; fmt->wrap_char = 0; } else if (!strcmp(tmp_param, "char")) { fmt->wrap_word = 0; fmt->wrap_char = 1; } else { fmt->wrap_word = 0; fmt->wrap_char = 0; } } else if (cmd == left_marginstr) { if (!strcmp(tmp_param, "reset")) fmt->margin.l = 0; else { if (tmp_param[0] == '+') fmt->margin.l += atoi(&(tmp_param[1])); else if (tmp_param[0] == '-') fmt->margin.l -= atoi(&(tmp_param[1])); else fmt->margin.l = atoi(tmp_param); if (fmt->margin.l < 0) fmt->margin.l = 0; } } else if (cmd == right_marginstr) { if (!strcmp(tmp_param, "reset")) fmt->margin.r = 0; else { if (tmp_param[0] == '+') fmt->margin.r += atoi(&(tmp_param[1])); else if (tmp_param[0] == '-') fmt->margin.r -= atoi(&(tmp_param[1])); else fmt->margin.r = atoi(tmp_param); if (fmt->margin.r < 0) fmt->margin.r = 0; } } else if (cmd == underlinestr) { if (!strcmp(tmp_param, "off")) { fmt->underline = 0; fmt->underline2 = 0; } else if ((!strcmp(tmp_param, "on")) || (!strcmp(tmp_param, "single"))) { fmt->underline = 1; fmt->underline2 = 0; } else if (!strcmp(tmp_param, "double")) { fmt->underline = 1; fmt->underline2 = 1; } } else if (cmd == strikethroughstr) { if (!strcmp(tmp_param, "off")) fmt->strikethrough = 0; else if (!strcmp(tmp_param, "on")) fmt->strikethrough = 1; } else if (cmd == backingstr) { if (!strcmp(tmp_param, "off")) fmt->backing = 0; else if (!strcmp(tmp_param, "on")) fmt->backing = 1; } else if (cmd == stylestr) { if (!strcmp(tmp_param, "off")) fmt->style = EVAS_TEXT_STYLE_PLAIN; else if (!strcmp(tmp_param, "none")) fmt->style = EVAS_TEXT_STYLE_PLAIN; else if (!strcmp(tmp_param, "plain")) fmt->style = EVAS_TEXT_STYLE_PLAIN; else if (!strcmp(tmp_param, "shadow")) fmt->style = EVAS_TEXT_STYLE_SHADOW; else if (!strcmp(tmp_param, "outline")) fmt->style = EVAS_TEXT_STYLE_OUTLINE; else if (!strcmp(tmp_param, "soft_outline")) fmt->style = EVAS_TEXT_STYLE_SOFT_OUTLINE; else if (!strcmp(tmp_param, "outline_shadow")) fmt->style = EVAS_TEXT_STYLE_OUTLINE_SHADOW; else if (!strcmp(tmp_param, "outline_soft_shadow")) fmt->style = EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW; else if (!strcmp(tmp_param, "glow")) fmt->style = EVAS_TEXT_STYLE_GLOW; else if (!strcmp(tmp_param, "far_shadow")) fmt->style = EVAS_TEXT_STYLE_FAR_SHADOW; else if (!strcmp(tmp_param, "soft_shadow")) fmt->style = EVAS_TEXT_STYLE_SOFT_SHADOW; else if (!strcmp(tmp_param, "far_soft_shadow")) fmt->style = EVAS_TEXT_STYLE_FAR_SOFT_SHADOW; else fmt->style = EVAS_TEXT_STYLE_PLAIN; } else if (cmd == tabstopsstr) { fmt->tabstops = atoi(tmp_param); if (fmt->tabstops < 1) fmt->tabstops = 1; } else if (cmd == linesizestr) { fmt->linesize = atoi(tmp_param); fmt->linerelsize = 0.0; } else if (cmd == linerelsizestr) { char *endptr = NULL; double val = strtod(tmp_param, &endptr); if (endptr) { while (*endptr && _is_white(*endptr)) endptr++; if (*endptr == '%') { fmt->linerelsize = val / 100.0; fmt->linesize = 0; if (fmt->linerelsize < 0.0) fmt->linerelsize = 0.0; } } } else if (cmd == linegapstr) { fmt->linegap = atoi(tmp_param); fmt->linerelgap = 0.0; } else if (cmd == linerelgapstr) { char *endptr = NULL; double val = strtod(tmp_param, &endptr); if (endptr) { while (*endptr && _is_white(*endptr)) endptr++; if (*endptr == '%') { fmt->linerelgap = val / 100.0; fmt->linegap = 0; if (fmt->linerelgap < 0.0) fmt->linerelgap = 0.0; } } } else if (cmd == itemstr) { // itemstr == replacement object items in textblock - inline imges // for example } else if (cmd == linefillstr) { char *endptr = NULL; double val = strtod(tmp_param, &endptr); if (endptr) { while (*endptr && _is_white(*endptr)) endptr++; if (*endptr == '%') { fmt->linefill = val / 100.0; if (fmt->linefill < 0.0) fmt->linefill = 0.0; } } } if (new_font) { void *of; char *buf = NULL; of = fmt->font.font; if ((fmt->font.name) && (fmt->font.fallbacks)) { buf = malloc(strlen(fmt->font.name) + 1 + strlen(fmt->font.fallbacks) + 1); strcpy(buf, fmt->font.name); strcat(buf, ","); strcat(buf, fmt->font.fallbacks); } else if (fmt->font.name) buf = strdup(fmt->font.name); fmt->font.font = evas_font_load(obj->layer->evas, buf, fmt->font.source, (int)(((double)fmt->font.size) * obj->cur.scale)); if (buf) free(buf); if (of) evas_font_free(obj->layer->evas, of); } } static int _format_is_param(const char *item) { if (strchr(item, '=')) return 1; return 0; } static void _format_param_parse(char *item, const char **key, const char **val) { char *p, *tmp; const char *k, *v; p = strchr(item, '='); *p = '\0'; k = eina_stringshare_add(item); *key = k; *p = '='; p++; /* Null terminate before the spaces */ tmp = strchr(item, ' '); if (tmp) *tmp = '\0'; v = eina_stringshare_add(p); *val = v; } static const char * _format_parse(const char **s) { const char *p, *item; const char *s1 = NULL, *s2 = NULL; p = *s; if (*p == 0) return NULL; for (;;) { if (!s1) { if (*p != ' ') s1 = p; if (*p == 0) break; } else if (!s2) { if ((p > *s) && (p[-1] != '\\')) { if (*p == ' ') s2 = p; } if (*p == 0) s2 = p; } p++; if (s1 && s2) { item = s1; *s = s2; return item; } } *s = p; return NULL; } static void _format_fill(Evas_Object *obj, Evas_Object_Textblock_Format *fmt, const char *str) { const char *s; const char *item; s = str; /* get rid of anything +s or -s off the start of the string */ while ((*s == ' ') || (*s == '+') || (*s == '-')) s++; while ((item = _format_parse(&s))) { char tmp_delim = *s; if (_format_is_param(item)) { const char *key = NULL, *val = NULL; char *tmp = alloca(s - item + 1); strncpy(tmp, item, s - item); tmp[s - item] = '\0'; _format_param_parse(tmp, &key, &val); _format_command(obj, fmt, key, val); eina_stringshare_del(key); eina_stringshare_del(val); } else { /* immediate - not handled here */ } } } static Evas_Object_Textblock_Format * _format_dup(Evas_Object *obj, Evas_Object_Textblock_Format *fmt) { Evas_Object_Textblock_Format *fmt2; char *buf = NULL; fmt2 = calloc(1, sizeof(Evas_Object_Textblock_Format)); memcpy(fmt2, fmt, sizeof(Evas_Object_Textblock_Format)); fmt2->ref = 1; if (fmt->font.name) fmt2->font.name = eina_stringshare_add(fmt->font.name); if (fmt->font.fallbacks) fmt2->font.fallbacks = eina_stringshare_add(fmt->font.fallbacks); if (fmt->font.source) fmt2->font.source = eina_stringshare_add(fmt->font.source); if ((fmt2->font.name) && (fmt2->font.fallbacks)) { buf = malloc(strlen(fmt2->font.name) + 1 + strlen(fmt2->font.fallbacks) + 1); strcpy(buf, fmt2->font.name); strcat(buf, ","); strcat(buf, fmt2->font.fallbacks); } else if (fmt2->font.name) buf = strdup(fmt2->font.name); fmt2->font.font = evas_font_load(obj->layer->evas, buf, fmt2->font.source, (int)(((double)fmt2->font.size) * obj->cur.scale)); if (buf) free(buf); return fmt2; } typedef struct _Ctxt Ctxt; struct _Ctxt { Evas_Object *obj; Evas_Object_Textblock *o; Evas_Object_Textblock_Paragraph *paragraphs; Evas_Object_Textblock_Paragraph *par; Evas_Object_Textblock_Line *ln; Eina_List *format_stack; int x, y; int w, h; int wmax, hmax; int maxascent, maxdescent; int marginl, marginr; int line_no; int underline_extend; int have_underline, have_underline2; double align; }; static void _layout_format_ascent_descent_adjust(Ctxt *c, Evas_Object_Textblock_Format *fmt) { int ascent, descent; if (fmt->font.font) { // ascent = c->ENFN->font_max_ascent_get(c->ENDT, fmt->font.font); // descent = c->ENFN->font_max_descent_get(c->ENDT, fmt->font.font); ascent = c->ENFN->font_ascent_get(c->ENDT, fmt->font.font); descent = c->ENFN->font_descent_get(c->ENDT, fmt->font.font); if (fmt->linesize > 0) { if ((ascent + descent) < fmt->linesize) { ascent = ((fmt->linesize * ascent) / (ascent + descent)); descent = fmt->linesize - ascent; } } else if (fmt->linerelsize > 0.0) { descent = ((ascent + descent) * fmt->linerelsize) - (ascent * fmt->linerelsize); ascent = ascent * fmt->linerelsize; } c->maxdescent += fmt->linegap; c->maxdescent += ((ascent + descent) * fmt->linerelgap); if (c->maxascent < ascent) c->maxascent = ascent; if (c->maxdescent < descent) c->maxdescent = descent; if (fmt->linefill > 0.0) { int dh; dh = c->obj->cur.geometry.h - (c->maxascent + c->maxdescent); if (dh < 0) dh = 0; dh = fmt->linefill * dh; c->maxdescent += dh / 2; c->maxascent += dh - (dh / 2); // FIXME: set flag that says "if heigh changes - reformat" } } } static void _layout_line_new(Ctxt *c, Evas_Object_Textblock_Format *fmt) { c->ln = calloc(1, sizeof(Evas_Object_Textblock_Line)); c->align = fmt->halign; c->marginl = fmt->margin.l; c->marginr = fmt->margin.r; c->par->lines = (Evas_Object_Textblock_Line *)eina_inlist_append(EINA_INLIST_GET(c->par->lines), EINA_INLIST_GET(c->ln)); c->x = 0; c->maxascent = c->maxdescent = 0; c->ln->line_no = -1; _layout_format_ascent_descent_adjust(c, fmt); } static void _layout_paragraph_new(Ctxt *c) { c->par = calloc(1, sizeof(Evas_Object_Textblock_Paragraph)); c->paragraphs = (Evas_Object_Textblock_Paragraph *)eina_inlist_append(EINA_INLIST_GET(c->paragraphs), EINA_INLIST_GET(c->par)); c->x = 0; c->par->par_no= -1; } static void _paragraph_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par) { while (par->lines) { Evas_Object_Textblock_Line *ln; ln = (Evas_Object_Textblock_Line *) par->lines; par->lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(par->lines), EINA_INLIST_GET(par->lines)); _line_free(obj, ln); } free(par); } static void _paragraphs_clear(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars) { while (pars) { Evas_Object_Textblock_Paragraph *par; par = (Evas_Object_Textblock_Paragraph *) pars; pars = (Evas_Object_Textblock_Paragraph *)eina_inlist_remove(EINA_INLIST_GET(pars), EINA_INLIST_GET(par)); _paragraph_free(obj, par); } } static Evas_Object_Textblock_Format * _layout_format_push(Ctxt *c, Evas_Object_Textblock_Format *fmt) { if (fmt) { fmt = _format_dup(c->obj, fmt); c->format_stack = eina_list_prepend(c->format_stack, fmt); } else { fmt = calloc(1, sizeof(Evas_Object_Textblock_Format)); c->format_stack = eina_list_prepend(c->format_stack, fmt); fmt->ref = 1; fmt->halign = 0.0; fmt->valign = -1.0; fmt->style = EVAS_TEXT_STYLE_PLAIN; fmt->tabstops = 32; fmt->linesize = 0; fmt->linerelsize = 0.0; fmt->linegap = 0; fmt->linerelgap = 0.0; } return fmt; } static Evas_Object_Textblock_Format * _layout_format_pop(Ctxt *c, Evas_Object_Textblock_Format *fmt) { if ((c->format_stack) && (c->format_stack->next)) { _format_unref_free(c->obj, fmt); c->format_stack = eina_list_remove_list(c->format_stack, c->format_stack); fmt = c->format_stack->data; } return fmt; } static void _layout_format_value_handle(Ctxt *c, Evas_Object_Textblock_Format *fmt, const char *item) { const char *key = NULL, *val = NULL; char *tmp; tmp = alloca(strlen(item) + 1); strcpy(tmp, item); _format_param_parse(tmp, &key, &val); if ((key) && (val)) _format_command(c->obj, fmt, key, val); if (key) eina_stringshare_del(key); if (val) eina_stringshare_del(val); c->align = fmt->halign; c->marginl = fmt->margin.l; c->marginr = fmt->margin.r; } #define VSIZE_FULL 0 #define VSIZE_ASCENT 1 #define SIZE 0 #define SIZE_ABS 1 #define SIZE_REL 2 static void _layout_line_advance(Ctxt *c, Evas_Object_Textblock_Format *fmt) { Evas_Object_Textblock_Item *it; Evas_Object_Textblock_Format_Item *fi; c->maxascent = c->maxdescent = 0; if (!c->ln->items) _layout_format_ascent_descent_adjust(c, fmt); EINA_INLIST_FOREACH(c->ln->items, it) { int endx; if (it->format->font.font) it->baseline = c->ENFN->font_max_ascent_get(c->ENDT, it->format->font.font); _layout_format_ascent_descent_adjust(c, it->format); endx = it->x + it->w; if (endx > c->ln->w) c->ln->w = endx; } EINA_INLIST_FOREACH(c->ln->format_items, fi) { int endx; if (!fi->formatme) continue; endx = fi->x + fi->w; if (endx > c->ln->w) c->ln->w = endx; switch (fi->size) { case SIZE: case SIZE_ABS: switch (fi->vsize) { case VSIZE_FULL: if (fi->h > (c->maxdescent + c->maxascent)) { c->maxascent += fi->h - (c->maxdescent + c->maxascent); fi->y = -c->maxascent; } else fi->y = -(fi->h - c->maxdescent); break; case VSIZE_ASCENT: if (fi->h > c->maxascent) { c->maxascent = fi->h; fi->y = -fi->h; } else fi->y = -fi->h; break; default: break; } break; case SIZE_REL: switch (fi->vsize) { case VSIZE_FULL: case VSIZE_ASCENT: fi->y = -fi->ascent; break; default: break; } break; default: break; } } c->ln->y = c->y + c->o->style_pad.t; c->ln->h = c->maxascent + c->maxdescent; c->ln->baseline = c->maxascent; if (c->have_underline2) { if (c->maxdescent < 4) c->underline_extend = 4 - c->maxdescent; } else if (c->have_underline) { if (c->maxdescent < 2) c->underline_extend = 2 - c->maxdescent; } c->ln->line_no = c->line_no; c->line_no++; c->y += c->maxascent + c->maxdescent; if (c->w >= 0) { c->ln->x = c->marginl + c->o->style_pad.l + ((c->w - c->ln->w - c->o->style_pad.l - c->o->style_pad.r - c->marginl - c->marginr) * c->align); if ((c->ln->x + c->ln->w + c->marginr - c->o->style_pad.l) > c->wmax) c->wmax = c->ln->x + c->ln->w + c->marginl + c->marginr - c->o->style_pad.l; } else { c->ln->x = c->marginl + c->o->style_pad.l; if ((c->ln->x + c->ln->w + c->marginr - c->o->style_pad.l) > c->wmax) c->wmax = c->ln->x + c->ln->w + c->marginl + c->marginr - c->o->style_pad.l; } _layout_line_new(c, fmt); } static Evas_Object_Textblock_Item * _layout_item_new(Ctxt *c __UNUSED__, Evas_Object_Textblock_Format *fmt, const Eina_Unicode *str) { Evas_Object_Textblock_Item *it; it = calloc(1, sizeof(Evas_Object_Textblock_Item)); it->format = fmt; it->format->ref++; it->text = eina_unicode_strdup(str); return it; } static int _layout_text_cutoff_get(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Item *it) { if (fmt->font.font) return c->ENFN->font_last_up_to_pos(c->ENDT, fmt->font.font, it->text, &it->bidi_props, c->w - c->o->style_pad.l - c->o->style_pad.r - c->marginl - c->marginr - c->x, 0); return -1; } static void _layout_item_text_cutoff(Ctxt *c __UNUSED__, Evas_Object_Textblock_Item *it, int cut) { Eina_Unicode *ts; ts = it->text; ts[cut] = 0; it->text = eina_unicode_strdup(ts); free(ts); } static int _layout_word_start(const Eina_Unicode *str, int start) { int p, tp, chr = 0; p = start; chr = GET_NEXT(str, p); if (_is_white(chr)) { tp = p; while (_is_white(chr) && (p >= 0)) { tp = p; chr = GET_NEXT(str, p); } return tp; } p = start; tp = p; while (p > 0) { chr = GET_PREV(str, p); if (_is_white(chr)) break; tp = p; } if (p < 0) p = 0; if ((p >= 0) && (_is_white(chr))) { GET_NEXT(str, p); } return p; } static int _str_ends_with_whitespace(const Eina_Unicode *str) { int p, chr; p = eina_unicode_strlen(str) - 1; if (p < 0) return 0; chr = GET_NEXT(str, p); return _is_white(chr); } static int _layout_strip_trailing_whitespace(Ctxt *c, Evas_Object_Textblock_Format *fmt __UNUSED__, Evas_Object_Textblock_Item *it) { int p, tp, chr, adv, tw, th; p = eina_unicode_strlen(it->text) - 1; tp = p; if (p >= 0) { chr = GET_PREV(it->text, p); if (_is_white(chr)) { _layout_item_text_cutoff(c, it, tp); adv = 0; if (it->format->font.font) adv = c->ENFN->font_h_advance_get(c->ENDT, it->format->font.font, it->text, &it->bidi_props); tw = th = 0; if (it->format->font.font) c->ENFN->font_string_size_get(c->ENDT, it->format->font.font, it->text, &it->bidi_props, &tw, &th); it->w = tw; it->h = th; c->x = it->x + adv; return 1; } } return 0; } static int _layout_item_abort(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Item *it) { if (it->text) free(it->text); _format_unref_free(c->obj, it->format); #ifdef BIDI_SUPPORT evas_bidi_props_clean(&it->bidi_props); #endif free(it); if (c->ln->items) { it = (Evas_Object_Textblock_Item *)(EINA_INLIST_GET(c->ln->items))->last; return _layout_strip_trailing_whitespace(c, fmt, it); } return 0; } static int _layout_last_item_ends_with_whitespace(Ctxt *c) { Evas_Object_Textblock_Item *it; if (!c->ln->items) return 1; it = (Evas_Object_Textblock_Item *)(EINA_INLIST_GET(c->ln->items))->last; return _str_ends_with_whitespace(it->text); } static int _layout_word_end(const Eina_Unicode *str, int p) { int ch, tp; tp = p; ch = GET_NEXT(str, tp); while ((!_is_white(ch)) && (tp >= 0) && (ch != 0)) { p = tp; ch = GET_NEXT(str, tp); } if (ch == 0) return -1; return p; } static int _layout_word_next(Eina_Unicode *str, int p) { int ch, tp; tp = p; ch = GET_NEXT(str, tp); while ((!_is_white(ch)) && (tp >= 0) && (ch != 0)) { p = tp; ch = GET_NEXT(str, tp); } if (ch == 0) return -1; while ((_is_white(ch)) && (tp >= 0) && (ch != 0)) { p = tp; ch = GET_NEXT(str, tp); } if (ch == 0) return -1; return p; } static void _layout_walk_back_to_item_word_redo(Ctxt *c, Evas_Object_Textblock_Item *it) { Evas_Object_Textblock_Item *pit, *new_it = NULL; Eina_List *remove_items = NULL, *l; Eina_Inlist *data; int index, tw, th, inset, adv; /* it is not appended yet */ EINA_INLIST_REVERSE_FOREACH((EINA_INLIST_GET(c->ln->items)), pit) { if (_str_ends_with_whitespace(pit->text)) { break; } index = eina_unicode_strlen(pit->text) - 1; if (index < 0) index = 0; index = _layout_word_start(pit->text, index); if (index == 0) remove_items = eina_list_prepend(remove_items, pit); else { new_it = _layout_item_new(c, pit->format, pit->text + index); new_it->source_node = pit->source_node; new_it->source_pos = pit->source_pos + index; new_it->bidi_props.start = new_it->source_pos; new_it->bidi_props.props = &new_it->source_node->bidi_props; _layout_item_text_cutoff(c, pit, index); _layout_strip_trailing_whitespace(c, pit->format, pit); break; } } EINA_LIST_FOREACH(remove_items, l, data) c->ln->items = (Evas_Object_Textblock_Item *)eina_inlist_remove(EINA_INLIST_GET(c->ln->items), data); /* new line now */ if (remove_items) { pit = remove_items->data; _layout_line_advance(c, pit->format); } else { _layout_line_advance(c, it->format); } if (new_it) { /* append new_it */ tw = th = 0; if (new_it->format->font.font) c->ENFN->font_string_size_get(c->ENDT, new_it->format->font.font, new_it->text, &it->bidi_props, &tw, &th); new_it->w = tw; new_it->h = th; inset = 0; if (new_it->format->font.font) inset = c->ENFN->font_inset_get(c->ENDT, new_it->format->font.font, new_it->text); new_it->inset = inset; new_it->x = c->x; adv = 0; if (new_it->format->font.font) adv = c->ENFN->font_h_advance_get(c->ENDT, new_it->format->font.font, new_it->text, &new_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(new_it)); } while (remove_items) { pit = remove_items->data; remove_items = eina_list_remove_list(remove_items, remove_items); /* append pit */ pit->x = c->x; adv = c->ENFN->font_h_advance_get(c->ENDT, pit->format->font.font, pit->text, &pit->bidi_props); c->x += adv; c->ln->items = (Evas_Object_Textblock_Item *)eina_inlist_append(EINA_INLIST_GET(c->ln->items), EINA_INLIST_GET(pit)); } if (it) { /* append it */ it->x = c->x; adv = 0; if (it->format->font.font) adv = c->ENFN->font_h_advance_get(c->ENDT, it->format->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)); } } 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 wrap, twrap, ch, index, white_stripped; Eina_Unicode *alloc_str = NULL; const Eina_Unicode *str = EINA_UNICODE_EMPTY_STRING; const Eina_Unicode *ustr; const Eina_Unicode *tbase; Evas_Object_Textblock_Item *it; if (n) { if ((repch) && (eina_ustrbuf_length_get(n->unicode))) { int i, len, ind; Eina_Unicode *ptr; Eina_Unicode urepch; len = eina_unicode_strlen(eina_ustrbuf_string_get(n->unicode)); str = alloca((len + 1) * sizeof(Eina_Unicode)); tbase = str; ind = 0; urepch = evas_common_encoding_utf8_get_next(repch, &ind); for (i = 0, ptr = (Eina_Unicode *)tbase; i < len; ptr++, i++) *ptr = urepch; *ptr = 0; } else { int len; len = eina_ustrbuf_length_get(n->unicode); if (off == 0) return; else if (off < 0) off = len - start; if (start < 0) { start = 0; } else if ((start >= len) || (off > len)) { return; } str = eina_ustrbuf_string_get(n->unicode); alloc_str = eina_unicode_strdup(str + start); if (off > 0) { alloc_str[off] = 0; } tbase = str = alloc_str; } } else { 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; while (str) { /* if this is the first line item and it starts with spaces - remove them */ wrap = 0; white_stripped = 0; it = _layout_item_new(c, fmt, str); it->source_node = n; it->source_pos = start + str - tbase; it->bidi_props.start = it->source_pos; it->bidi_props.props = &it->source_node->bidi_props; 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); 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, it); if (wrap == 0) GET_NEXT(str, wrap); 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 (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, 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 { /* 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, it, twrap); str += wrap; } else { empty_item = 1; if (it->text) free(it->text); _format_unref_free(c->obj, it->format); free(it); if (c->ln->items) { it = (Evas_Object_Textblock_Item *)(EINA_INLIST_GET(c->ln->items))->last; _layout_strip_trailing_whitespace(c, fmt, it); twrap = _layout_word_end(str, wrap); if (twrap >= 0) { ch = GET_NEXT(str, twrap); str += twrap; } else str = NULL; } } } } else { /* wrap now is the index of the word START */ index = wrap; ch = GET_NEXT(str, index); if (!_is_white(ch) && (!_layout_last_item_ends_with_whitespace(c))) { _layout_walk_back_to_item_word_redo(c, it); goto end; } if (c->ln->items != NULL) { white_stripped = _layout_item_abort(c, fmt, it); empty_item = 1; } else { if (wrap <= 0) { wrap = 0; twrap = _layout_word_end(it->text, wrap); wrap = twrap; if (twrap >= 0) { ch = GET_NEXT(str, wrap); _layout_item_text_cutoff(c, it, twrap); } if (wrap > 0) str += wrap; else str = NULL; } else str = NULL; } } } else if (fmt->wrap_char) { _layout_item_text_cutoff(c, it, wrap); str += wrap; } new_line = 1; } else { /* wrap now is the index of the word START */ if (wrap <= 0) { if (wrap < 0) wrap = 0; index = wrap; ch = GET_NEXT(str, index); if (!_is_white(ch) && (!_layout_last_item_ends_with_whitespace(c))) { _layout_walk_back_to_item_word_redo(c, it); goto end; } } if (c->ln->items != NULL) { white_stripped = _layout_item_abort(c, fmt, it); empty_item = 1; new_line = 1; } else { if (wrap <= 0) { wrap = 0; twrap = _layout_word_end(it->text, wrap); wrap = _layout_word_next(it->text, wrap); if (twrap >= 0) _layout_item_text_cutoff(c, it, twrap); if (wrap >= 0) str += wrap; else str = NULL; } else str = NULL; 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; 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)); } 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); } } end: if (alloc_str) free(alloc_str); } static Evas_Object_Textblock_Format_Item * _layout_format_item_add(Ctxt *c, Evas_Object_Textblock_Node_Format *n, const char *item) { Evas_Object_Textblock_Format_Item *fi; fi = calloc(1, sizeof(Evas_Object_Textblock_Format_Item)); fi->item = eina_stringshare_add(item); 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)); return fi; } /* A macro to check if the string is a new line, either the actual char or a * relevant escape sequence */ #define _IS_LINE_SEPARATOR(item) \ (!strcmp(item, "\n") || !strcmp(item, "\\n")) /* same as the above just with paragraphs */ #define _IS_PARAGRAPH_SEPARATOR(item) \ (!strcmp(item, "ps")) /* Paragraph separator */ static void _layout_do_format(const Evas_Object *obj, Ctxt *c, Evas_Object_Textblock_Format **_fmt, Evas_Object_Textblock_Node_Format *n, int *style_pad_l, int *style_pad_r, int *style_pad_t, int *style_pad_b) { Evas_Object_Textblock_Format *fmt = *_fmt; const char *s; const char *item; int handled = 0; s = eina_strbuf_string_get(n->format); if (!strncmp(s, "+ item ", 7)) { // one of: // item size=20x10 href=name // item relsize=20x10 href=name // item abssize=20x10 href=name // // optional arguments: // vsize=full // vsize=ascent // // size == item size (modifies line size) - can be multiplied by // scale factor // relsize == relative size (height is current font height, width // modified accordingly keeping aspect) // abssize == absolute size (modifies line size) - never mulitplied by // scale factor // href == name of item - to be found and matched later and used for // positioning Evas_Object_Textblock_Format_Item *fi; int x2, w = 1, h = 1; int vsize = 0, size = 0; char *p; // don't care //href = strstr(s, " href="); p = strstr(s, " vsize="); if (p) { p += 7; if (!strncmp(p, "full", 4)) vsize = VSIZE_FULL; else if (!strncmp(p, "ascent", 6)) vsize = VSIZE_ASCENT; } p = strstr(s, " size="); if (p) { p += 6; if (sscanf(p, "%ix%i", &w, &h) == 2) { w = w * obj->cur.scale; h = h * obj->cur.scale; size = SIZE; } } else { p = strstr(s, " absize="); if (p) { p += 8; if (sscanf(p, "%ix%i", &w, &h) == 2) { size = SIZE_ABS; } } else { p = strstr(s, " relsize="); if (p) { p += 9; if (sscanf(p, "%ix%i", &w, &h) == 2) { int sz = 1; size = SIZE_REL; if (vsize == VSIZE_FULL) { sz = c->maxdescent + c->maxascent; } else if (vsize == VSIZE_ASCENT) { sz = c->maxascent; } w = (w * sz) / h; h = sz; } } } } 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); x2 = w; } fi = _layout_format_item_add(c, n, NULL); fi->x = c->x; fi->vsize = vsize; fi->size = size; fi->formatme = 1; fi->w = w; fi->h = h; fi->ascent = c->maxascent; fi->descent = c->maxdescent; c->x = x2; handled = 1; } if (!handled) { if (s[0] == '+') { fmt = _layout_format_push(c, fmt); s++; } else if (s[0] == '-') { fmt = _layout_format_pop(c, fmt); s++; } while ((item = _format_parse(&s))) { char *tmp = alloca(s - item + 1); strncpy(tmp, item, s - item); tmp[s - item] = '\0'; if (_format_is_param(item)) { _layout_format_value_handle(c, fmt, item); } else { if (_IS_PARAGRAPH_SEPARATOR(item)) { Evas_Object_Textblock_Format_Item *fi; fi = _layout_format_item_add(c, n, item); fi->x = c->x; fi->w = 0; _layout_line_advance(c, fmt); } else if (_IS_LINE_SEPARATOR(item)) { Evas_Object_Textblock_Format_Item *fi; fi = _layout_format_item_add(c, n, item); fi->x = c->x; fi->w = 0; _layout_line_advance(c, fmt); } else if ((!strcmp(item, "\t")) || (!strcmp(item, "\\t"))) { Evas_Object_Textblock_Format_Item *fi; int x2; x2 = (fmt->tabstops * ((c->x + fmt->tabstops) / fmt->tabstops)); if (x2 > (c->w - c->o->style_pad.l - c->o->style_pad.r - c->marginl - c->marginr)) { _layout_line_advance(c, fmt); x2 = (fmt->tabstops * ((c->x + fmt->tabstops) / 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->x = c->x; fi->w = x2 - c->x; c->x = x2; } } } } evas_text_style_pad_get(fmt->style, style_pad_l, style_pad_r, style_pad_t, style_pad_b); if (fmt->underline2) c->have_underline2 = 1; else if (fmt->underline) c->have_underline = 1; *_fmt = fmt; } static void _layout(const Evas_Object *obj, int calc_only, int w, int h, int *w_ret, int *h_ret) { Evas_Object_Textblock *o; Ctxt ctxt, *c; Evas_Object_Textblock_Line *ln; Evas_Object_Textblock_Node_Text *n; Eina_List *removes = NULL; Evas_Object_Textblock_Format *fmt = NULL; int style_pad_l = 0, style_pad_r = 0, style_pad_t = 0, style_pad_b = 0; /* setup context */ o = (Evas_Object_Textblock *)(obj->object_data); c = &ctxt; c->obj = (Evas_Object *)obj; c->o = o; c->paragraphs = c->par = NULL; c->format_stack = NULL; c->x = c->y = 0; c->w = w; c->h = h; c->wmax = c->hmax = 0; c->maxascent = c->maxdescent = 0; c->marginl = c->marginr = 0; c->have_underline = 0; c->have_underline2 = 0; c->underline_extend = 0; c->line_no = 0; c->align = 0.0; c->ln = NULL; /* setup default base style */ if ((c->o->style) && (c->o->style->default_tag)) { fmt = _layout_format_push(c, NULL); _format_fill(c->obj, fmt, c->o->style->default_tag); } if (!fmt) { if (w_ret) *w_ret = 0; if (h_ret) *h_ret = 0; return; } /* run through all text and format nodes generating lines */ if (!c->o->text_nodes && !c->o->format_nodes) { /* If there are no nodes and lines, do the inital creation. */ if (!c->par && !c->ln) { _layout_paragraph_new(c); _layout_line_new(c, fmt); _layout_text_append(c, fmt, NULL, 0, 0, NULL); _layout_line_advance(c, fmt); } } EINA_INLIST_FOREACH(c->o->text_nodes, n) { Evas_Object_Textblock_Node_Format *fnode; size_t start; int off; int first_run; /*FIXME-tom: A hack, so we'll only have one paragraph * until full support is implemented */ if (!c->par) { _layout_paragraph_new(c); /* Each node is a paragraph */ } if (!c->ln) _layout_line_new(c, fmt); fnode = n->format_node; start = off = 0; first_run = 1; while (fnode && fnode->text_node == 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) { off = -1; start++; } else { off = 0; } first_run = 0; fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); } _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) && (fmt)) _layout_line_advance(c, fmt); /* Clean the rest of the format stack */ while (c->format_stack) { fmt = c->format_stack->data; c->format_stack = eina_list_remove_list(c->format_stack, c->format_stack); _format_unref_free(c->obj, fmt); } EINA_INLIST_FOREACH(c->par->lines, ln) { if (ln->line_no == -1) { removes = eina_list_append(removes, ln); } else { if ((ln->y + ln->h) > c->hmax) c->hmax = ln->y + ln->h; } } while (removes) { ln = removes->data; c->par->lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(c->par->lines), EINA_INLIST_GET(ln)); removes = eina_list_remove_list(removes, removes); _line_free(obj, ln); } if (w_ret) *w_ret = c->wmax; if (h_ret) *h_ret = c->hmax; 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)) { Evas_Object_Textblock_Line *lines; lines = c->par->lines; 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); _lines_clear(obj, lines); return; } if (!calc_only) { o->lines = c->par->lines; return; } if (c->paragraphs) _paragraphs_clear(obj, c->paragraphs); } static void _relayout(const Evas_Object *obj) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *lines; o = (Evas_Object_Textblock *)(obj->object_data); lines = o->lines; o->lines = NULL; o->formatted.valid = 0; o->native.valid = 0; _layout(obj, 0, obj->cur.geometry.w, obj->cur.geometry.h, &o->formatted.w, &o->formatted.h); o->formatted.valid = 1; if (lines) _lines_clear(obj, lines); o->last_w = obj->cur.geometry.w; o->changed = 0; o->redraw = 1; } static void _find_layout_item_line_match(Evas_Object *obj, Evas_Object_Textblock_Node_Text *n, int pos, Evas_Object_Textblock_Line **lnr, Evas_Object_Textblock_Item **itr) { Evas_Object_Textblock_Line *ln; Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); if (!o->formatted.valid) _relayout(obj); EINA_INLIST_FOREACH(o->lines, ln) { Evas_Object_Textblock_Format_Item *fit; Evas_Object_Textblock_Item *it; Evas_Object_Textblock_Line *lnn; lnn = (Evas_Object_Textblock_Line *)(((Eina_Inlist *)ln)->next); EINA_INLIST_FOREACH(ln->items, it) { if (it->source_node == n) { Evas_Object_Textblock_Item *itn; int p; itn = (Evas_Object_Textblock_Item *)(((Eina_Inlist *)it)->next); p = (int)(it->source_pos + eina_unicode_strlen(it->text)); /*FIXME: Bad, this sholud be > pos */ if ((p >= pos) || ((p == pos) && (!lnn) && ((!itn) | ((itn) && (itn->source_node != n))))) { *lnr = ln; *itr = it; return; } } } } } static void _find_layout_format_item_line_match(Evas_Object *obj, Evas_Object_Textblock_Node_Format *n, Evas_Object_Textblock_Line **lnr, Evas_Object_Textblock_Format_Item **fir) { Evas_Object_Textblock_Line *ln; Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); if (!o->formatted.valid) _relayout(obj); EINA_INLIST_FOREACH(o->lines, ln) { Evas_Object_Textblock_Format_Item *fi; EINA_INLIST_FOREACH(ln->format_items, fi) { if (fi->source_node == n) { *lnr = ln; *fir = fi; return; } } } } static Evas_Object_Textblock_Line * _find_layout_line_num(const Evas_Object *obj, int line) { Evas_Object_Textblock_Line *ln; Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); EINA_INLIST_FOREACH(o->lines, ln) { if (ln->line_no == line) return ln; } return NULL; } /** * Adds a textblock to the given evas. * @param e The given evas. * @return The new textblock object. */ EAPI Evas_Object * evas_object_textblock_add(Evas *e) { Evas_Object *obj; MAGIC_CHECK(e, Evas, MAGIC_EVAS); return NULL; MAGIC_CHECK_END(); obj = evas_object_new(e); evas_object_textblock_init(obj); evas_object_inject(obj, e); return obj; } /** * Creates a new textblock style. * @return The new textblock style. */ EAPI Evas_Textblock_Style * evas_textblock_style_new(void) { Evas_Textblock_Style *ts; ts = calloc(1, sizeof(Evas_Textblock_Style)); return ts; } /** * Destroys a textblock style. * @param ts The textblock style to free. */ EAPI void evas_textblock_style_free(Evas_Textblock_Style *ts) { if (!ts) return; if (ts->objects) { ts->delete_me = 1; return; } _style_clear(ts); free(ts); } /** * to be documented. * @param ts to be documented. * @param text to be documented. * @return Returns no value. */ EAPI void evas_textblock_style_set(Evas_Textblock_Style *ts, const char *text) { Eina_List *l; Evas_Object *obj; if (!ts) return; EINA_LIST_FOREACH(ts->objects, l, obj) { Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); if (o->markup_text) { free(o->markup_text); o->markup_text = NULL; evas_object_textblock_text_markup_get(obj); } } _style_clear(ts); if (text) ts->style_text = strdup(text); if (ts->style_text) { // format MUST be KEY='VALUE'[KEY='VALUE']... char *p; char *key_start, *key_stop, *val_start, *val_stop; key_start = key_stop = val_start = val_stop = NULL; p = ts->style_text; while (*p) { if (!key_start) { if (!isspace(*p)) key_start = p; } else if (!key_stop) { if ((*p == '=') || (isspace(*p))) key_stop = p; } else if (!val_start) { if (((*p) == '\'') && (*(p + 1))) val_start = p + 1; } else if (!val_stop) { if (((*p) == '\'') && (p > ts->style_text) && (p[-1] != '\\')) val_stop = p; } if ((key_start) && (key_stop) && (val_start) && (val_stop)) { char *tags, *replaces; Evas_Object_Style_Tag *tag; size_t tag_len = key_stop - key_start; size_t replace_len = val_stop - val_start; tags = malloc(tag_len + 1); if (tags) { memcpy(tags, key_start, tag_len); tags[tag_len] = 0; } replaces = malloc(replace_len + 1); if (replaces) { memcpy(replaces, val_start, replace_len); replaces[replace_len] = 0; } if ((tags) && (replaces)) { if (!strcmp(tags, "DEFAULT")) { ts->default_tag = replaces; free(tags); } else { tag = calloc(1, sizeof(Evas_Object_Style_Tag)); if (tag) { tag->tag = tags; tag->replace = replaces; tag->tag_len = tag_len; tag->replace_len = replace_len; ts->tags = (Evas_Object_Style_Tag *)eina_inlist_append(EINA_INLIST_GET(ts->tags), EINA_INLIST_GET(tag)); } else { free(tags); free(replaces); } } } else { if (tags) free(tags); if (replaces) free(replaces); } key_start = key_stop = val_start = val_stop = NULL; } p++; } } EINA_LIST_FOREACH(ts->objects, l, obj) { Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); if (o->markup_text) { char *m; m = strdup(o->markup_text); if (m) { evas_object_textblock_text_markup_set(obj, m); free(m); } } } } /** * to be documented. * @param ts to be documented. * @return to be documented. */ EAPI const char * evas_textblock_style_get(const Evas_Textblock_Style *ts) { if (!ts) return NULL; return ts->style_text; } /* textblock styles */ /** * to be documented. * @param obj to be documented. * @param ts to be documented. * @return Returns no value. */ EAPI void evas_object_textblock_style_set(Evas_Object *obj, Evas_Textblock_Style *ts) { TB_HEAD(); if (ts == o->style) return; if ((ts) && (ts->delete_me)) return; if (o->markup_text) { if (o->style) { free(o->markup_text); o->markup_text = NULL; evas_object_textblock_text_markup_get(obj); } } if (o->style) { Evas_Textblock_Style *old_ts; old_ts = o->style; old_ts->objects = eina_list_remove(old_ts->objects, obj); if ((old_ts->delete_me) && (!old_ts->objects)) evas_textblock_style_free(old_ts); } if (ts) { ts->objects = eina_list_append(ts->objects, obj); o->style = ts; } else { o->style = NULL; } _evas_textblock_changed(o, obj); } /** * to be documented. * @param obj to be documented. * @return to be documented. */ EAPI const Evas_Textblock_Style * evas_object_textblock_style_get(const Evas_Object *obj) { TB_HEAD_RETURN(NULL); return o->style; } /** * @brief Set the "replacement character" to use for the given textblock object. * * @param obj The given textblock object. * @param ch The charset name. */ EAPI void evas_object_textblock_replace_char_set(Evas_Object *obj, const char *ch) { TB_HEAD(); if (o->repch) eina_stringshare_del(o->repch); if (ch) o->repch = eina_stringshare_add(ch); else o->repch = NULL; _evas_textblock_changed(o, obj); } /** * @brief Get the "replacement character" for given textblock object. Returns * NULL if no replacement character is in use. * * @param obj The given textblock object * @return replacement character or NULL */ EAPI const char * evas_object_textblock_replace_char_get(Evas_Object *obj) { TB_HEAD_RETURN(NULL); return o->repch; } /* Advance the pointer *p_buf to point after the next null - used in the escape table */ static inline void _escaped_advance_after_end_of_string(const char **p_buf) { while (**p_buf != 0) (*p_buf)++; (*p_buf)++; } /* Advance the pointer *p_buf to point after the next null - returns true if * there is a match */ static inline int _escaped_is_eq_and_advance(const char *s, const char *s_end, const char **p_m, const char *m_end) { for (;((s < s_end) && (*p_m < m_end)); s++, (*p_m)++) { if (*s != **p_m) { _escaped_advance_after_end_of_string(p_m); return 0; } } if (*p_m < m_end) _escaped_advance_after_end_of_string(p_m); return s == s_end; } /* Returns a pointer to the matched espcae char */ static inline const char * _escaped_char_match(const char *s, int *adv) { const char *map_itr, *map_end, *mc, *sc; map_itr = escape_strings; map_end = map_itr + sizeof(escape_strings); while (map_itr < map_end) { const char *escape; int match; escape = map_itr; _escaped_advance_after_end_of_string(&map_itr); if (map_itr >= map_end) break; mc = map_itr; sc = s; match = 1; while ((*mc) && (*sc)) { if ((unsigned char)*sc < (unsigned char)*mc) return NULL; if (*sc != *mc) match = 0; mc++; sc++; } if (match) { *adv = mc - map_itr; return escape; } _escaped_advance_after_end_of_string(&map_itr); } return NULL; } static inline const char * _escaped_char_get(const char *s, const char *s_end) { const char *map_itr, *map_end; map_itr = escape_strings; map_end = map_itr + sizeof(escape_strings); while (map_itr < map_end) { if (_escaped_is_eq_and_advance(s, s_end, &map_itr, map_end)) return map_itr; if (map_itr < map_end) _escaped_advance_after_end_of_string(&map_itr); } return NULL; } /** * to be documented. * @param escape to be documented. * @return to be documented. */ EAPI const char * evas_textblock_escape_string_get(const char *escape) { /* & -> & */ return _escaped_char_get(escape, escape + strlen(escape)); } /** * to be documented. * @param escape_start to be documented. * @param escape_end to be documented. * @return to be documented. */ EAPI const char * evas_textblock_escape_string_range_get(const char *escape_start, const char *escape_end) { return _escaped_char_get(escape_start, escape_end); } /** * to be documented. * @param string to be documented. * @param len_ret to be documented. * @return to be documented. */ EAPI const char * evas_textblock_string_escape_get(const char *string, int *len_ret) { /* & -> & */ return _escaped_char_match(string, len_ret); } /* Appends the escaped char beteewn s and s_end to the curosr */ static inline void _append_escaped_char(Evas_Textblock_Cursor *cur, const char *s, const char *s_end) { const char *escape; escape = _escaped_char_get(s, s_end); if (escape) evas_textblock_cursor_text_append(cur, escape); } static inline void _prepend_escaped_char(Evas_Textblock_Cursor *cur, const char *s, const char *s_end) { const char *escape; escape = _escaped_char_get(s, s_end); if (escape) evas_textblock_cursor_text_prepend(cur, escape); } /** * to be documented. * @param obj to be documented. * @param text to be documented. * @return Return no value. */ EAPI void evas_object_textblock_text_markup_set(Evas_Object *obj, const char *text) { TB_HEAD(); if ((text != o->markup_text) && (o->markup_text)) { free(o->markup_text); o->markup_text = NULL; } _nodes_clear(obj); if (!o->style) { if (text != o->markup_text) { if (text) o->markup_text = strdup(text); } return; } evas_textblock_cursor_paragraph_first(o->cursor); evas_object_textblock_text_markup_prepend(o->cursor, text); { Eina_List *l; Evas_Textblock_Cursor *data; evas_textblock_cursor_paragraph_first(o->cursor); EINA_LIST_FOREACH(o->cursors, l, data) evas_textblock_cursor_paragraph_first(data); } } /** * to be documented. * @param cur to be documented. * @param text to be documented. * @return Return no value. */ EAPI void evas_object_textblock_text_markup_prepend(Evas_Textblock_Cursor *cur, const char *text) { Evas_Object *obj = cur->obj; TB_HEAD(); if (text) { char *s, *p; char *tag_start, *tag_end, *esc_start, *esc_end; tag_start = tag_end = esc_start = esc_end = NULL; p = (char *)text; s = p; /* This loop goes through all of the mark up text until it finds format * tags, escape sequences or the terminating NULL. When it finds either * of those, it appends the text found up until that point to the textblock * proccesses whatever found. It repeats itself until the termainating * NULL is reached. */ for (;;) { /* If we got to the end of string or just finished/started tag * or escape sequence handling. */ if ((*p == 0) || (tag_end) || (esc_end) || (tag_start) || (esc_start)) { if (tag_end) { /* If we reached to a tag ending, analyze the tag */ /* FIXME: Move tag analyzing to a different function */ char *ttag; size_t ttag_len = tag_end - tag_start -1; ttag = malloc(ttag_len + 1); if (ttag) { const char *match; size_t replace_len; memcpy(ttag, tag_start + 1, ttag_len); ttag[ttag_len] = 0; match = _style_match_tag(o->style, ttag, ttag_len, &replace_len); if (match) { evas_textblock_cursor_format_prepend(o->cursor, match); } else { char *ttag2; ttag2 = malloc(ttag_len + 2 + 1); if (ttag2) { if (ttag[0] == '/') { strcpy(ttag2, "- "); strcat(ttag2, ttag + 1); } else { strcpy(ttag2, "+ "); strcat(ttag2, ttag); } evas_textblock_cursor_format_prepend(o->cursor, ttag2); free(ttag2); } } free(ttag); } tag_start = tag_end = NULL; } else if (esc_end) { _prepend_escaped_char(o->cursor, esc_start, esc_end); esc_start = esc_end = NULL; } else if (*p == 0) { _prepend_text_run(o, s, p); s = NULL; } if (*p == 0) break; } if (*p == '<') { if (!esc_start) { /* Append the text prior to this to the textblock and mark * the start of the tag */ tag_start = p; tag_end = NULL; _prepend_text_run(o, s, p); s = NULL; } } else if (*p == '>') { if (tag_start) { tag_end = p; s = p + 1; } } else if (*p == '&') { if (!tag_start) { /* Append the text prior to this to the textblock and mark * the start of the escape sequence */ esc_start = p; esc_end = NULL; _prepend_text_run(o, s, p); s = NULL; } } else if (*p == ';') { if (esc_start) { esc_end = p; s = p + 1; } } p++; } } _evas_textblock_changed(o, obj); } static void _markup_get_format_append(Evas_Object_Textblock *o, Eina_Strbuf *txt, Evas_Object_Textblock_Node_Format *fnode) { size_t replace_len; size_t tag_len; const char *tag; const char *replace; replace_len = eina_strbuf_length_get(fnode->format); replace = eina_strbuf_string_get(fnode->format); tag = _style_match_replace(o->style, replace, replace_len, &tag_len); eina_strbuf_append_char(txt, '<'); if (tag) { eina_strbuf_append_length(txt, tag, tag_len); } else { const char *s; int push = 0; int pop = 0; // FIXME: need to escape s = eina_strbuf_string_get(fnode->format); if (*s == '+') push = 1; if (*s == '-') pop = 1; while ((*s == ' ') || (*s == '+') || (*s == '-')) s++; if (pop) eina_strbuf_append_char(txt, '/'); eina_strbuf_append(txt, s); } eina_strbuf_append_char(txt, '>'); } static void _markup_get_text_append(Eina_Strbuf *txt, const Eina_Unicode *text) { char *p = evas_common_encoding_unicode_to_utf8(text, NULL); char *base = p; while (*p) { const char *escape; int adv; escape = _escaped_char_match(p, &adv); if (escape) { p += adv; eina_strbuf_append(txt, escape); } else { eina_strbuf_append_char(txt, *p); p++; } } free(base); } /** * to be documented. * @param obj to be documented. * @return to be documented. */ EAPI const char * evas_object_textblock_text_markup_get(const Evas_Object *obj) { Evas_Object_Textblock_Node_Text *n; Eina_Strbuf *txt = NULL; TB_HEAD_RETURN(NULL); if (o->markup_text) return(o->markup_text); txt = eina_strbuf_new(); EINA_INLIST_FOREACH(o->text_nodes, n) { Evas_Object_Textblock_Node_Format *fnode; Eina_Unicode *text_base, *text; int off; int first_run; text_base = text = eina_unicode_strdup(eina_ustrbuf_string_get(n->unicode)); fnode = n->format_node; off = 0; first_run = 1; while (fnode && (fnode->text_node == n)) { Eina_Unicode tmp_ch; off += fnode->offset; /* No need to skip on the first run */ tmp_ch = text[off]; text[off] = 0; /* Null terminate the part of the string */ _markup_get_text_append(txt, text); _markup_get_format_append(o, txt, fnode); text[off] = tmp_ch; /* Restore the char */ text += off; if (fnode->visible) { off = -1; text++; } else { off = 0; } first_run = 0; fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); } /* Add the rest, skip replacement */ _markup_get_text_append(txt, text); free(text_base); } o->markup_text = eina_strbuf_string_steal(txt); eina_strbuf_free(txt); return o->markup_text; } /* cursors */ static void _evas_textblock_node_update_format(Evas_Object_Textblock_Node_Text *n, Evas_Object_Textblock_Node_Format *fmt) { Evas_Object_Textblock_Node_Text *itr; itr = n; while (itr && (itr->format_node->text_node != itr)) { itr->format_node = fmt; } } /* Merge the current node with the next, no need to remove, already * not there. */ static void _evas_textblock_nodes_merge(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *to, Evas_Object_Textblock_Node_Text *from) { Evas_Object_Textblock_Node_Format *itr; Evas_Object_Textblock_Node_Format *pnode; const Eina_Unicode *text; int to_len, len; if (!to || !from) return; to_len = eina_ustrbuf_length_get(to->unicode); text = eina_ustrbuf_string_get(from->unicode); len = eina_ustrbuf_length_get(from->unicode); eina_ustrbuf_append_length(to->unicode, text, len); itr = from->format_node; if (itr && (itr->text_node == from)) { pnode = _NODE_FORMAT(EINA_INLIST_GET(itr)->prev); if (pnode && (pnode->text_node == to)) { itr->offset += to_len - _evas_textblock_node_format_pos_get(pnode); itr->offset -= (pnode->visible) ? 1 : 0; } else { itr->offset += to_len; } } while (itr && (itr->text_node == from)) { itr->text_node = to; itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); } if (!to->format_node || (to->format_node->text_node != to)) { to->format_node = from->format_node; } o->text_nodes = _NODE_TEXT(eina_inlist_remove( EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(from))); _evas_textblock_node_text_free(from); } static void _evas_textblock_cursor_nodes_merge(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock_Node_Text *nnode; Evas_Object_Textblock *o; if (!cur) return; o = (Evas_Object_Textblock *)(cur->obj->object_data); nnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->next); _evas_textblock_nodes_merge(o, cur->node, nnode); { Eina_List *l; Evas_Textblock_Cursor *data; int len; len = eina_ustrbuf_length_get(cur->node->unicode); if (nnode == o->cursor->node) { o->cursor->node = cur->node; o->cursor->pos += len; } EINA_LIST_FOREACH(o->cursors, l, data) { if (nnode == data->node) { data->node = cur->node; data->pos += len; } } } } /** * Return the format at a specific position. * to be documented. * @param cur to be documented. * @return to be documented. */ static Evas_Object_Textblock_Node_Format * _evas_textblock_cursor_node_format_at_pos_get(const Evas_Textblock_Cursor *cur) { Evas_Object_Textblock_Node_Format *node; Evas_Object_Textblock_Node_Format *itr; size_t position = 0; if (!cur->node) return NULL; node = cur->node->format_node; if (!node) return NULL; /* If there is no exclusive format node to this paragraph return the * previous's node */ /* Find the main format node */ EINA_INLIST_FOREACH(node, itr) { if (itr->text_node != cur->node) { return NULL; } if ((position + itr->offset) == cur->pos) { return itr; } position += itr->offset; } return NULL; } static Evas_Object_Textblock_Node_Format * _evas_textblock_node_format_last_at_off(const Evas_Object_Textblock_Node_Format *n) { const Evas_Object_Textblock_Node_Format *nnode; if (!n) return NULL; nnode = n; do { n = nnode; nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); } while (nnode && (nnode->offset == 0)); return (Evas_Object_Textblock_Node_Format *) n; } static Evas_Object_Textblock_Node_Format * _evas_textblock_node_visible_at_pos_get(const Evas_Object_Textblock_Node_Format *n) { const Evas_Object_Textblock_Node_Format *nnode; if (!n) return NULL; nnode = n; do { n = nnode; if (n->visible) return (Evas_Object_Textblock_Node_Format *) n; nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); } while (nnode && (nnode->offset == 0)); return NULL; } /** * Return the last format that applies to a specific cursor. * to be documented. * @param cur to be documented. * @return to be documented. */ static Evas_Object_Textblock_Node_Format * _evas_textblock_cursor_node_format_before_or_at_pos_get(const Evas_Textblock_Cursor *cur) { Evas_Object_Textblock_Node_Format *node, *pitr = NULL; Evas_Object_Textblock_Node_Format *itr; size_t position = 0; if (!cur->node) return NULL; node = cur->node->format_node; if (!node) return NULL; /* If there is no exclusive format node to this paragraph return the * previous's node */ if (node->text_node != cur->node) { return node; } else if (node->offset > cur->pos) { return _NODE_FORMAT(EINA_INLIST_GET(node)->prev); } /* Find the main format node */ pitr = _NODE_FORMAT(EINA_INLIST_GET(node)->prev); EINA_INLIST_FOREACH(node, itr) { if ((itr->text_node != cur->node) || ((position + itr->offset) > cur->pos)) { return pitr; } else if ((position + itr->offset) == cur->pos) { return itr; } pitr = itr; position += itr->offset; } return pitr; } /** * Returns the last format of a text node that applies to the cursor * to be documented. * @param cur to be documented. * @return to be documented. */ static Evas_Object_Textblock_Node_Format * _evas_textblock_cursor_node_format_before_pos_get(const Evas_Textblock_Cursor *cur) { Evas_Object_Textblock_Node_Format *node, *pitr = NULL; Evas_Object_Textblock_Node_Format *itr; size_t position = 0; if (!cur->node) return NULL; node = cur->node->format_node; if (!node) return NULL; /* If there is no exclusive format node to this paragraph return the * previous's node */ if (node->text_node != cur->node) { return node; } else if (node->offset > cur->pos) { return _NODE_FORMAT(EINA_INLIST_GET(node)->prev); } /* Find the main format node */ pitr = _NODE_FORMAT(EINA_INLIST_GET(node)->prev); EINA_INLIST_FOREACH(node, itr) { position += itr->offset; if ((itr->text_node != cur->node) || (position >= cur->pos)) { return pitr; } pitr = itr; } return pitr; } /** * to be documented. * @param obj to be documented. * @return to be documented. */ EAPI const Evas_Textblock_Cursor * evas_object_textblock_cursor_get(const Evas_Object *obj) { TB_HEAD_RETURN(NULL); return o->cursor; } /** * to be documented. * @param obj to be documented. * @return to be documented. */ EAPI Evas_Textblock_Cursor * evas_object_textblock_cursor_new(Evas_Object *obj) { Evas_Textblock_Cursor *cur; TB_HEAD_RETURN(NULL); cur = calloc(1, sizeof(Evas_Textblock_Cursor)); cur->obj = obj; cur->node = o->text_nodes; cur->pos = 0; o->cursors = eina_list_append(o->cursors, cur); return cur; } /** * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_free(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock *o; if (!cur) return; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (cur == o->cursor) return; o->cursors = eina_list_remove(o->cursors, cur); free(cur); } /** * Returns true if the cursor points to a format. * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI Eina_Bool evas_textblock_cursor_is_format(const Evas_Textblock_Cursor *cur) { if (!cur || !cur->node) return; if (evas_textblock_cursor_format_is_visible_get(cur)) return EINA_TRUE; return (_evas_textblock_cursor_node_format_at_pos_get(cur)) ? EINA_TRUE : EINA_FALSE; } /** * Returns the first format node. * * @param o The textblock, must not be NULL. * @return Returns the first format node, may be null if there are none. */ EAPI const Evas_Object_Textblock_Node_Format * evas_textblock_node_format_first_get(const Evas_Object *obj) { TB_HEAD_RETURN(NULL); return o->format_nodes; } /** * Returns the last format node. * * @param o The textblock, must not be NULL. * @return Returns the first format node, may be null if there are none. */ EAPI const Evas_Object_Textblock_Node_Format * evas_textblock_node_format_last_get(const Evas_Object *obj) { TB_HEAD_RETURN(NULL); if (o->format_nodes) { return _NODE_FORMAT(EINA_INLIST_GET(o->format_nodes)->last); } return NULL; } /** * Returns the last format node. * * @param o The textblock, must not be NULL. * @return Returns the first format node, may be null if there are none. */ EAPI const Evas_Object_Textblock_Node_Format * evas_textblock_node_format_next_get(const Evas_Object_Textblock_Node_Format *n) { return _NODE_FORMAT(EINA_INLIST_GET(n)->next); } /** * Returns the last format node. * * @param o The textblock, must not be NULL. * @return Returns the first format node, may be null if there are none. */ EAPI const Evas_Object_Textblock_Node_Format * evas_textblock_node_format_prev_get(const Evas_Object_Textblock_Node_Format *n) { return _NODE_FORMAT(EINA_INLIST_GET(n)->prev); } /** * Sets the cursor to the start of the first text node/visible format. * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_paragraph_first(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock *o; if (!cur) return; o = (Evas_Object_Textblock *)(cur->obj->object_data); cur->node = o->text_nodes; cur->pos = 0; } /** * sets the cursor to the end of the last text node/visible format. * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_paragraph_last(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock *o; Evas_Object_Textblock_Node_Text *node; if (!cur) return; o = (Evas_Object_Textblock *)(cur->obj->object_data); node = o->text_nodes; if (node) { node = _NODE_TEXT(EINA_INLIST_GET(node)->last); cur->node = node; cur->pos = 0; evas_textblock_cursor_paragraph_char_last(cur); } else { cur->node = NULL; cur->pos = 0; } } /** * Advances to the next text node * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_paragraph_next(Evas_Textblock_Cursor *cur) { if (!cur) return EINA_FALSE; if (!cur->node) return EINA_FALSE; /* If there is a current text node, return the next text node (if exists) * otherwise, just return False. */ if (cur->node) { Evas_Object_Textblock_Node_Text *nnode; nnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->next); if (nnode) { cur->node = nnode; cur->pos = 0; return EINA_TRUE; } } return EINA_FALSE; } /** * Advances to the previous text node. * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_paragraph_prev(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock_Node_Text *node; if (!cur) return EINA_FALSE; if (!cur->node) return EINA_FALSE; /* If the current node is a text node, just get the prev if any, * if it's a format, get the current text node out of the format and return * the prev text node if any. */ node = cur->node; /* If there is a current text node, return the prev text node * (if exists) otherwise, just return False. */ if (node) { Evas_Object_Textblock_Node_Text *pnode; pnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->prev); if (pnode) { cur->node = pnode; evas_textblock_cursor_paragraph_char_last(cur); return EINA_TRUE; } } return EINA_FALSE; } EAPI void evas_textblock_cursor_set_at_format(Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Format *n) { if (!cur || !n) return; cur->node = n->text_node; cur->pos = _evas_textblock_node_format_pos_get(n); } /** * Advances to the next format node * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_format_next(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock_Node_Format *node; if (!cur) return EINA_FALSE; if (!cur->node) return EINA_FALSE; /* If the current node is a format node, just get the next if any, * if it's a text, get the current format node out of the text and return * the next format node if any. */ node = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); node = _evas_textblock_node_format_last_at_off(node); if (!node) { if (cur->node->format_node) { cur->node = cur->node; cur->pos = _evas_textblock_node_format_pos_get(node); return EINA_TRUE; } } /* If there is a current text node, return the next format node (if exists) * otherwise, just return False. */ else { Evas_Object_Textblock_Node_Format *nnode; nnode = _NODE_FORMAT(EINA_INLIST_GET(node)->next); if (nnode) { cur->node = nnode->text_node; cur->pos = _evas_textblock_node_format_pos_get(nnode); return EINA_TRUE; } } return EINA_FALSE; } /** * Advances to the previous format node. * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_format_prev(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock_Node_Format *node; if (!cur) return EINA_FALSE; if (!cur->node) return EINA_FALSE; /* If the current node is a format node, just get the next if any, * if it's a text, get the current format node out of the text and return * the next format node if any. */ node = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); if (evas_textblock_cursor_is_format(cur)) { if (node) { cur->pos = _evas_textblock_node_format_pos_get(node); return EINA_TRUE; } } /* If there is a current text node, return the next text node (if exists) * otherwise, just return False. */ if (node) { Evas_Object_Textblock_Node_Format *pnode; pnode = _NODE_FORMAT(EINA_INLIST_GET(node)->prev); if (pnode) { cur->node = pnode->text_node; cur->pos = _evas_textblock_node_format_pos_get(node); return EINA_TRUE; } } return EINA_FALSE; } /** * Advances 1 char * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_char_next(Evas_Textblock_Cursor *cur) { int index; const Eina_Unicode *text; if (!cur) return EINA_FALSE; if (!cur->node) return EINA_FALSE; index = cur->pos; text = eina_ustrbuf_string_get(cur->node->unicode); GET_NEXT(text, index); /* Only allow pointing a null if it's the last paragraph. * because we don't have a PS there. */ if (text[index]) { cur->pos = index; return EINA_TRUE; } else { if (!evas_textblock_cursor_paragraph_next(cur)) { cur->pos = index; return EINA_TRUE; } else { return EINA_FALSE; } } } /** * Goes back one char (only works on text nodes). * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_char_prev(Evas_Textblock_Cursor *cur) { if (!cur) return EINA_FALSE; if (!cur->node) return EINA_FALSE; if (cur->pos != 0) { cur->pos--; return EINA_TRUE; } return evas_textblock_cursor_paragraph_prev(cur); } /** * Go to the first char in the node the cursor is pointing on. * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_paragraph_char_first(Evas_Textblock_Cursor *cur) { if (!cur) return; cur->pos = 0; } /** * Go to the last char in a text node. * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_paragraph_char_last(Evas_Textblock_Cursor *cur) { int index; if (!cur) return; if (!cur->node) return; index = eina_unicode_strlen(eina_ustrbuf_string_get(cur->node->unicode)) - 1; if (index < 0) cur->pos = 0; cur->pos = index; } /** * Go to the start of the current line * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_line_char_first(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln = NULL; Evas_Object_Textblock_Item *it = NULL; Evas_Object_Textblock_Format_Item *fi = NULL; if (!cur) return; if (!cur->node) return; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (!o->formatted.valid) _relayout(cur->obj); if (evas_textblock_cursor_format_is_visible_get(cur)) { _find_layout_format_item_line_match(cur->obj, _evas_textblock_node_visible_at_pos_get( _evas_textblock_cursor_node_format_before_or_at_pos_get(cur)), &ln, &fi); } else { _find_layout_item_line_match(cur->obj, cur->node, cur->pos, &ln, &it); } if (!ln) return; it = (Evas_Object_Textblock_Item *)ln->items; fi = (Evas_Object_Textblock_Format_Item *)ln->format_items; if ((it) && (fi)) { if (it->x < fi->x) fi = NULL; else it = NULL; } if (it) { cur->pos = it->source_pos; cur->node = it->source_node; } else if (fi) { cur->node = fi->source_node->text_node; cur->pos = _evas_textblock_node_format_pos_get(fi->source_node); } } /** * Go to the end of the current line. * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_line_char_last(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln = NULL; Evas_Object_Textblock_Item *it = NULL; Evas_Object_Textblock_Format_Item *fi = NULL; if (!cur) return; if (!cur->node) return; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (!o->formatted.valid) _relayout(cur->obj); // kills "click below text" and up/downm arrow. disable if (evas_textblock_cursor_format_is_visible_get(cur)) { _find_layout_format_item_line_match(cur->obj, _evas_textblock_node_visible_at_pos_get( _evas_textblock_cursor_node_format_before_or_at_pos_get(cur)), &ln, &fi); } else { _find_layout_item_line_match(cur->obj, cur->node, cur->pos, &ln, &it); } if (!ln) return; if (ln->items) it = (Evas_Object_Textblock_Item *)((EINA_INLIST_GET(ln->items))->last); else it = NULL; if (ln->format_items) fi = (Evas_Object_Textblock_Format_Item *)((EINA_INLIST_GET(ln->format_items))->last); else fi = NULL; if ((it) && (fi)) { if ((it->x + it->w) > (fi->x + fi->w)) fi = NULL; else it = NULL; } if (it) { int index; cur->pos = it->source_pos; cur->node = it->source_node; index = eina_unicode_strlen(it->text) - 1; if (index < 0) index = 0; if ((index >= 0) && (it->text[0] != 0)) GET_NEXT(it->text, index); if (index >= 0) cur->pos += index; } else if (fi) { cur->node = fi->source_node->text_node; cur->pos = _evas_textblock_node_format_pos_get(fi->source_node); } } static Eina_Bool _evas_textblock_format_is_visible(const char *s) { if (!s) return EINA_FALSE; const char *item; if (s[0] == '+' || s[0] == '-') { s++; } while ((item = _format_parse(&s))) { char *tmp; tmp = alloca(s - item + 1); strncpy(tmp, item, s - item); tmp[s - item] = '\0'; if (((!strcmp(item, "\n")) || (!strcmp(item, "\\n"))) || ((!strcmp(item, "\t")) || (!strcmp(item, "\\t"))) || (!strcmp(item, "ps")) || (!strcmp(item, "item"))) return EINA_TRUE; } return EINA_FALSE; } /** * Sets the cursor to the position of where the fmt points to. * to be documented. * @param cur to be documented. * @return to be documented. */ static void _evas_textblock_cursor_node_text_at_format(Evas_Textblock_Cursor *cur, Evas_Object_Textblock_Node_Format *fmt) { Evas_Object_Textblock_Node_Text *text; Evas_Object_Textblock_Node_Format *base_format; Evas_Object_Textblock_Node_Format *itr; size_t position = 0; if (!cur || !fmt) return; /* Find the main format node */ text = fmt->text_node; cur->node = text; base_format = text->format_node; EINA_INLIST_FOREACH(base_format, itr) { if (itr == fmt) { break; } position += itr->offset; } cur->pos = position; } /** * Reduce offset from the next offset FIXME: doc * to be documented. * @param cur to be documented. * @return to be documented. */ static void _evas_textblock_node_format_adjust_offset(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *tnode, Evas_Object_Textblock_Node_Format *fmt, int offset) { size_t position = 0; if (fmt) { fmt = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next); } else { fmt = o->format_nodes; } if (fmt && (tnode == fmt->text_node)) { fmt->offset += offset; } } /** * Removes a format node * to be documented. * @param cur to be documented. * @return to be documented. */ static void _evas_textblock_node_format_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n) { int visible_adjustment; /* Update the text nodes about the change */ { Evas_Object_Textblock_Node_Format *nnode; nnode = _NODE_FORMAT(EINA_INLIST_GET(n)->next); /* If there's a next node that belongs to the same text node * and the curret node was the main one, advance the format node */ if (nnode && (nnode->text_node == n->text_node)) { if (nnode->text_node->format_node == n) { nnode->text_node->format_node = nnode; } } else { Evas_Object_Textblock_Node_Text *tnode; /* If there's no next one update the text nodes */ nnode = _NODE_FORMAT(EINA_INLIST_GET(n)->prev); tnode = n->text_node; while (tnode && (tnode->format_node == n)) { tnode->format_node = nnode; tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next); } } } /* If it's a visible format, reduce one */ visible_adjustment = (n->visible) ? 1 : 0; _evas_textblock_node_format_adjust_offset(o, n->text_node, n, n->offset - visible_adjustment); o->format_nodes = _NODE_FORMAT(eina_inlist_remove( EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n))); } static void _evas_textblock_node_format_remove_all_at_pos(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n) { Evas_Object_Textblock_Node_Format *nnode; Evas_Object_Textblock_Node_Text *tnode; int off; nnode = n; tnode = n->text_node; do { Evas_Object_Textblock_Node_Format *curnode; curnode = nnode; nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); off = nnode->offset; _evas_textblock_node_format_remove(o, curnode); } while (nnode && (nnode->text_node == tnode) && (off == 0)); } /* end = -1 means to the end */ static void _evas_textblock_node_text_remove_formats_between(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n, int start, int end) { Evas_Object_Textblock_Node_Format *itr; int use_end = 1; int offset = 0; itr = n->format_node; if (end < 0) end = 0; while (itr && (itr->text_node == n)) { if ((end <= 0) && use_end) { itr->offset += offset; break; } if (start <= 0) { offset += itr->offset; _evas_textblock_node_format_remove(o, itr); } else { start -= itr->offset; } end -= itr->offset; itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); } } /** * Removes a text node * to be documented. * @param cur to be documented. * @return to be documented. */ static void _evas_textblock_node_text_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n) { _evas_textblock_node_text_remove_formats_between(o, n, 0, -1); o->text_nodes = _NODE_TEXT(eina_inlist_remove( EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n))); } /** * Return the position where the formats starts at. * to be documented. * @param cur to be documented. * @return to be documented. */ static size_t _evas_textblock_node_format_pos_get(const Evas_Object_Textblock_Node_Format *fmt) { Evas_Object_Textblock_Node_Text *text; Evas_Object_Textblock_Node_Format *base_format; Evas_Object_Textblock_Node_Format *itr; size_t position = 0; if (!fmt) return 0; /* Find the main format node */ text = fmt->text_node; base_format = text->format_node; EINA_INLIST_FOREACH(base_format, itr) { if (itr == fmt) { break; } position += itr->offset; } return position + fmt->offset; } /** * Return the current cursor pos. * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI int evas_textblock_cursor_pos_get(const Evas_Textblock_Cursor *cur) { if (!cur) return -1; return cur->pos; } /** * Set the cursor pos. * to be documented. * @param cur to be documented. * @param pos to be documented. */ EAPI void evas_textblock_cursor_pos_set(Evas_Textblock_Cursor *cur, int pos) { unsigned int len; if (!cur) return; if (!cur->node) return; len = eina_ustrbuf_length_get(cur->node->unicode); if (pos < 0) pos = 0; else if (pos > len) pos = len; cur->pos = pos; } /** * Go to the start of the line passed * to be documented. * @param cur to be documented. * @param line to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_line_set(Evas_Textblock_Cursor *cur, int line) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln; Evas_Object_Textblock_Item *it; Evas_Object_Textblock_Format_Item *fi; if (!cur) return EINA_FALSE; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (!o->formatted.valid) _relayout(cur->obj); ln = _find_layout_line_num(cur->obj, line); if (!ln) return EINA_FALSE; it = (Evas_Object_Textblock_Item *)ln->items; fi = (Evas_Object_Textblock_Format_Item *)ln->format_items; if ((it) && (fi)) { if (it->x < fi->x) fi = NULL; else it = NULL; } if (it) { cur->pos = it->source_pos; cur->node = it->source_node; } else if (fi) { cur->node = fi->source_node->text_node; cur->pos = _evas_textblock_node_format_pos_get(fi->source_node); } else { cur->pos = 0; cur->node = o->text_nodes; } return EINA_TRUE; } /** * Compare two cursors. * to be documented. * @param cur1 to be documented. * @param cur2 to be documented. * @return to be documented. */ EAPI int evas_textblock_cursor_compare(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2) { Eina_Inlist *l1, *l2; if (!cur1) return 0; if (!cur2) return 0; if (cur1->obj != cur2->obj) return 0; if ((!cur1->node) || (!cur2->node)) return 0; if (cur1->node == cur2->node) { if (cur1->pos < cur2->pos) return -1; /* cur1 < cur2 */ else if (cur1->pos > cur2->pos) return 1; /* cur2 < cur1 */ return 0; } for (l1 = EINA_INLIST_GET(cur1->node), l2 = EINA_INLIST_GET(cur1->node); (l1) || (l2);) { if (l1 == EINA_INLIST_GET(cur2->node)) return 1; /* cur2 < cur 1 */ else if (l2 == EINA_INLIST_GET(cur2->node)) return -1; /* cur1 < cur 2 */ else if (!l1) return -1; /* cur1 < cur 2 */ else if (!l2) return 1; /* cur2 < cur 1 */ l1 = l1->prev; l2 = l2->next; } return 0; } /** * Make cur_dest point to the same place as cur. * to be documented. * @param cur to be documented. * @param cur_dest to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_copy(const Evas_Textblock_Cursor *cur, Evas_Textblock_Cursor *cur_dest) { if (!cur) return; if (!cur_dest) return; if (cur->obj != cur_dest->obj) return; cur_dest->pos = cur->pos; cur_dest->node = cur->node; } /* text controls */ static void _evas_textblock_node_text_free(Evas_Object_Textblock_Node_Text *n) { if (!n) return; eina_ustrbuf_free(n->unicode); if (n->utf8) free(n->utf8); free(n); } static Evas_Object_Textblock_Node_Text * _evas_textblock_node_text_new() { Evas_Object_Textblock_Node_Text *n; n = calloc(1, sizeof(Evas_Object_Textblock_Node_Text)); n->unicode = eina_ustrbuf_new(); #ifdef BIDI_SUPPORT n->bidi_props.direction = FRIBIDI_PAR_ON; #endif return n; } /** * to be documented. * @param cur to be documented. * @param text to be documented. * @return Returns no value. */ static void _evas_textblock_cursor_break_paragraph(Evas_Textblock_Cursor *cur, Evas_Object_Textblock_Node_Format *fnode) { Evas_Object_Textblock *o; Evas_Object_Textblock_Node_Text *n; if (!cur) return; o = (Evas_Object_Textblock *)(cur->obj->object_data); n = _evas_textblock_node_text_new(); o->text_nodes = _NODE_TEXT(eina_inlist_append_relative( EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n), EINA_INLIST_GET(cur->node))); /* Handle text and format changes. */ if (cur->node) { Evas_Object_Textblock_Node_Format *nnode; size_t len, start; const Eina_Unicode *text; /* If there was a format node in the delete range, * make it our format and update the text_node fields, * otherwise, use the paragraph separator * of the previous paragraph. */ nnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); if (nnode && (nnode->text_node == cur->node)) { n->format_node = nnode; nnode->offset--; /* We don't have to take the replacement char into account anymore */ while (nnode && (nnode->text_node == cur->node)) { nnode->text_node = n; nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); } } else { n->format_node = fnode; } /* cur->pos now points to the PS, move after. */ start = cur->pos + 1; text = eina_ustrbuf_string_get(cur->node->unicode); len = eina_ustrbuf_length_get(cur->node->unicode) - start; eina_ustrbuf_append_length(n->unicode, text + start, len); eina_ustrbuf_remove(cur->node->unicode, start, start + len); } else { Evas_Object_Textblock_Node_Format *fnode; fnode = o->format_nodes; if (fnode) { fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->last); } n->format_node = fnode; } } static void _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj) { o->formatted.valid = 0; o->native.valid = 0; o->changed = 1; if (o->markup_text) { free(o->markup_text); o->markup_text = NULL; } evas_object_change(obj); } /** * to be documented. * @param cur to be documented. * @param text to be documented. * @return Returns the len; */ EAPI size_t evas_textblock_cursor_text_append(Evas_Textblock_Cursor *cur, const char *_text) { Evas_Object_Textblock *o; Evas_Object_Textblock_Node_Text *n; Evas_Object_Textblock_Node_Format *fnode = NULL; Eina_Unicode *text; size_t len = 0; if (!cur) return 0; text = evas_common_encoding_utf8_to_unicode(_text, &len); o = (Evas_Object_Textblock *)(cur->obj->object_data); /*FIXME: should we? cause we don't. */ /* Update all the cursors after our position. */ { Eina_List *l; Evas_Textblock_Cursor *data; if (cur != o->cursor) { if (cur->node == o->cursor->node) { if ((o->cursor->node) && (o->cursor->pos >= cur->pos)) { o->cursor->pos += eina_unicode_strlen(text); } } } } n = cur->node; if (n) { Evas_Object_Textblock_Node_Format *nnode; /*FIXME: won't work for invisible formats */ if (evas_textblock_cursor_format_is_visible_get(cur)) { fnode = _evas_textblock_cursor_node_format_before_pos_get(cur); } else { fnode = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); fnode = _evas_textblock_node_format_last_at_off(fnode); } /* find the node after the current in the same paragraph * either we find one and then take the next, or we try to get * the first for the paragraph which must be after our position */ if (fnode) { nnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); if (nnode && (nnode->text_node == n)) { fnode = nnode; } else { fnode = NULL; } } else { fnode = n->format_node; } } else { n = _evas_textblock_node_text_new(); o->text_nodes = _NODE_TEXT(eina_inlist_append( EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n))); cur->node = n; } eina_ustrbuf_insert_length(n->unicode, text, len, cur->pos); /* Advance the formats */ if (fnode && (fnode->text_node == cur->node)) fnode->offset += len; #ifdef BIDI_SUPPORT evas_bidi_update_props(eina_ustrbuf_string_get(n->unicode), &n->bidi_props); #endif _evas_textblock_changed(o, cur->obj); free(text); return len; } /** * to be documented. * @param cur to be documented. * @param text to be documented. * @return Returns the length of _text */ EAPI size_t evas_textblock_cursor_text_prepend(Evas_Textblock_Cursor *cur, const char *_text) { size_t len; /*append is essentially prepend without advancing */ len = evas_textblock_cursor_text_append(cur, _text); cur->pos += len; /*Advance */ } static void _evas_textblock_node_format_free(Evas_Object_Textblock_Node_Format *n) { if (!n) return; eina_strbuf_free(n->format); free(n); } static Evas_Object_Textblock_Node_Format * _evas_textblock_node_format_new(const char *format) { Evas_Object_Textblock_Node_Format *n; n = calloc(1, sizeof(Evas_Object_Textblock_Node_Format)); n->format = eina_strbuf_new(); eina_strbuf_append(n->format, format); n->visible = _evas_textblock_format_is_visible(format); return n; } /** * to be documented. * @param cur to be documented. * @param format to be documented. * @return Returns true if visible */ EAPI Eina_Bool evas_textblock_cursor_format_append(Evas_Textblock_Cursor *cur, const char *format) { Evas_Object_Textblock *o; Evas_Object_Textblock_Node_Format *n; Eina_Bool is_visible; if (!cur) return EINA_FALSE; if ((!format) || (format[0] == 0)) return EINA_FALSE; o = (Evas_Object_Textblock *)(cur->obj->object_data); /* We should always have at least one text node */ if (!o->text_nodes) { evas_textblock_cursor_text_prepend(cur, ""); } n = _evas_textblock_node_format_new(format); is_visible = n->visible; if (!cur->node) { o->format_nodes = _NODE_FORMAT(eina_inlist_append( EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n))); cur->pos = 0; n->text_node = (EINA_INLIST_GET(n)->prev) ? _NODE_FORMAT(EINA_INLIST_GET(n)->prev)->text_node : o->text_nodes; cur->node = n->text_node; } else { Evas_Object_Textblock_Node_Format *fmt; fmt = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); n->text_node = cur->node; if (!fmt) { o->format_nodes = _NODE_FORMAT(eina_inlist_prepend( EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n))); n->offset = cur->pos; } else { if (evas_textblock_cursor_format_is_visible_get(cur)) { o->format_nodes = _NODE_FORMAT(eina_inlist_prepend_relative( EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n), EINA_INLIST_GET(fmt) )); n->offset = fmt->offset; if (fmt->text_node->format_node == fmt) { fmt->text_node->format_node = n; } } else { fmt = _evas_textblock_node_format_last_at_off(fmt); o->format_nodes = _NODE_FORMAT(eina_inlist_append_relative( EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n), EINA_INLIST_GET(fmt) )); if (fmt->text_node != cur->node) { n->offset = cur->pos; } else { n->offset = cur->pos - _evas_textblock_node_format_pos_get(fmt); } } } /* Adjust differently if we insert a format char */ if (is_visible) { _evas_textblock_node_format_adjust_offset(o, cur->node, n, -(n->offset - 1)); } else { _evas_textblock_node_format_adjust_offset(o, cur->node, n, -n->offset); } if (!fmt || (fmt->text_node != cur->node)) { cur->node->format_node = n; } } if (is_visible) { eina_ustrbuf_insert_char(cur->node->unicode, EVAS_TEXTBLOCK_REPLACEMENT_CHAR, cur->pos); /*FIXME: should we? because we don't */ /* Advance all the cursors after our cursor */ Eina_List *l; Evas_Textblock_Cursor *data; if (cur != o->cursor) { if (cur->node == o->cursor->node) { if ((o->cursor->node) && (o->cursor->pos >= cur->pos)) { o->cursor->pos++; } } } } if (_IS_PARAGRAPH_SEPARATOR(format)) { _evas_textblock_cursor_break_paragraph(cur, n); } _evas_textblock_changed(o, cur->obj); return is_visible; } /** * to be documented. * @param cur to be documented. * @param format to be documented. * @return Returns no value. */ EAPI Eina_Bool evas_textblock_cursor_format_prepend(Evas_Textblock_Cursor *cur, const char *format) { Eina_Bool is_visible; /* append is essentially prepend without advancing */ is_visible = evas_textblock_cursor_format_append(cur, format); if (is_visible) { /* Advance after the replacement char */ evas_textblock_cursor_char_next(cur); } return is_visible; } /** * to be documented. * @param cur to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_char_delete(Evas_Textblock_Cursor *cur) { Evas_Object_Textblock *o; Evas_Object_Textblock_Node_Text *n, *n2; int merge_nodes = 0; const Eina_Unicode *text; int chr, index, ppos; if (!cur) return; o = (Evas_Object_Textblock *)(cur->obj->object_data); n = cur->node; text = eina_ustrbuf_string_get(n->unicode); index = cur->pos; chr = GET_NEXT(text, index); if (chr == 0) return; ppos = cur->pos; /* Remove a format node if needed, and remove the char only if the * fmt node is not visible */ { Evas_Object_Textblock_Node_Format *fmt; fmt = _evas_textblock_cursor_node_format_at_pos_get(cur); if (fmt) { const char *format = NULL; Evas_Object_Textblock_Node_Format *itr; itr = fmt; do { format = eina_strbuf_string_get(fmt->format); if (format && _IS_PARAGRAPH_SEPARATOR(format)) { merge_nodes = 1; } itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); } while (itr && (itr->text_node == fmt->text_node) && (itr->offset == 0)); _evas_textblock_node_format_remove_all_at_pos(o, fmt); } /* If the format node is not visible (because visible nodes adjust * automatically when removing them) adjust */ if (!evas_textblock_cursor_format_is_visible_get(cur)) { fmt = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); fmt = _evas_textblock_node_format_last_at_off(fmt); _evas_textblock_node_format_adjust_offset(o, cur->node, fmt, -(index - cur->pos)); } } eina_ustrbuf_remove(n->unicode, cur->pos, index); /* If it was a paragraph separator, we should merge the current with the * next, there must be a next. */ if (merge_nodes) { _evas_textblock_cursor_nodes_merge(cur); } if (cur->pos == eina_ustrbuf_length_get(n->unicode)) { n2 = _NODE_TEXT(EINA_INLIST_GET(n)->next); if (n2) { cur->node = n2; cur->pos = 0; } } { Eina_List *l; Evas_Textblock_Cursor *data; if (cur != o->cursor) { if ((n == o->cursor->node) && (o->cursor->pos > ppos)) { o->cursor->pos -= (index - ppos); } } EINA_LIST_FOREACH(o->cursors, l, data) { if (data != cur) { if ((n == data->node) && (data->pos > ppos)) { data->pos -= (index - ppos); } } } } _evas_textblock_changed(o, cur->obj); } /** * to be documented. * @param cur1 to be documented. * @param cur2 to be documented. * @return Returns no value. */ EAPI void evas_textblock_cursor_range_delete(Evas_Textblock_Cursor *cur1, Evas_Textblock_Cursor *cur2) { Evas_Object_Textblock *o; Evas_Object_Textblock_Node_Text *n1, *n2, *n; Evas_Object_Textblock_Node_Format *fnode = NULL; if (!cur1 || !cur1->node) return; if (!cur2 || !cur2->node) return; if (cur1->obj != cur2->obj) return; o = (Evas_Object_Textblock *)(cur1->obj->object_data); if (evas_textblock_cursor_compare(cur1, cur2) > 0) { Evas_Textblock_Cursor *tc; tc = cur1; cur1 = cur2; cur2 = tc; } n1 = cur1->node; n2 = cur2->node; cur2->pos++; /* Also remove the marked char */ /* Find the first format node after cur2 */ fnode = _evas_textblock_cursor_node_format_before_pos_get(cur2); { Evas_Object_Textblock_Node_Text *tnode; if (fnode) { tnode = fnode->text_node; fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); if (fnode && (tnode != fnode->text_node)) { fnode = NULL; } } else { fnode = o->format_nodes; } } if (n1 == n2) { _evas_textblock_node_text_remove_formats_between(o, n1, cur1->pos, cur2->pos); eina_ustrbuf_remove(n1->unicode, cur1->pos, cur2->pos); if (fnode && (fnode->text_node == n1)) { fnode->offset -= cur2->pos - cur1->pos; } } else { int len; n = _NODE_TEXT(EINA_INLIST_GET(n1)->next); /* Remove all the text nodes between */ while (n && (n != n2)) { _evas_textblock_node_text_remove(o, n); n = _NODE_TEXT(EINA_INLIST_GET(n)->next); } /* Remove the formats and the strings in the first and last nodes */ len = eina_ustrbuf_length_get(n1->unicode); _evas_textblock_node_text_remove_formats_between(o, n1, cur1->pos, len); _evas_textblock_node_text_remove_formats_between(o, n2, 0, cur2->pos); eina_ustrbuf_remove(n1->unicode, cur1->pos, len); eina_ustrbuf_remove(n2->unicode, 0, cur2->pos); if (fnode && (fnode->text_node == n2)) { fnode->offset -= cur2->pos; } /* Merge the nodes because we removed the PS */ _evas_textblock_nodes_merge(o, n1, n2); } evas_textblock_cursor_copy(cur1, cur2); _evas_textblock_changed(o, cur1->obj); } /** * to be documented. * @param cur1 to be documented. * @param cur2 to be documented. * @param format to be documented. * @return to be documented. */ /* FIXME: support format and markup */ EAPI char * evas_textblock_cursor_range_text_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *_cur2, Evas_Textblock_Text_Type format) { Evas_Object_Textblock *o; Evas_Object_Textblock_Node_Text *n1, *n2, *n; const Eina_Unicode *text; Eina_UStrbuf *buf; Evas_Textblock_Cursor *cur2; buf = eina_ustrbuf_new(); if (!cur1 || !cur1->node) return; if (!_cur2 || !_cur2->node) return; if (cur1->obj != _cur2->obj) return; o = (Evas_Object_Textblock *)(cur1->obj->object_data); if (evas_textblock_cursor_compare(cur1, _cur2) > 0) { const Evas_Textblock_Cursor *tc; tc = cur1; cur1 = _cur2; _cur2 = tc; } n1 = cur1->node; n2 = _cur2->node; /* Work on a local copy of the cur */ cur2 = alloca(sizeof(Evas_Textblock_Cursor)); cur2->obj = _cur2->obj; evas_textblock_cursor_copy(_cur2, cur2); cur2->pos++; /* We want to also copy the pointed to char */ if (n1 == n2) { text = eina_ustrbuf_string_get(n1->unicode); eina_ustrbuf_append_length(buf, text, cur2->pos - cur1->pos); } else { int len; n = _NODE_TEXT(EINA_INLIST_GET(n1)->next); /* Add all the text nodes between */ while (n && (n != n2)) { text = eina_ustrbuf_string_get(n->unicode); eina_ustrbuf_append(buf, text); n = _NODE_TEXT(EINA_INLIST_GET(n)->next); } len = eina_ustrbuf_length_get(n1->unicode); text = eina_ustrbuf_string_get(n1->unicode); eina_ustrbuf_append_length(buf, text, len - cur1->pos); len = eina_ustrbuf_length_get(n2->unicode); text = eina_ustrbuf_string_get(n2->unicode); eina_ustrbuf_append_length(buf, text + cur2->pos, len - cur2->pos); } /* return the string */ { char *ret; const Eina_Unicode *tmp; tmp = eina_ustrbuf_string_get(buf); ret = evas_common_encoding_unicode_to_utf8(tmp, NULL); eina_ustrbuf_free(buf); return ret; } } /** * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI const char * evas_textblock_cursor_paragraph_text_get(const Evas_Textblock_Cursor *cur) { if (!cur) return NULL; if (!cur->node) return NULL; /*FIXME-tom: strip replace chars */ if (cur->node->utf8) { free(cur->node->utf8); } cur->node->utf8 = evas_common_encoding_unicode_to_utf8( eina_ustrbuf_string_get(cur->node->unicode), NULL); return cur->node->utf8; } /** * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI int evas_textblock_cursor_paragraph_text_length_get(const Evas_Textblock_Cursor *cur) { if (!cur) return 0; if (!cur->node) return 0; return eina_ustrbuf_length_get(cur->node->unicode); } /** * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI const Evas_Object_Textblock_Node_Format * evas_textblock_cursor_format_get(const Evas_Textblock_Cursor *cur) { if (!cur) return NULL; if (!cur->node) return NULL; return _evas_textblock_cursor_node_format_at_pos_get(cur); } /** * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI const char * evas_textblock_node_format_text_get(const Evas_Object_Textblock_Node_Format *fmt) { if (!fmt) return NULL; return eina_strbuf_string_get(fmt->format); } /** * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI void evas_textblock_cursor_at_format_set(Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Format *fmt) { if (!fmt || !cur) return; cur->node = fmt->text_node; cur->pos = _evas_textblock_node_format_pos_get(fmt); } /** * to be documented. * @param cur to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_format_is_visible_get(const Evas_Textblock_Cursor *cur) { const Eina_Unicode *text; if (!cur) return EINA_FALSE; if (!cur->node) return EINA_FALSE; text = eina_ustrbuf_string_get(cur->node->unicode); return (text[cur->pos] == EVAS_TEXTBLOCK_REPLACEMENT_CHAR) ? EINA_TRUE : EINA_FALSE; } /** * to be documented. * @param cur to be documented. * @param cx to be documented. * @param cy to be documented. * @param cw to be documented. * @param ch to be documented. * @return to be documented. */ EAPI int evas_textblock_cursor_char_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln = NULL; Evas_Object_Textblock_Item *it = NULL; Evas_Object_Textblock_Format_Item *fi = NULL; int x = 0, y = 0, w = 0, h = 0; int pos, ret; if (!cur) return -1; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (!cur->node) { if (!o->text_nodes) { ln = o->lines; if (!ln) return -1; if (cx) *cx = ln->x; if (cy) *cy = ln->y; if (cw) *cw = ln->w; if (ch) *ch = ln->h; return ln->line_no; } else return -1; } if (!o->formatted.valid) _relayout(cur->obj); if (evas_textblock_cursor_format_is_visible_get(cur)) { _find_layout_format_item_line_match(cur->obj, _evas_textblock_cursor_node_format_at_pos_get(cur), &ln, &fi); } else { _find_layout_item_line_match(cur->obj, cur->node, cur->pos, &ln, &it); } if (!ln) { return -1; } if (it) { pos = cur->pos - it->source_pos; ret = -1; if (pos < 0) pos = 0; if (it->format->font.font) { ret = cur->ENFN->font_char_coords_get(cur->ENDT, it->format->font.font, it->text, &it->bidi_props, pos, &x, &y, &w, &h); } if (ret <= 0) { if (it->format->font.font) cur->ENFN->font_string_size_get(cur->ENDT, it->format->font.font, it->text, &it->bidi_props, &w, &h); x = w; y = 0; w = 0; } x = ln->x + it->x - it->inset + x; if (x < ln->x) { x = ln->x; w -= (ln->x - x); } y = ln->y; h = ln->h; } else if (fi) { x = ln->x + fi->x; y = ln->y; w = fi->w; h = ln->h; } else { return -1; } if (cx) *cx = x; if (cy) *cy = y; if (cw) *cw = w; if (ch) *ch = h; return ln->line_no; } /** * to be documented. * @param cur to be documented. * @param cx to be documented. * @param cy to be documented. * @param cw to be documented. * @param ch to be documented. * @return to be documented. */ EAPI int evas_textblock_cursor_line_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln = NULL; Evas_Object_Textblock_Item *it = NULL; Evas_Object_Textblock_Format_Item *fi = NULL; int x, y, w, h; if (!cur) return -1; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (!o->formatted.valid) _relayout(cur->obj); if (!cur->node) { ln = o->lines; } else { if (evas_textblock_cursor_format_is_visible_get(cur)) { _find_layout_format_item_line_match(cur->obj, _evas_textblock_node_visible_at_pos_get( _evas_textblock_cursor_node_format_before_or_at_pos_get(cur)), &ln, &fi); } else { _find_layout_item_line_match(cur->obj, cur->node, cur->pos, &ln, &it); } } if (!ln) return -1; x = ln->x; y = ln->y; w = ln->w; h = ln->h; if (cx) *cx = x; if (cy) *cy = y; if (cw) *cw = w; if (ch) *ch = h; return ln->line_no; } /** * to be documented. * @param cur to be documented. * @param x to be documented. * @param y to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln; Evas_Object_Textblock_Item *it = NULL, *it_break = NULL; Evas_Object_Textblock_Format_Item *fi = NULL; if (!cur) return EINA_FALSE; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (!o->formatted.valid) _relayout(cur->obj); x += o->style_pad.l; y += o->style_pad.t; EINA_INLIST_FOREACH(o->lines, ln) { if (ln->y > y) break; if ((ln->y <= y) && ((ln->y + ln->h) > y)) { EINA_INLIST_FOREACH(ln->items, it) { if ((it->x + ln->x) > x) { it_break = it; break; } if (((it->x + ln->x) <= x) && (((it->x + ln->x) + it->w) > x)) { int pos; int cx, cy, cw, ch; pos = -1; if (it->format->font.font) pos = cur->ENFN->font_char_at_coords_get(cur->ENDT, it->format->font.font, it->text, &it->bidi_props, x - it->x - ln->x, 0, &cx, &cy, &cw, &ch); if (pos < 0) return EINA_FALSE; cur->pos = pos + it->source_pos; cur->node = it->source_node; return 1; } } EINA_INLIST_FOREACH(ln->format_items, fi) { if ((fi->x + ln->x) > x) break; if (((fi->x + ln->x) <= x) && (((fi->x + ln->x) + fi->w) > x)) { cur->pos = _evas_textblock_node_format_pos_get(fi->source_node); cur->node = fi->source_node->text_node; return EINA_TRUE; } } if (it_break) { it = it_break; cur->pos = it->source_pos; cur->node = it->source_node; return EINA_TRUE; } } } return EINA_FALSE; } /** * to be documented. * @param cur to be documented. * @param y to be documented. * @return to be documented. */ EAPI int evas_textblock_cursor_line_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord y) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln; if (!cur) return -1; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (!o->formatted.valid) _relayout(cur->obj); y += o->style_pad.t; EINA_INLIST_FOREACH(o->lines, ln) { if (ln->y > y) break; if ((ln->y <= y) && ((ln->y + ln->h) > y)) { evas_textblock_cursor_line_set(cur, ln->line_no); return ln->line_no; } } return -1; } /** * to be documented. * @param cur1 to be documented. * @param cur2 to be documented. * @return to be documented. */ EAPI Eina_List * evas_textblock_cursor_range_geometry_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2) { Eina_List *rects = NULL; Evas_Coord cx, cy, cw, ch, lx, ly, lw, lh; Evas_Textblock_Rectangle *tr; int i, line, line2; if (!cur1) return NULL; if (!cur2) return NULL; if (cur1->obj != cur2->obj) return NULL; if (evas_textblock_cursor_compare(cur1, cur2) > 0) { const Evas_Textblock_Cursor *tc; tc = cur1; cur1 = cur2; cur2 = tc; } line = evas_textblock_cursor_char_geometry_get(cur1, &cx, &cy, &cw, &ch); if (line < 0) return NULL; line = evas_textblock_cursor_line_geometry_get(cur1, &lx, &ly, &lw, &lh); if (line < 0) return NULL; line2 = evas_textblock_cursor_line_geometry_get(cur2, NULL, NULL, NULL, NULL); if (line2 < 0) return NULL; if (line == line2) { tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); rects = eina_list_append(rects, tr); tr->x = cx; tr->y = ly; tr->h = lh; line = evas_textblock_cursor_char_geometry_get(cur2, &cx, &cy, &cw, &ch); if (line < 0) { while (rects) { free(rects->data); rects = eina_list_remove_list(rects, rects); } return NULL; } tr->w = cx + cw - tr->x; } else { tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); rects = eina_list_append(rects, tr); tr->x = cx; tr->y = ly; tr->h = lh; tr->w = lx + lw - cx; for (i = line +1; i < line2; i++) { evas_object_textblock_line_number_geometry_get(cur1->obj, i, &lx, &ly, &lw, &lh); tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); rects = eina_list_append(rects, tr); tr->x = lx; tr->y = ly; tr->h = lh; tr->w = lw; } line = evas_textblock_cursor_char_geometry_get(cur2, &cx, &cy, &cw, &ch); if (line < 0) { while (rects) { free(rects->data); rects = eina_list_remove_list(rects, rects); } return NULL; } line = evas_textblock_cursor_line_geometry_get(cur2, &lx, &ly, &lw, &lh); if (line < 0) { while (rects) { free(rects->data); rects = eina_list_remove_list(rects, rects); } return NULL; } tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); rects = eina_list_append(rects, tr); tr->x = lx; tr->y = ly; tr->h = lh; tr->w = cx + cw - lx; } return rects; } /** * to be documented. * @param cur to be documented. * @param cx to be documented. * @param cy to be documented. * @param cw to be documented. * @param ch to be documented. * @return to be documented. */ EAPI Eina_Bool evas_textblock_cursor_format_item_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln = NULL; Evas_Object_Textblock_Format_Item *fi = NULL; Evas_Coord x, y, w, h; if (!cur) return 0; o = (Evas_Object_Textblock *)(cur->obj->object_data); if (!o->formatted.valid) _relayout(cur->obj); _find_layout_format_item_line_match(cur->obj, _evas_textblock_cursor_node_format_before_or_at_pos_get(cur), &ln, &fi); if ((!ln) || (!fi)) return 0; x = ln->x + fi->x; y = ln->y + ln->baseline + fi->y; w = fi->w; h = fi->h; if (cx) *cx = x; if (cy) *cy = y; if (cw) *cw = w; if (ch) *ch = h; return 1; } /** * To be documented. * * FIXME: To be fixed. * */ EAPI Eina_Bool evas_textblock_cursor_eol_get(const Evas_Textblock_Cursor *cur) { Eina_Bool ret = EINA_FALSE; Evas_Textblock_Cursor cur2; if (!cur) return EINA_FALSE; /* FIXME: optimize a bit */ evas_textblock_cursor_copy(cur, &cur2); evas_textblock_cursor_line_char_last(&cur2); if (cur2.pos == cur->pos) { ret = EINA_TRUE; } return ret; } /* general controls */ /** * to be documented. * @param obj to be documented. * @param line to be documented. * @param cx to be documented. * @param cy to be documented. * @param cw to be documented. * @param ch to be documented. * @return to be documented. */ EAPI Eina_Bool evas_object_textblock_line_number_geometry_get(const Evas_Object *obj, int line, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) { Evas_Object_Textblock_Line *ln; TB_HEAD_RETURN(0); ln = _find_layout_line_num(obj, line); if (!ln) return EINA_FALSE; if (cx) *cx = ln->x; if (cy) *cy = ln->y; if (cw) *cw = ln->w; if (ch) *ch = ln->h; return EINA_TRUE; } /** * to be documented. * @param obj to be documented. * @return Returns no value. */ EAPI void evas_object_textblock_clear(Evas_Object *obj) { Eina_List *l; Evas_Textblock_Cursor *cur; TB_HEAD(); _nodes_clear(obj); o->cursor->node = NULL; o->cursor->pos = 0; EINA_LIST_FOREACH(o->cursors, l, cur) { cur->node = NULL; cur->pos = 0; } /* FIXME: free the paragraphs as well */ if (o->lines) { _lines_clear(obj, o->lines); o->lines = NULL; } _evas_textblock_changed(o, obj); } /** * to be documented. * @param obj to be documented. * @param w to be documented. * @param h to be documented. * @return Returns no value. */ EAPI void evas_object_textblock_size_formatted_get(const Evas_Object *obj, Evas_Coord *w, Evas_Coord *h) { TB_HEAD(); if (!o->formatted.valid) _relayout(obj); if (w) *w = o->formatted.w; if (h) *h = o->formatted.h; } /** * to be documented. * @param obj to be documented. * @param w to be documented. * @param h to be documented. * @return Returns no value. */ EAPI void evas_object_textblock_size_native_get(const Evas_Object *obj, Evas_Coord *w, Evas_Coord *h) { TB_HEAD(); if (!o->native.valid) { _layout(obj, 1, -1, -1, &o->native.w, &o->native.h); o->native.valid = 1; } if (w) *w = o->native.w; if (h) *h = o->native.h; } /** * to be documented. * @param obj to be documented. * @param l to be documented. * @param r to be documented. * @param t to be documented. * @param b to be documented. * @return Returns no value. */ EAPI void evas_object_textblock_style_insets_get(const Evas_Object *obj, Evas_Coord *l, Evas_Coord *r, Evas_Coord *t, Evas_Coord *b) { TB_HEAD(); if (!o->formatted.valid) _relayout(obj); if (l) *l = o->style_pad.l; if (r) *r = o->style_pad.r; if (t) *t = o->style_pad.t; if (b) *b = o->style_pad.b; } /** * @} */ /* all nice and private */ static void evas_object_textblock_init(Evas_Object *obj) { Evas_Object_Textblock *o; /* alloc image ob, setup methods and default values */ obj->object_data = evas_object_textblock_new(); /* set up default settings for this kind of object */ obj->cur.color.r = 255; obj->cur.color.g = 255; obj->cur.color.b = 255; obj->cur.color.a = 255; obj->cur.geometry.x = 0.0; obj->cur.geometry.y = 0.0; obj->cur.geometry.w = 0.0; obj->cur.geometry.h = 0.0; obj->cur.layer = 0; /* set up object-specific settings */ obj->prev = obj->cur; /* set up methods (compulsory) */ obj->func = &object_func; obj->type = o_type; o = (Evas_Object_Textblock *)(obj->object_data); o->cursor->obj = obj; } static void * evas_object_textblock_new(void) { Evas_Object_Textblock *o; /* alloc obj private data */ o = calloc(1, sizeof(Evas_Object_Textblock)); o->magic = MAGIC_OBJ_TEXTBLOCK; o->cursor = calloc(1, sizeof(Evas_Textblock_Cursor)); _format_command_init(); return o; } static void evas_object_textblock_free(Evas_Object *obj) { Evas_Object_Textblock *o; evas_object_textblock_clear(obj); evas_object_textblock_style_set(obj, NULL); o = (Evas_Object_Textblock *)(obj->object_data); free(o->cursor); while (o->cursors) { Evas_Textblock_Cursor *cur; cur = (Evas_Textblock_Cursor *)o->cursors->data; o->cursors = eina_list_remove_list(o->cursors, o->cursors); free(cur); } if (o->repch) eina_stringshare_del(o->repch); o->magic = 0; free(o); _format_command_shutdown(); } static void evas_object_textblock_render(Evas_Object *obj, void *output, void *context, void *surface, int x, int y) { Evas_Object_Textblock_Line *ln; Evas_Object_Textblock *o; int i, j; int pback = 0, backx = 0; int pline = 0, linex = 0; int pline2 = 0, line2x = 0; int pstrike = 0, strikex = 0; int x2; unsigned char r = 0, g = 0, b = 0, a = 0; unsigned char r2 = 0, g2 = 0, b2 = 0, a2 = 0; unsigned char r3 = 0, g3 = 0, b3 = 0, a3 = 0; int cx, cy, cw, ch, clip; const char vals[5][5] = { {0, 1, 2, 1, 0}, {1, 3, 4, 3, 1}, {2, 4, 5, 4, 2}, {1, 3, 4, 3, 1}, {0, 1, 2, 1, 0} }; /* render object to surface with context, and offxet by x,y */ o = (Evas_Object_Textblock *)(obj->object_data); obj->layer->evas->engine.func->context_multiplier_unset(output, context); clip = ENFN->context_clip_get(output, context, &cx, &cy, &cw, &ch); #define ITEM_WALK() \ EINA_INLIST_FOREACH(o->lines, ln) \ { \ Evas_Object_Textblock_Item *it; \ \ pback = 0; \ pline = 0; \ pline2 = 0; \ pstrike = 0; \ if (clip) \ { \ if ((obj->cur.geometry.y + y + ln->y + ln->h) < (cy - 20)) \ continue; \ if ((obj->cur.geometry.y + y + ln->y) > (cy + ch + 20)) \ break; \ } \ EINA_INLIST_FOREACH(ln->items, it) \ { \ int yoff; \ \ yoff = ln->baseline; \ if (it->format->valign != -1.0) \ yoff = (it->format->valign * (double)(ln->h - it->h)) + it->baseline; \ if (clip) \ { \ if ((obj->cur.geometry.x + x + ln->x + it->x - it->inset + it->w) < (cx - 20)) \ continue; \ if ((obj->cur.geometry.x + x + ln->x + it->x - it->inset) > (cx + cw + 20)) \ break; \ } #define ITEM_WALK_END() \ } \ } #define COLOR_SET(col) \ ENFN->context_color_set(output, context, \ (obj->cur.cache.clip.r * it->format->color.col.r) / 255, \ (obj->cur.cache.clip.g * it->format->color.col.g) / 255, \ (obj->cur.cache.clip.b * it->format->color.col.b) / 255, \ (obj->cur.cache.clip.a * it->format->color.col.a) / 255); #define COLOR_SET_AMUL(col, amul) \ ENFN->context_color_set(output, context, \ (obj->cur.cache.clip.r * it->format->color.col.r * (amul)) / 65025, \ (obj->cur.cache.clip.g * it->format->color.col.g * (amul)) / 65025, \ (obj->cur.cache.clip.b * it->format->color.col.b * (amul)) / 65025, \ (obj->cur.cache.clip.a * it->format->color.col.a * (amul)) / 65025); # define DRAW_TEXT(ox, oy) \ if (it->format->font.font) ENFN->font_draw(output, context, surface, it->format->font.font, \ obj->cur.geometry.x + ln->x + it->x - it->inset + x + (ox), \ obj->cur.geometry.y + ln->y + yoff + y + (oy), \ it->w, it->h, it->w, it->h, it->text, &it->bidi_props); # if 0 #define DRAW_TEXT(ox, oy) \ if (it->format->font.font) ENFN->font_draw(output, context, surface, it->format->font.font, \ obj->cur.geometry.x + ln->x + it->x - it->inset + x + (ox), \ obj->cur.geometry.y + ln->y + yoff + y + (oy), \ obj->cur.cache.geometry.x + ln->x + it->x - it->inset + x + (ox), \ obj->cur.cache.geometry.y + ln->y + yoff + y + (oy), \ it->w, it->h, it->w, it->h, it->text, &it->bidi_props); #endif #define ITEM_WALK_LINE_SKIP_DROP() \ if ((ln->y + ln->h) <= 0) continue; \ if (ln->y > obj->cur.geometry.h) break pback = 0; /* backing */ ITEM_WALK(); ITEM_WALK_LINE_SKIP_DROP(); if ((it->format->backing) && (!pback) && ((EINA_INLIST_GET(it))->next)) { pback = 1; backx = it->x; r = it->format->color.backing.r; g = it->format->color.backing.g; b = it->format->color.backing.b; a = it->format->color.backing.a; } else if (((pback) && (!it->format->backing)) || (!(EINA_INLIST_GET(it))->next) || (it->format->color.backing.r != r) || (it->format->color.backing.g != g) || (it->format->color.backing.b != b) || (it->format->color.backing.a != a)) { if ((it->format->backing) && (!pback) && (!(EINA_INLIST_GET(it))->next)) { r = it->format->color.backing.r; g = it->format->color.backing.g; b = it->format->color.backing.b; a = it->format->color.backing.a; pback = 1; backx = it->x; } if (!it->format->backing) x2 = it->x; else x2 = it->x + it->w; if ((pback) && (x2 > backx)) { ENFN->context_color_set(output, context, (obj->cur.cache.clip.r * r) / 255, (obj->cur.cache.clip.g * g) / 255, (obj->cur.cache.clip.b * b) / 255, (obj->cur.cache.clip.a * a) / 255); ENFN->rectangle_draw(output, context, surface, obj->cur.geometry.x + ln->x + backx + x, obj->cur.geometry.y + ln->y + y, //// obj->cur.cache.geometry.x + ln->x + backx + x, //// obj->cur.cache.geometry.y + ln->y + y, x2 - backx, ln->h); } pback = it->format->backing; backx = it->x; r = it->format->color.backing.r; g = it->format->color.backing.g; b = it->format->color.backing.b; a = it->format->color.backing.a; } ITEM_WALK_END(); /* prepare everything for text draw */ /* shadows */ ITEM_WALK(); ITEM_WALK_LINE_SKIP_DROP(); if (it->format->style == EVAS_TEXT_STYLE_SHADOW) { COLOR_SET(shadow); DRAW_TEXT(1, 1); } else if ((it->format->style == EVAS_TEXT_STYLE_OUTLINE_SHADOW) || (it->format->style == EVAS_TEXT_STYLE_FAR_SHADOW)) { COLOR_SET(shadow); DRAW_TEXT(2, 2); } else if ((it->format->style == EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW) || (it->format->style == EVAS_TEXT_STYLE_FAR_SOFT_SHADOW)) { for (j = 0; j < 5; j++) { for (i = 0; i < 5; i++) { if (vals[i][j] != 0) { COLOR_SET_AMUL(shadow, vals[i][j] * 50); DRAW_TEXT(i, j); } } } } else if (it->format->style == EVAS_TEXT_STYLE_SOFT_SHADOW) { for (j = 0; j < 5; j++) { for (i = 0; i < 5; i++) { if (vals[i][j] != 0) { COLOR_SET_AMUL(shadow, vals[i][j] * 50); DRAW_TEXT(i - 1, j - 1); } } } } ITEM_WALK_END(); /* glows */ ITEM_WALK(); ITEM_WALK_LINE_SKIP_DROP(); if (it->format->style == EVAS_TEXT_STYLE_GLOW) { for (j = 0; j < 5; j++) { for (i = 0; i < 5; i++) { if (vals[i][j] != 0) { COLOR_SET_AMUL(glow, vals[i][j] * 50); DRAW_TEXT(i - 2, j - 2); } } } COLOR_SET(glow2); DRAW_TEXT(-1, 0); DRAW_TEXT(1, 0); DRAW_TEXT(0, -1); DRAW_TEXT(0, 1); } ITEM_WALK_END(); /* outlines */ ITEM_WALK(); ITEM_WALK_LINE_SKIP_DROP(); if ((it->format->style == EVAS_TEXT_STYLE_OUTLINE) || (it->format->style == EVAS_TEXT_STYLE_OUTLINE_SHADOW) || (it->format->style == EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW)) { COLOR_SET(outline); DRAW_TEXT(-1, 0); DRAW_TEXT(1, 0); DRAW_TEXT(0, -1); DRAW_TEXT(0, 1); } else if (it->format->style == EVAS_TEXT_STYLE_SOFT_OUTLINE) { for (j = 0; j < 5; j++) { for (i = 0; i < 5; i++) { if (((i != 2) || (j != 2)) && (vals[i][j] != 0)) { COLOR_SET_AMUL(outline, vals[i][j] * 50); DRAW_TEXT(i - 2, j - 2); } } } } ITEM_WALK_END(); /* normal text */ ITEM_WALK(); ITEM_WALK_LINE_SKIP_DROP(); COLOR_SET(normal); DRAW_TEXT(0, 0); if ((it->format->strikethrough) && (!pstrike) && ((EINA_INLIST_GET(it))->next)) { pstrike = 1; strikex = it->x; r3 = it->format->color.strikethrough.r; g3 = it->format->color.strikethrough.g; b3 = it->format->color.strikethrough.b; a3 = it->format->color.strikethrough.a; } else if (((pstrike) && (!it->format->strikethrough)) || (!(EINA_INLIST_GET(it))->next) || (it->format->color.strikethrough.r != r3) || (it->format->color.strikethrough.g != g3) || (it->format->color.strikethrough.b != b3) || (it->format->color.strikethrough.a != a3)) { if ((it->format->strikethrough) && (!pstrike)) { strikex = it->x; r3 = it->format->color.strikethrough.r; g3 = it->format->color.strikethrough.g; b3 = it->format->color.strikethrough.b; a3 = it->format->color.strikethrough.a; } x2 = it->x + it->w; if (!it->format->strikethrough) { x2 = it->x; pstrike = 0; } if (x2 > strikex) { ENFN->context_color_set(output, context, (obj->cur.cache.clip.r * r3) / 255, (obj->cur.cache.clip.g * g3) / 255, (obj->cur.cache.clip.b * b3) / 255, (obj->cur.cache.clip.a * a3) / 255); ENFN->rectangle_draw(output, context, surface, obj->cur.geometry.x + ln->x + strikex + x, obj->cur.geometry.y + ln->y + y + (ln->h / 2), //// obj->cur.cache.geometry.x + ln->x + strikex + x, //// obj->cur.cache.geometry.y + ln->y + y + (ln->h / 2), x2 - strikex, 1); } if (it->format->strikethrough) pstrike = 1; strikex = it->x; r3 = it->format->color.strikethrough.r; g3 = it->format->color.strikethrough.g; b3 = it->format->color.strikethrough.b; a3 = it->format->color.strikethrough.a; } if ((it->format->underline) && (!pline) && ((EINA_INLIST_GET(it))->next)) { pline = 1; linex = it->x; r = it->format->color.underline.r; g = it->format->color.underline.g; b = it->format->color.underline.b; a = it->format->color.underline.a; } else if (((pline) && (!it->format->underline)) || (!(EINA_INLIST_GET(it))->next) || (it->format->color.underline.r != r) || (it->format->color.underline.g != g) || (it->format->color.underline.b != b) || (it->format->color.underline.a != a)) { if ((it->format->underline) && (!pline)) { linex = it->x; r = it->format->color.underline.r; g = it->format->color.underline.g; b = it->format->color.underline.b; a = it->format->color.underline.a; } x2 = it->x + it->w; if (!it->format->underline) { x2 = it->x; pline = 0; } if (x2 > linex) { ENFN->context_color_set(output, context, (obj->cur.cache.clip.r * r) / 255, (obj->cur.cache.clip.g * g) / 255, (obj->cur.cache.clip.b * b) / 255, (obj->cur.cache.clip.a * a) / 255); ENFN->rectangle_draw(output, context, surface, obj->cur.geometry.x + ln->x + linex + x, obj->cur.geometry.y + ln->y + y + ln->baseline + 1, //// obj->cur.cache.geometry.x + ln->x + linex + x, //// obj->cur.cache.geometry.y + ln->y + y + ln->baseline + 1, x2 - linex, 1); } if (it->format->underline) pline = 1; linex = it->x; r = it->format->color.underline.r; g = it->format->color.underline.g; b = it->format->color.underline.b; a = it->format->color.underline.a; } if ((it->format->underline2) && (!pline2) && ((EINA_INLIST_GET(it))->next)) { pline2 = 1; line2x = it->x; r2 = it->format->color.underline2.r; g2 = it->format->color.underline2.g; b2 = it->format->color.underline2.b; a2 = it->format->color.underline2.a; } else if (((pline2) && (!it->format->underline2)) || (!(EINA_INLIST_GET(it))->next) || (it->format->color.underline2.r != r2) || (it->format->color.underline2.g != g2) || (it->format->color.underline2.b != b2) || (it->format->color.underline2.a != a2)) { if ((it->format->underline2) && (!pline2)) { line2x = it->x; r2 = it->format->color.underline2.r; g2 = it->format->color.underline2.g; b2 = it->format->color.underline2.b; a2 = it->format->color.underline2.a; } x2 = it->x + it->w; if (!it->format->underline2) { x2 = it->x; pline2 = 0; } if (x2 > line2x) { ENFN->context_color_set(output, context, (obj->cur.cache.clip.r * r2) / 255, (obj->cur.cache.clip.g * g2) / 255, (obj->cur.cache.clip.b * b2) / 255, (obj->cur.cache.clip.a * a2) / 255); ENFN->rectangle_draw(output, context, surface, obj->cur.geometry.x + ln->x + line2x + x, obj->cur.geometry.y + ln->y + y + ln->baseline + 3, //// obj->cur.cache.geometry.x + ln->x + line2x + x, //// obj->cur.cache.geometry.y + ln->y + y + ln->baseline + 3, x2 - line2x, 1); } if (it->format->underline2) pline2 = 1; line2x = it->x; r2 = it->format->color.underline2.r; g2 = it->format->color.underline2.g; b2 = it->format->color.underline2.b; a2 = it->format->color.underline2.a; } ITEM_WALK_END(); } static void evas_object_textblock_render_pre(Evas_Object *obj) { Evas_Object_Textblock *o; int is_v, was_v; /* dont pre-render the obj twice! */ if (obj->pre_render_done) return; obj->pre_render_done = 1; /* pre-render phase. this does anything an object needs to do just before */ /* rendering. this could mean loading the image data, retrieving it from */ /* elsewhere, decoding video etc. */ /* then when this is done the object needs to figure if it changed and */ /* if so what and where and add the appropriate redraw textblocks */ o = (Evas_Object_Textblock *)(obj->object_data); if ((o->changed) || (o->last_w != obj->cur.geometry.w)) { Evas_Object_Textblock_Line *lines; lines = o->lines; o->lines = NULL; o->formatted.valid = 0; o->native.valid = 0; _layout(obj, 0, obj->cur.geometry.w, obj->cur.geometry.h, &o->formatted.w, &o->formatted.h); o->formatted.valid = 1; if (lines) _lines_clear(obj, lines); o->last_w = obj->cur.geometry.w; o->redraw = 0; evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); o->changed = 0; is_v = evas_object_is_visible(obj); was_v = evas_object_was_visible(obj); goto done; } if (o->redraw) { o->redraw = 0; evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); o->changed = 0; is_v = evas_object_is_visible(obj); was_v = evas_object_was_visible(obj); goto done; } /* if someone is clipping this obj - go calculate the clipper */ if (obj->cur.clipper) { if (obj->cur.cache.clip.dirty) evas_object_clip_recalc(obj->cur.clipper); obj->cur.clipper->func->render_pre(obj->cur.clipper); } /* now figure what changed and add draw rects */ /* if it just became visible or invisible */ is_v = evas_object_is_visible(obj); was_v = evas_object_was_visible(obj); if (is_v != was_v) { evas_object_render_pre_visible_change(&obj->layer->evas->clip_changes, obj, is_v, was_v); goto done; } if ((obj->cur.map != obj->prev.map) || (obj->cur.usemap != obj->prev.usemap)) { evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); goto done; } /* it's not visible - we accounted for it appearing or not so just abort */ if (!is_v) goto done; /* clipper changed this is in addition to anything else for obj */ evas_object_render_pre_clipper_change(&obj->layer->evas->clip_changes, obj); /* if we restacked (layer or just within a layer) and don't clip anyone */ if (obj->restack) { evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); goto done; } /* if it changed color */ if ((obj->cur.color.r != obj->prev.color.r) || (obj->cur.color.g != obj->prev.color.g) || (obj->cur.color.b != obj->prev.color.b) || (obj->cur.color.a != obj->prev.color.a)) { evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); goto done; } /* if it changed geometry - and obviously not visibility or color */ /* caluclate differences since we have a constant color fill */ /* we really only need to update the differences */ if ((obj->cur.geometry.x != obj->prev.geometry.x) || (obj->cur.geometry.y != obj->prev.geometry.y) || (obj->cur.geometry.w != obj->prev.geometry.w) || (obj->cur.geometry.h != obj->prev.geometry.h)) { evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); goto done; } if (o->changed) { evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); o->changed = 0; } done: evas_object_render_pre_effect_updates(&obj->layer->evas->clip_changes, obj, is_v, was_v); } static void evas_object_textblock_render_post(Evas_Object *obj) { /* Evas_Object_Textblock *o; */ /* this moves the current data to the previous state parts of the object */ /* in whatever way is safest for the object. also if we don't need object */ /* data anymore we can free it if the object deems this is a good idea */ /* o = (Evas_Object_Textblock *)(obj->object_data); */ /* remove those pesky changes */ evas_object_clip_changes_clean(obj); /* move cur to prev safely for object data */ obj->prev = obj->cur; /* o->prev = o->cur; */ /* o->changed = 0; */ } static unsigned int evas_object_textblock_id_get(Evas_Object *obj) { Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); if (!o) return 0; return MAGIC_OBJ_TEXTBLOCK; } static unsigned int evas_object_textblock_visual_id_get(Evas_Object *obj) { Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); if (!o) return 0; return MAGIC_OBJ_CUSTOM; } static void *evas_object_textblock_engine_data_get(Evas_Object *obj) { Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); if (!o) return NULL; return o->engine_data; } static int evas_object_textblock_is_opaque(Evas_Object *obj) { /* this returns 1 if the internal object data implies that the object is */ /* currently fulyl opque over the entire gradient it occupies */ return 0; } static int evas_object_textblock_was_opaque(Evas_Object *obj) { /* this returns 1 if the internal object data implies that the object was */ /* currently fulyl opque over the entire gradient it occupies */ return 0; } static void evas_object_textblock_coords_recalc(Evas_Object *obj) { Evas_Object_Textblock *o; o = (Evas_Object_Textblock *)(obj->object_data); if (obj->cur.geometry.w != o->last_w) { o->formatted.valid = 0; o->native.valid = 0; o->changed = 1; } } static void evas_object_textblock_scale_update(Evas_Object *obj) { _relayout(obj); } void _evas_object_textblock_rehint(Evas_Object *obj) { Evas_Object_Textblock *o; Evas_Object_Textblock_Line *ln; o = (Evas_Object_Textblock *)(obj->object_data); EINA_INLIST_FOREACH(o->lines, ln) { Evas_Object_Textblock_Item *it; EINA_INLIST_FOREACH(ln->items, it) { if (it->format->font.font) { #ifdef EVAS_FRAME_QUEUING evas_common_pipe_op_text_flush(it->format->font.font); #endif evas_font_load_hinting_set(obj->layer->evas, it->format->font.font, obj->layer->evas->hinting); } } } o->formatted.valid = 0; o->native.valid = 0; o->changed = 1; evas_object_change(obj); }