aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/libraries/evas/src/lib/canvas/evas_object_textblock.c
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/evas/src/lib/canvas/evas_object_textblock.c')
-rw-r--r--libraries/evas/src/lib/canvas/evas_object_textblock.c9569
1 files changed, 9569 insertions, 0 deletions
diff --git a/libraries/evas/src/lib/canvas/evas_object_textblock.c b/libraries/evas/src/lib/canvas/evas_object_textblock.c
new file mode 100644
index 0000000..7941a45
--- /dev/null
+++ b/libraries/evas/src/lib/canvas/evas_object_textblock.c
@@ -0,0 +1,9569 @@
1/**
2 * @internal
3 * @section Evas_Object_Textblock_Internal Internal Textblock Object Tutorial
4 *
5 * This explains the internal design of the Evas Textblock Object, it's assumed
6 * that the reader of this section has already read @ref Evas_Object_Textblock_Tutorial "Textblock's usage docs.".
7 *
8 * @subsection textblock_internal_intro Introduction
9 * There are two main parts to the textblock object, the first being the node
10 * system, and the second being the layout system. The former is just an
11 * internal representation of the markup text, while the latter is the internal
12 * visual representation of the text (i.e positioning, sizing, fonts and etc).
13 *
14 * @subsection textblock_nodes The Nodes system
15 * The nodes mechanism consists of two main data types:
16 * ::Evas_Object_Textblock_Node_Text and ::Evas_Object_Textblock_Node_Format
17 * the former is for Text nodes and the latter is for format nodes.
18 * There's always at least one text node, even if there are only formats.
19 *
20 * @subsection textblock_nodes_text Text nodes
21 * Each text node is essentially a paragraph, it includes an @ref Eina_UStrbuf
22 * that stores the actual paragraph text, a utf8 string to store the paragraph
23 * text in utf8 (which is not used internally at all), A pointer to it's
24 * main @ref textblock_nodes_format_internal "Format Node" and the paragraph's
25 * @ref evas_bidi_props "BiDi properties". The pointer to the format node may be
26 * NULL if there's no format node anywhere before the end of the text node,
27 * not even in previous text nodes. If not NULL, it points to the first format
28 * node pointing to text inside of the text node, or if there is none, it points
29 * to the previous's text nodes format node. Each paragraph has a format node
30 * representing a paragraph separator pointing to it's last position except
31 * for the last paragraph, which has no such constraint. This constraint
32 * happens because text nodes are paragraphs and paragraphs are delimited by
33 * paragraph separators.
34 *
35 * @subsection textblock_nodes_format_internal Format Nodes - Internal
36 * Each format node stores a group of format information, for example the
37 * markup: \<font=Vera,Kochi font_size=10 align=left\> will all be inserted
38 * inside the same format node, altohugh it consists of different formatting
39 * commands.
40 * Each node has a pointer to it's text node, this pointer is NEVER NULL, even
41 * if there's only one format, and no text, a text node is created. Each format
42 * node includes an offset from the last format node of the same text node. For
43 * example, the markup "0<b>12</b>" will create two format nodes, the first
44 * having an offset of 1 and the second an offset of 2. Each format node also
45 * includes a @ref Eina_Strbuf that includes the textual representation of the
46 * format, and a boolean stating if the format is a visible format or not, see
47 * @ref textblock_nodes_format_visible
48 *
49 * @subsection textblock_nodes_format_visible Visible Format Nodes
50 * There are two types of format nodes, visible and invisible. They are the same
51 * in every way, except for the representation in the text node. While invisible
52 * format nodes have no representation in the text node, the visible ones do.
53 * The Uniceode object replacement character (0xFFFC) is inserted to every place
54 * a visible format node points to. This makes it very easy to treat visible
55 * formats as items in the text, both for BiDi purposes and cursor handling
56 * purposes.
57 * Here are a few example visible an invisible formats:
58 * Visible: newline char, tab, paragraph separator and an embedded item.
59 * Invisible: setting the color, font or alignment of the text.
60 *
61 * @subsection textblock_layout The layout system
62 * @todo write @ref textblock_layout
63 */
64#include <stdlib.h>
65
66#include "evas_common.h"
67#include "evas_private.h"
68
69#ifdef HAVE_LINEBREAK
70#include "linebreak.h"
71#endif
72
73/* save typing */
74#define ENFN obj->layer->evas->engine.func
75#define ENDT obj->layer->evas->engine.data.output
76
77/* private magic number for textblock objects */
78static const char o_type[] = "textblock";
79
80/* The char to be inserted instead of visible formats */
81#define EVAS_TEXTBLOCK_REPLACEMENT_CHAR 0xFFFC
82#define _PARAGRAPH_SEPARATOR 0x2029
83#define EVAS_TEXTBLOCK_IS_VISIBLE_FORMAT_CHAR(ch) \
84 (((ch) == EVAS_TEXTBLOCK_REPLACEMENT_CHAR) || \
85 ((ch) == '\n') || \
86 ((ch) == '\t') || \
87 ((ch) == _PARAGRAPH_SEPARATOR))
88
89/* private struct for textblock object internal data */
90/**
91 * @internal
92 * @typedef Evas_Object_Textblock
93 * The actual textblock object.
94 */
95typedef struct _Evas_Object_Textblock Evas_Object_Textblock;
96/**
97 * @internal
98 * @typedef Evas_Object_Style_Tag
99 * The structure used for finding style tags.
100 */
101typedef struct _Evas_Object_Style_Tag Evas_Object_Style_Tag;
102/**
103 * @internal
104 * @typedef Evas_Object_Textblock_Node_Text
105 * A text node.
106 */
107typedef struct _Evas_Object_Textblock_Node_Text Evas_Object_Textblock_Node_Text;
108/*
109 * Defined in Evas.h
110typedef struct _Evas_Object_Textblock_Node_Format Evas_Object_Textblock_Node_Format;
111*/
112
113/**
114 * @internal
115 * @typedef Evas_Object_Textblock_Paragraph
116 * A layouting paragraph.
117 */
118typedef struct _Evas_Object_Textblock_Paragraph Evas_Object_Textblock_Paragraph;
119/**
120 * @internal
121 * @typedef Evas_Object_Textblock_Line
122 * A layouting line.
123 */
124typedef struct _Evas_Object_Textblock_Line Evas_Object_Textblock_Line;
125/**
126 * @internal
127 * @typedef Evas_Object_Textblock_Item
128 * A layouting item.
129 */
130typedef struct _Evas_Object_Textblock_Item Evas_Object_Textblock_Item;
131/**
132 * @internal
133 * @typedef Evas_Object_Textblock_Item
134 * A layouting text item.
135 */
136typedef struct _Evas_Object_Textblock_Text_Item Evas_Object_Textblock_Text_Item;
137/**
138 * @internal
139 * @typedef Evas_Object_Textblock_Format_Item
140 * A layouting format item.
141 */
142typedef struct _Evas_Object_Textblock_Format_Item Evas_Object_Textblock_Format_Item;
143/**
144 * @internal
145 * @typedef Evas_Object_Textblock_Format
146 * A textblock format.
147 */
148typedef struct _Evas_Object_Textblock_Format Evas_Object_Textblock_Format;
149
150/**
151 * @internal
152 * @def IS_AT_END(ti, ind)
153 * Return true if ind is at the end of the text item, false otherwise.
154 */
155#define IS_AT_END(ti, ind) (ind == ti->text_props.text_len)
156
157/**
158 * @internal
159 * @def MOVE_PREV_UNTIL(limit, ind)
160 * This decrements ind as long as ind > limit.
161 */
162#define MOVE_PREV_UNTIL(limit, ind) \
163 do \
164 { \
165 if ((limit) < (ind)) \
166 (ind)--; \
167 } \
168 while (0)
169
170/**
171 * @internal
172 * @def MOVE_NEXT_UNTIL(limit, ind)
173 * This increments ind as long as ind < limit
174 */
175#define MOVE_NEXT_UNTIL(limit, ind) \
176 do \
177 { \
178 if ((ind) < (limit)) \
179 (ind)++; \
180 } \
181 while (0)
182
183/**
184 * @internal
185 * @def GET_ITEM_TEXT(ti)
186 * Returns a const reference to the text of the ti (not null terminated).
187 */
188#define GET_ITEM_TEXT(ti) \
189 (((ti)->parent.text_node) ? \
190 (eina_ustrbuf_string_get((ti)->parent.text_node->unicode) + \
191 (ti)->parent.text_pos) : EINA_UNICODE_EMPTY_STRING)
192/**
193 * @internal
194 * @def _FORMAT_IS_CLOSER_OF(base, closer, closer_len)
195 * Returns true if closer is the closer of base.
196 */
197#define _FORMAT_IS_CLOSER_OF(base, closer, closer_len) \
198 (!strncmp(base + 1, closer, closer_len) && \
199 (!base[closer_len + 1] || \
200 (base[closer_len + 1] == '=') || \
201 _is_white(base[closer_len + 1])))
202
203/*FIXME: document the structs and struct items. */
204struct _Evas_Object_Style_Tag
205{
206 EINA_INLIST;
207 char *tag;
208 char *replace;
209 size_t tag_len;
210 size_t replace_len;
211};
212
213struct _Evas_Object_Textblock_Node_Text
214{
215 EINA_INLIST;
216 Eina_UStrbuf *unicode;
217 char *utf8;
218 Evas_Object_Textblock_Node_Format *format_node;
219 Evas_Object_Textblock_Paragraph *par;
220 Eina_Bool dirty : 1;
221 Eina_Bool is_new : 1;
222};
223
224struct _Evas_Object_Textblock_Node_Format
225{
226 EINA_INLIST;
227 const char *format;
228 const char *orig_format;
229 Evas_Object_Textblock_Node_Text *text_node;
230 size_t offset;
231 unsigned char anchor : 2;
232 Eina_Bool visible : 1;
233 Eina_Bool format_change : 1;
234 Eina_Bool is_new : 1;
235};
236
237#define ANCHOR_NONE 0
238#define ANCHOR_A 1
239#define ANCHOR_ITEM 2
240
241/**
242 * @internal
243 * @def _NODE_TEXT(x)
244 * A convinience macro for casting to a text node.
245 */
246#define _NODE_TEXT(x) ((Evas_Object_Textblock_Node_Text *) (x))
247/**
248 * @internal
249 * @def _NODE_FORMAT(x)
250 * A convinience macro for casting to a format node.
251 */
252#define _NODE_FORMAT(x) ((Evas_Object_Textblock_Node_Format *) (x))
253/**
254 * @internal
255 * @def _ITEM(x)
256 * A convinience macro for casting to a generic item.
257 */
258#define _ITEM(x) ((Evas_Object_Textblock_Item *) (x))
259/**
260 * @internal
261 * @def _ITEM_TEXT(x)
262 * A convinience macro for casting to a text item.
263 */
264#define _ITEM_TEXT(x) ((Evas_Object_Textblock_Text_Item *) (x))
265/**
266 * @internal
267 * @def _ITEM_FORMAT(x)
268 * A convinience macro for casting to a format item.
269 */
270#define _ITEM_FORMAT(x) ((Evas_Object_Textblock_Format_Item *) (x))
271
272struct _Evas_Object_Textblock_Paragraph
273{
274 EINA_INLIST;
275 Evas_Object_Textblock_Line *lines;
276 Evas_Object_Textblock_Node_Text *text_node;
277 Eina_List *logical_items;
278 Evas_BiDi_Paragraph_Props *bidi_props; /* Only valid during layout */
279 Evas_BiDi_Direction direction;
280 Evas_Coord y, w, h;
281 int line_no;
282 Eina_Bool is_bidi : 1;
283 Eina_Bool visible : 1;
284 Eina_Bool rendered : 1;
285};
286
287struct _Evas_Object_Textblock_Line
288{
289 EINA_INLIST;
290 Evas_Object_Textblock_Item *items;
291 Evas_Object_Textblock_Paragraph *par;
292 Evas_Coord x, y, w, h;
293 int baseline;
294 int line_no;
295};
296
297typedef enum _Evas_Textblock_Item_Type
298{
299 EVAS_TEXTBLOCK_ITEM_TEXT,
300 EVAS_TEXTBLOCK_ITEM_FORMAT,
301} Evas_Textblock_Item_Type;
302
303struct _Evas_Object_Textblock_Item
304{
305 EINA_INLIST;
306 Evas_Textblock_Item_Type type;
307 Evas_Object_Textblock_Node_Text *text_node;
308 Evas_Object_Textblock_Format *format;
309 size_t text_pos;
310#ifdef BIDI_SUPPORT
311 size_t visual_pos;
312#endif
313 Evas_Coord adv, x, w, h;
314 Eina_Bool merge : 1; /* Indicates whether this
315 item should merge to the
316 previous item or not */
317 Eina_Bool visually_deleted : 1;
318 /* Indicates whether this
319 item is used in the visual
320 layout or not. */
321};
322
323struct _Evas_Object_Textblock_Text_Item
324{
325 Evas_Object_Textblock_Item parent;
326 Evas_Text_Props text_props;
327 Evas_Coord inset;
328 Evas_Coord x_adjustment; /* Used to indicate by how
329 much we adjusted sizes */
330};
331
332struct _Evas_Object_Textblock_Format_Item
333{
334 Evas_Object_Textblock_Item parent;
335 Evas_BiDi_Direction bidi_dir;
336 const char *item;
337 int y;
338 unsigned char vsize : 2;
339 unsigned char size : 2;
340 Eina_Bool formatme : 1;
341};
342
343struct _Evas_Object_Textblock_Format
344{
345 Evas_Object_Textblock_Node_Format *fnode;
346 double halign;
347 double valign;
348 struct {
349 Evas_Font_Description *fdesc;
350 const char *source;
351 Evas_Font_Set *font;
352 Evas_Font_Size size;
353 } font;
354 struct {
355 struct {
356 unsigned char r, g, b, a;
357 } normal, underline, underline2, underline_dash, outline, shadow, glow, glow2, backing,
358 strikethrough;
359 } color;
360 struct {
361 int l, r;
362 } margin;
363 int ref;
364 int tabstops;
365 int linesize;
366 int linegap;
367 int underline_dash_width;
368 int underline_dash_gap;
369 double linerelsize;
370 double linerelgap;
371 double linefill;
372 double ellipsis;
373 unsigned char style;
374 Eina_Bool wrap_word : 1;
375 Eina_Bool wrap_char : 1;
376 Eina_Bool wrap_mixed : 1;
377 Eina_Bool underline : 1;
378 Eina_Bool underline2 : 1;
379 Eina_Bool underline_dash : 1;
380 Eina_Bool strikethrough : 1;
381 Eina_Bool backing : 1;
382 Eina_Bool password : 1;
383 Eina_Bool halign_auto : 1;
384};
385
386struct _Evas_Textblock_Style
387{
388 const char *style_text;
389 char *default_tag;
390 Evas_Object_Style_Tag *tags;
391 Eina_List *objects;
392 Eina_Bool delete_me : 1;
393};
394
395struct _Evas_Textblock_Cursor
396{
397 Evas_Object *obj;
398 size_t pos;
399 Evas_Object_Textblock_Node_Text *node;
400};
401
402/* Size of the index array */
403#define TEXTBLOCK_PAR_INDEX_SIZE 10
404struct _Evas_Object_Textblock
405{
406 DATA32 magic;
407 Evas_Textblock_Style *style;
408 Evas_Textblock_Cursor *cursor;
409 Eina_List *cursors;
410 Evas_Object_Textblock_Node_Text *text_nodes;
411 Evas_Object_Textblock_Node_Format *format_nodes;
412
413 int num_paragraphs;
414 Evas_Object_Textblock_Paragraph *paragraphs;
415 Evas_Object_Textblock_Paragraph *par_index[TEXTBLOCK_PAR_INDEX_SIZE];
416
417 Evas_Object_Textblock_Text_Item *ellip_ti;
418 Eina_List *anchors_a;
419 Eina_List *anchors_item;
420 int last_w, last_h;
421 struct {
422 int l, r, t, b;
423 } style_pad;
424 double valign;
425 char *markup_text;
426 void *engine_data;
427 const char *repch;
428 const char *bidi_delimiters;
429 struct {
430 int w, h;
431 Eina_Bool valid : 1;
432 } formatted, native;
433 Eina_Bool redraw : 1;
434 Eina_Bool changed : 1;
435 Eina_Bool content_changed : 1;
436 Eina_Bool format_changed : 1;
437 Eina_Bool have_ellipsis : 1;
438 Eina_Bool legacy_newline : 1;
439};
440
441/* private methods for textblock objects */
442static void evas_object_textblock_init(Evas_Object *obj);
443static void *evas_object_textblock_new(void);
444static void evas_object_textblock_render(Evas_Object *obj, void *output, void *context, void *surface, int x, int y);
445static void evas_object_textblock_free(Evas_Object *obj);
446static void evas_object_textblock_render_pre(Evas_Object *obj);
447static void evas_object_textblock_render_post(Evas_Object *obj);
448
449static unsigned int evas_object_textblock_id_get(Evas_Object *obj);
450static unsigned int evas_object_textblock_visual_id_get(Evas_Object *obj);
451static void *evas_object_textblock_engine_data_get(Evas_Object *obj);
452
453static int evas_object_textblock_is_opaque(Evas_Object *obj);
454static int evas_object_textblock_was_opaque(Evas_Object *obj);
455
456static void evas_object_textblock_coords_recalc(Evas_Object *obj);
457
458static void evas_object_textblock_scale_update(Evas_Object *obj);
459
460static const Evas_Object_Func object_func =
461{
462 /* methods (compulsory) */
463 evas_object_textblock_free,
464 evas_object_textblock_render,
465 evas_object_textblock_render_pre,
466 evas_object_textblock_render_post,
467 evas_object_textblock_id_get,
468 evas_object_textblock_visual_id_get,
469 evas_object_textblock_engine_data_get,
470 /* these are optional. NULL = nothing */
471 NULL,
472 NULL,
473 NULL,
474 NULL,
475 evas_object_textblock_is_opaque,
476 evas_object_textblock_was_opaque,
477 NULL,
478 NULL,
479 evas_object_textblock_coords_recalc,
480 evas_object_textblock_scale_update,
481 NULL,
482 NULL,
483 NULL
484};
485
486/* the actual api call to add a textblock */
487
488#define TB_HEAD() \
489 Evas_Object_Textblock *o; \
490 MAGIC_CHECK(obj, Evas_Object, MAGIC_OBJ); \
491 return; \
492 MAGIC_CHECK_END(); \
493 o = (Evas_Object_Textblock *)(obj->object_data); \
494 MAGIC_CHECK(o, Evas_Object_Textblock, MAGIC_OBJ_TEXTBLOCK); \
495 return; \
496 MAGIC_CHECK_END();
497
498#define TB_HEAD_RETURN(x) \
499 Evas_Object_Textblock *o; \
500 MAGIC_CHECK(obj, Evas_Object, MAGIC_OBJ); \
501 return (x); \
502 MAGIC_CHECK_END(); \
503 o = (Evas_Object_Textblock *)(obj->object_data); \
504 MAGIC_CHECK(o, Evas_Object_Textblock, MAGIC_OBJ_TEXTBLOCK); \
505 return (x); \
506 MAGIC_CHECK_END();
507
508
509
510static Eina_Bool _evas_textblock_cursor_is_at_the_end(const Evas_Textblock_Cursor *cur);
511static void _evas_textblock_node_text_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n);
512static void _evas_textblock_node_text_remove_formats_between(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n, int start, int end);
513static Evas_Object_Textblock_Node_Format *_evas_textblock_cursor_node_format_before_or_at_pos_get(const Evas_Textblock_Cursor *cur);
514static size_t _evas_textblock_node_format_pos_get(const Evas_Object_Textblock_Node_Format *fmt);
515static void _evas_textblock_node_format_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n, int visual_adjustment);
516static void _evas_textblock_node_format_free(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n);
517static void _evas_textblock_node_text_free(Evas_Object_Textblock_Node_Text *n);
518static void _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj);
519static void _evas_textblock_invalidate_all(Evas_Object_Textblock *o);
520static void _evas_textblock_cursors_update_offset(const Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Text *n, size_t start, int offset);
521static void _evas_textblock_cursors_set_node(Evas_Object_Textblock *o, const Evas_Object_Textblock_Node_Text *n, Evas_Object_Textblock_Node_Text *new_node);
522
523/* styles */
524/**
525 * @internal
526 * Clears the textblock style passed except for the style_text which is replaced.
527 * @param ts The ts to be cleared. Must not be NULL.
528 * @param style_text the style's text.
529 */
530static void
531_style_replace(Evas_Textblock_Style *ts, const char *style_text)
532{
533 eina_stringshare_replace(&ts->style_text, style_text);
534 if (ts->default_tag) free(ts->default_tag);
535 while (ts->tags)
536 {
537 Evas_Object_Style_Tag *tag;
538
539 tag = (Evas_Object_Style_Tag *)ts->tags;
540 ts->tags = (Evas_Object_Style_Tag *)eina_inlist_remove(EINA_INLIST_GET(ts->tags), EINA_INLIST_GET(tag));
541 free(tag->tag);
542 free(tag->replace);
543 free(tag);
544 }
545 ts->default_tag = NULL;
546 ts->tags = NULL;
547}
548
549/**
550 * @internal
551 * Clears the textblock style passed.
552 * @param ts The ts to be cleared. Must not be NULL.
553 */
554static void
555_style_clear(Evas_Textblock_Style *ts)
556{
557 _style_replace(ts, NULL);
558}
559
560/**
561 * @internal
562 * Searches inside the tags stored in the style for the tag matching s.
563 * @param ts The ts to be cleared. Must not be NULL.
564 * @param s The tag to be matched.
565 * @param tag_len the length of the tag string.
566 * @param[out] replace_len The length of the replcaement found. - Must not be NULL.
567 * @return The replacement string found.
568 */
569static inline const char *
570_style_match_tag(Evas_Textblock_Style *ts, const char *s, size_t tag_len, size_t *replace_len)
571{
572 Evas_Object_Style_Tag *tag;
573
574 EINA_INLIST_FOREACH(ts->tags, tag)
575 {
576 if (tag->tag_len != tag_len) continue;
577 if (!strncmp(tag->tag, s, tag_len))
578 {
579 *replace_len = tag->replace_len;
580 return tag->replace;
581 }
582 }
583 *replace_len = 0;
584 return NULL;
585}
586
587/**
588 * @internal
589 * Clears all the nodes (text and format) of the textblock object.
590 * @param obj The evas object, must not be NULL.
591 */
592static void
593_nodes_clear(const Evas_Object *obj)
594{
595 Evas_Object_Textblock *o;
596
597 o = (Evas_Object_Textblock *)(obj->object_data);
598 while (o->text_nodes)
599 {
600 Evas_Object_Textblock_Node_Text *n;
601
602 n = o->text_nodes;
603 o->text_nodes = _NODE_TEXT(eina_inlist_remove(
604 EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n)));
605 _evas_textblock_node_text_free(n);
606 }
607 while (o->format_nodes)
608 {
609 Evas_Object_Textblock_Node_Format *n;
610
611 n = o->format_nodes;
612 o->format_nodes = _NODE_FORMAT(eina_inlist_remove(EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n)));
613 _evas_textblock_node_format_free(o, n);
614 }
615}
616
617/**
618 * @internal
619 * Unrefs and frees (if needed) a textblock format.
620 * @param obj The Evas_Object, Must not be NULL.
621 * @param fmt the format to be cleaned, must not be NULL.
622 */
623static void
624_format_unref_free(const Evas_Object *obj, Evas_Object_Textblock_Format *fmt)
625{
626 fmt->ref--;
627 if (fmt->ref > 0) return;
628 if (fmt->font.fdesc) evas_font_desc_unref(fmt->font.fdesc);
629 if (fmt->font.source) eina_stringshare_del(fmt->font.source);
630 evas_font_free(obj->layer->evas, fmt->font.font);
631 free(fmt);
632}
633
634/**
635 * @internal
636 * Free a layout item
637 * @param obj The evas object, must not be NULL.
638 * @param ln the layout line on which the item is in, must not be NULL.
639 * @param it the layout item to be freed
640 */
641static void
642_item_free(const Evas_Object *obj, Evas_Object_Textblock_Line *ln, Evas_Object_Textblock_Item *it)
643{
644 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
645 {
646 Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it);
647
648 evas_common_text_props_content_unref(&ti->text_props);
649 }
650 else
651 {
652 Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it);
653
654 if (fi->item) eina_stringshare_del(fi->item);
655 }
656 _format_unref_free(obj, it->format);
657 if (ln)
658 {
659 ln->items = (Evas_Object_Textblock_Item *) eina_inlist_remove(
660 EINA_INLIST_GET(ln->items), EINA_INLIST_GET(ln->items));
661 }
662 free(it);
663}
664
665/**
666 * @internal
667 * Free a layout line.
668 * @param obj The evas object, must not be NULL.
669 * @param ln the layout line to be freed, must not be NULL.
670 */
671static void
672_line_free(Evas_Object_Textblock_Line *ln)
673{
674 /* Items are freed from the logical list, except for the ellip item */
675 if (ln) free(ln);
676}
677
678/* table of html escapes (that i can find) this should be ordered with the
679 * most common first as it's a linear search to match - no hash for this.
680 *
681 * these are stored as one large string and one additional array that
682 * contains the offsets to the tokens for space efficiency.
683 */
684/**
685 * @internal
686 * @var escape_strings[]
687 * This string consists of NULL terminated pairs of strings, the first of
688 * every pair is an escape and the second is the value of the escape.
689 */
690static const char escape_strings[] =
691/* most common escaped stuff */
692"&quot;\0" "\x22\0"
693"&amp;\0" "\x26\0"
694"&lt;\0" "\x3c\0"
695"&gt;\0" "\x3e\0"
696/* all the rest */
697"&nbsp;\0" "\xc2\xa0\0"
698"&iexcl;\0" "\xc2\xa1\0"
699"&cent;\0" "\xc2\xa2\0"
700"&pound;\0" "\xc2\xa3\0"
701"&curren;\0" "\xc2\xa4\0"
702"&yen;\0" "\xc2\xa5\0"
703"&brvbar;\0" "\xc2\xa6\0"
704"&sect;\0" "\xc2\xa7\0"
705"&uml;\0" "\xc2\xa8\0"
706"&copy;\0" "\xc2\xa9\0"
707"&ordf;\0" "\xc2\xaa\0"
708"&laquo;\0" "\xc2\xab\0"
709"&not;\0" "\xc2\xac\0"
710"&reg;\0" "\xc2\xae\0"
711"&macr;\0" "\xc2\xaf\0"
712"&deg;\0" "\xc2\xb0\0"
713"&plusmn;\0" "\xc2\xb1\0"
714"&sup2;\0" "\xc2\xb2\0"
715"&sup3;\0" "\xc2\xb3\0"
716"&acute;\0" "\xc2\xb4\0"
717"&micro;\0" "\xc2\xb5\0"
718"&para;\0" "\xc2\xb6\0"
719"&middot;\0" "\xc2\xb7\0"
720"&cedil;\0" "\xc2\xb8\0"
721"&sup1;\0" "\xc2\xb9\0"
722"&ordm;\0" "\xc2\xba\0"
723"&raquo;\0" "\xc2\xbb\0"
724"&frac14;\0" "\xc2\xbc\0"
725"&frac12;\0" "\xc2\xbd\0"
726"&frac34;\0" "\xc2\xbe\0"
727"&iquest;\0" "\xc2\xbf\0"
728"&Agrave;\0" "\xc3\x80\0"
729"&Aacute;\0" "\xc3\x81\0"
730"&Acirc;\0" "\xc3\x82\0"
731"&Atilde;\0" "\xc3\x83\0"
732"&Auml;\0" "\xc3\x84\0"
733"&Aring;\0" "\xc3\x85\0"
734"&Aelig;\0" "\xc3\x86\0"
735"&Ccedil;\0" "\xc3\x87\0"
736"&Egrave;\0" "\xc3\x88\0"
737"&Eacute;\0" "\xc3\x89\0"
738"&Ecirc;\0" "\xc3\x8a\0"
739"&Euml;\0" "\xc3\x8b\0"
740"&Igrave;\0" "\xc3\x8c\0"
741"&Iacute;\0" "\xc3\x8d\0"
742"&Icirc;\0" "\xc3\x8e\0"
743"&Iuml;\0" "\xc3\x8f\0"
744"&Eth;\0" "\xc3\x90\0"
745"&Ntilde;\0" "\xc3\x91\0"
746"&Ograve;\0" "\xc3\x92\0"
747"&Oacute;\0" "\xc3\x93\0"
748"&Ocirc;\0" "\xc3\x94\0"
749"&Otilde;\0" "\xc3\x95\0"
750"&Ouml;\0" "\xc3\x96\0"
751"&times;\0" "\xc3\x97\0"
752"&Oslash;\0" "\xc3\x98\0"
753"&Ugrave;\0" "\xc3\x99\0"
754"&Uacute;\0" "\xc3\x9a\0"
755"&Ucirc;\0" "\xc3\x9b\0"
756"&Yacute;\0" "\xc3\x9d\0"
757"&Thorn;\0" "\xc3\x9e\0"
758"&szlig;\0" "\xc3\x9f\0"
759"&agrave;\0" "\xc3\xa0\0"
760"&aacute;\0" "\xc3\xa1\0"
761"&acirc;\0" "\xc3\xa2\0"
762"&atilde;\0" "\xc3\xa3\0"
763"&auml;\0" "\xc3\xa4\0"
764"&aring;\0" "\xc3\xa5\0"
765"&aelig;\0" "\xc3\xa6\0"
766"&ccedil;\0" "\xc3\xa7\0"
767"&egrave;\0" "\xc3\xa8\0"
768"&eacute;\0" "\xc3\xa9\0"
769"&ecirc;\0" "\xc3\xaa\0"
770"&euml;\0" "\xc3\xab\0"
771"&igrave;\0" "\xc3\xac\0"
772"&iacute;\0" "\xc3\xad\0"
773"&icirc;\0" "\xc3\xae\0"
774"&iuml;\0" "\xc3\xaf\0"
775"&eth;\0" "\xc3\xb0\0"
776"&ntilde;\0" "\xc3\xb1\0"
777"&ograve;\0" "\xc3\xb2\0"
778"&oacute;\0" "\xc3\xb3\0"
779"&ocirc;\0" "\xc3\xb4\0"
780"&otilde;\0" "\xc3\xb5\0"
781"&ouml;\0" "\xc3\xb6\0"
782"&divide;\0" "\xc3\xb7\0"
783"&oslash;\0" "\xc3\xb8\0"
784"&ugrave;\0" "\xc3\xb9\0"
785"&uacute;\0" "\xc3\xba\0"
786"&ucirc;\0" "\xc3\xbb\0"
787"&uuml;\0" "\xc3\xbc\0"
788"&yacute;\0" "\xc3\xbd\0"
789"&thorn;\0" "\xc3\xbe\0"
790"&yuml;\0" "\xc3\xbf\0"
791"&alpha;\0" "\xce\x91\0"
792"&beta;\0" "\xce\x92\0"
793"&gamma;\0" "\xce\x93\0"
794"&delta;\0" "\xce\x94\0"
795"&epsilon;\0" "\xce\x95\0"
796"&zeta;\0" "\xce\x96\0"
797"&eta;\0" "\xce\x97\0"
798"&theta;\0" "\xce\x98\0"
799"&iota;\0" "\xce\x99\0"
800"&kappa;\0" "\xce\x9a\0"
801"&lambda;\0" "\xce\x9b\0"
802"&mu;\0" "\xce\x9c\0"
803"&nu;\0" "\xce\x9d\0"
804"&xi;\0" "\xce\x9e\0"
805"&omicron;\0" "\xce\x9f\0"
806"&pi;\0" "\xce\xa0\0"
807"&rho;\0" "\xce\xa1\0"
808"&sigma;\0" "\xce\xa3\0"
809"&tau;\0" "\xce\xa4\0"
810"&upsilon;\0" "\xce\xa5\0"
811"&phi;\0" "\xce\xa6\0"
812"&chi;\0" "\xce\xa7\0"
813"&psi;\0" "\xce\xa8\0"
814"&omega;\0" "\xce\xa9\0"
815"&hellip;\0" "\xe2\x80\xa6\0"
816"&euro;\0" "\xe2\x82\xac\0"
817"&larr;\0" "\xe2\x86\x90\0"
818"&uarr;\0" "\xe2\x86\x91\0"
819"&rarr;\0" "\xe2\x86\x92\0"
820"&darr;\0" "\xe2\x86\x93\0"
821"&harr;\0" "\xe2\x86\x94\0"
822"&larr;\0" "\xe2\x87\x90\0"
823"&rarr;\0" "\xe2\x87\x92\0"
824"&forall;\0" "\xe2\x88\x80\0"
825"&exist;\0" "\xe2\x88\x83\0"
826"&nabla;\0" "\xe2\x88\x87\0"
827"&prod;\0" "\xe2\x88\x8f\0"
828"&sum;\0" "\xe2\x88\x91\0"
829"&and;\0" "\xe2\x88\xa7\0"
830"&or;\0" "\xe2\x88\xa8\0"
831"&int;\0" "\xe2\x88\xab\0"
832"&ne;\0" "\xe2\x89\xa0\0"
833"&equiv;\0" "\xe2\x89\xa1\0"
834"&oplus;\0" "\xe2\x8a\x95\0"
835"&perp;\0" "\xe2\x8a\xa5\0"
836"&dagger;\0" "\xe2\x80\xa0\0"
837"&Dagger;\0" "\xe2\x80\xa1\0"
838"&bull;\0" "\xe2\x80\xa2\0"
839;
840
841EVAS_MEMPOOL(_mp_obj);
842
843/**
844 * @internal
845 * Checks if a char is a whitespace.
846 * @param c the unicode codepoint.
847 * @return EINA_TRUE if the unicode codepoint is a whitespace, EINA_FALSE otherwise.
848 */
849static Eina_Bool
850_is_white(Eina_Unicode c)
851{
852 /*
853 * unicode list of whitespace chars
854 *
855 * 0009..000D <control-0009>..<control-000D>
856 * 0020 SPACE
857 * 0085 <control-0085>
858 * 00A0 NO-BREAK SPACE
859 * 1680 OGHAM SPACE MARK
860 * 180E MONGOLIAN VOWEL SEPARATOR
861 * 2000..200A EN QUAD..HAIR SPACE
862 * 2028 LINE SEPARATOR
863 * 2029 PARAGRAPH SEPARATOR
864 * 202F NARROW NO-BREAK SPACE
865 * 205F MEDIUM MATHEMATICAL SPACE
866 * 3000 IDEOGRAPHIC SPACE
867 */
868 if (
869 (c == 0x20) ||
870 ((c >= 0x9) && (c <= 0xd)) ||
871 (c == 0x85) ||
872 (c == 0xa0) ||
873 (c == 0x1680) ||
874 (c == 0x180e) ||
875 ((c >= 0x2000) && (c <= 0x200a)) ||
876 (c == 0x2028) ||
877 (c == 0x2029) ||
878 (c == 0x202f) ||
879 (c == 0x205f) ||
880 (c == 0x3000)
881 )
882 return EINA_TRUE;
883 return EINA_FALSE;
884}
885
886/**
887 * @internal
888 * Prepends the text between s and p to the main cursor of the object.
889 *
890 * @param cur the cursor to prepend to.
891 * @param[in] s start of the string
892 * @param[in] p end of the string
893 */
894static void
895_prepend_text_run(Evas_Textblock_Cursor *cur, const char *s, const char *p)
896{
897 if ((s) && (p > s))
898 {
899 char *ts;
900
901 ts = alloca(p - s + 1);
902 strncpy(ts, s, p - s);
903 ts[p - s] = 0;
904 evas_textblock_cursor_text_prepend(cur, ts);
905 }
906}
907
908
909/**
910 * @internal
911 * Returns the numeric value of HEX chars for example for ch = 'A'
912 * the function will return 10.
913 *
914 * @param ch The HEX char.
915 * @return numeric value of HEX.
916 */
917static int
918_hex_string_get(char ch)
919{
920 if ((ch >= '0') && (ch <= '9')) return (ch - '0');
921 else if ((ch >= 'A') && (ch <= 'F')) return (ch - 'A' + 10);
922 else if ((ch >= 'a') && (ch <= 'f')) return (ch - 'a' + 10);
923 return 0;
924}
925
926/**
927 * @internal
928 * Parses a string of one of the formas:
929 * 1. "#RRGGBB"
930 * 2. "#RRGGBBAA"
931 * 3. "#RGB"
932 * 4. "#RGBA"
933 * To the rgba values.
934 *
935 * @param[in] str The string to parse - NOT NULL.
936 * @param[out] r The Red value - NOT NULL.
937 * @param[out] g The Green value - NOT NULL.
938 * @param[out] b The Blue value - NOT NULL.
939 * @param[out] a The Alpha value - NOT NULL.
940 */
941static void
942_format_color_parse(const char *str, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a)
943{
944 int slen;
945
946 slen = strlen(str);
947 *r = *g = *b = *a = 0;
948
949 if (slen == 7) /* #RRGGBB */
950 {
951 *r = (_hex_string_get(str[1]) << 4) | (_hex_string_get(str[2]));
952 *g = (_hex_string_get(str[3]) << 4) | (_hex_string_get(str[4]));
953 *b = (_hex_string_get(str[5]) << 4) | (_hex_string_get(str[6]));
954 *a = 0xff;
955 }
956 else if (slen == 9) /* #RRGGBBAA */
957 {
958 *r = (_hex_string_get(str[1]) << 4) | (_hex_string_get(str[2]));
959 *g = (_hex_string_get(str[3]) << 4) | (_hex_string_get(str[4]));
960 *b = (_hex_string_get(str[5]) << 4) | (_hex_string_get(str[6]));
961 *a = (_hex_string_get(str[7]) << 4) | (_hex_string_get(str[8]));
962 }
963 else if (slen == 4) /* #RGB */
964 {
965 *r = _hex_string_get(str[1]);
966 *r = (*r << 4) | *r;
967 *g = _hex_string_get(str[2]);
968 *g = (*g << 4) | *g;
969 *b = _hex_string_get(str[3]);
970 *b = (*b << 4) | *b;
971 *a = 0xff;
972 }
973 else if (slen == 5) /* #RGBA */
974 {
975 *r = _hex_string_get(str[1]);
976 *r = (*r << 4) | *r;
977 *g = _hex_string_get(str[2]);
978 *g = (*g << 4) | *g;
979 *b = _hex_string_get(str[3]);
980 *b = (*b << 4) | *b;
981 *a = _hex_string_get(str[4]);
982 *a = (*a << 4) | *a;
983 }
984 *r = (*r * *a) / 255;
985 *g = (*g * *a) / 255;
986 *b = (*b * *a) / 255;
987}
988
989/* The refcount for the formats. */
990static int format_refcount = 0;
991/* Holders for the stringshares */
992static const char *fontstr = NULL;
993static const char *font_fallbacksstr = NULL;
994static const char *font_sizestr = NULL;
995static const char *font_sourcestr = NULL;
996static const char *font_weightstr = NULL;
997static const char *font_stylestr = NULL;
998static const char *font_widthstr = NULL;
999static const char *langstr = NULL;
1000static const char *colorstr = NULL;
1001static const char *underline_colorstr = NULL;
1002static const char *underline2_colorstr = NULL;
1003static const char *underline_dash_colorstr = NULL;
1004static const char *outline_colorstr = NULL;
1005static const char *shadow_colorstr = NULL;
1006static const char *glow_colorstr = NULL;
1007static const char *glow2_colorstr = NULL;
1008static const char *backing_colorstr = NULL;
1009static const char *strikethrough_colorstr = NULL;
1010static const char *alignstr = NULL;
1011static const char *valignstr = NULL;
1012static const char *wrapstr = NULL;
1013static const char *left_marginstr = NULL;
1014static const char *right_marginstr = NULL;
1015static const char *underlinestr = NULL;
1016static const char *strikethroughstr = NULL;
1017static const char *backingstr = NULL;
1018static const char *stylestr = NULL;
1019static const char *tabstopsstr = NULL;
1020static const char *linesizestr = NULL;
1021static const char *linerelsizestr = NULL;
1022static const char *linegapstr = NULL;
1023static const char *linerelgapstr = NULL;
1024static const char *itemstr = NULL;
1025static const char *linefillstr = NULL;
1026static const char *ellipsisstr = NULL;
1027static const char *passwordstr = NULL;
1028static const char *underline_dash_widthstr = NULL;
1029static const char *underline_dash_gapstr = NULL;
1030
1031/**
1032 * @internal
1033 * Init the format strings.
1034 */
1035static void
1036_format_command_init(void)
1037{
1038 if (format_refcount == 0)
1039 {
1040 fontstr = eina_stringshare_add("font");
1041 font_fallbacksstr = eina_stringshare_add("font_fallbacks");
1042 font_sizestr = eina_stringshare_add("font_size");
1043 font_sourcestr = eina_stringshare_add("font_source");
1044 font_weightstr = eina_stringshare_add("font_weight");
1045 font_stylestr = eina_stringshare_add("font_style");
1046 font_widthstr = eina_stringshare_add("font_width");
1047 langstr = eina_stringshare_add("lang");
1048 colorstr = eina_stringshare_add("color");
1049 underline_colorstr = eina_stringshare_add("underline_color");
1050 underline2_colorstr = eina_stringshare_add("underline2_color");
1051 underline_dash_colorstr = eina_stringshare_add("underline_dash_color");
1052 outline_colorstr = eina_stringshare_add("outline_color");
1053 shadow_colorstr = eina_stringshare_add("shadow_color");
1054 glow_colorstr = eina_stringshare_add("glow_color");
1055 glow2_colorstr = eina_stringshare_add("glow2_color");
1056 backing_colorstr = eina_stringshare_add("backing_color");
1057 strikethrough_colorstr = eina_stringshare_add("strikethrough_color");
1058 alignstr = eina_stringshare_add("align");
1059 valignstr = eina_stringshare_add("valign");
1060 wrapstr = eina_stringshare_add("wrap");
1061 left_marginstr = eina_stringshare_add("left_margin");
1062 right_marginstr = eina_stringshare_add("right_margin");
1063 underlinestr = eina_stringshare_add("underline");
1064 strikethroughstr = eina_stringshare_add("strikethrough");
1065 backingstr = eina_stringshare_add("backing");
1066 stylestr = eina_stringshare_add("style");
1067 tabstopsstr = eina_stringshare_add("tabstops");
1068 linesizestr = eina_stringshare_add("linesize");
1069 linerelsizestr = eina_stringshare_add("linerelsize");
1070 linegapstr = eina_stringshare_add("linegap");
1071 linerelgapstr = eina_stringshare_add("linerelgap");
1072 itemstr = eina_stringshare_add("item");
1073 linefillstr = eina_stringshare_add("linefill");
1074 ellipsisstr = eina_stringshare_add("ellipsis");
1075 passwordstr = eina_stringshare_add("password");
1076 underline_dash_widthstr = eina_stringshare_add("underline_dash_width");
1077 underline_dash_gapstr = eina_stringshare_add("underline_dash_gap");
1078 }
1079 format_refcount++;
1080}
1081
1082/**
1083 * @internal
1084 * Shutdown the format strings.
1085 */
1086static void
1087_format_command_shutdown(void)
1088{
1089 if (--format_refcount > 0) return;
1090
1091 eina_stringshare_del(fontstr);
1092 eina_stringshare_del(font_fallbacksstr);
1093 eina_stringshare_del(font_sizestr);
1094 eina_stringshare_del(font_sourcestr);
1095 eina_stringshare_del(font_weightstr);
1096 eina_stringshare_del(font_stylestr);
1097 eina_stringshare_del(font_widthstr);
1098 eina_stringshare_del(langstr);
1099 eina_stringshare_del(colorstr);
1100 eina_stringshare_del(underline_colorstr);
1101 eina_stringshare_del(underline2_colorstr);
1102 eina_stringshare_del(underline_dash_colorstr);
1103 eina_stringshare_del(outline_colorstr);
1104 eina_stringshare_del(shadow_colorstr);
1105 eina_stringshare_del(glow_colorstr);
1106 eina_stringshare_del(glow2_colorstr);
1107 eina_stringshare_del(backing_colorstr);
1108 eina_stringshare_del(strikethrough_colorstr);
1109 eina_stringshare_del(alignstr);
1110 eina_stringshare_del(valignstr);
1111 eina_stringshare_del(wrapstr);
1112 eina_stringshare_del(left_marginstr);
1113 eina_stringshare_del(right_marginstr);
1114 eina_stringshare_del(underlinestr);
1115 eina_stringshare_del(strikethroughstr);
1116 eina_stringshare_del(backingstr);
1117 eina_stringshare_del(stylestr);
1118 eina_stringshare_del(tabstopsstr);
1119 eina_stringshare_del(linesizestr);
1120 eina_stringshare_del(linerelsizestr);
1121 eina_stringshare_del(linegapstr);
1122 eina_stringshare_del(linerelgapstr);
1123 eina_stringshare_del(itemstr);
1124 eina_stringshare_del(linefillstr);
1125 eina_stringshare_del(ellipsisstr);
1126 eina_stringshare_del(passwordstr);
1127 eina_stringshare_del(underline_dash_widthstr);
1128 eina_stringshare_del(underline_dash_gapstr);
1129}
1130
1131/**
1132 * @internal
1133 * Copies str to dst while removing the \\ char, i.e unescape the escape sequences.
1134 *
1135 * @param[out] dst the destination string - Should not be NULL.
1136 * @param[in] src the source string - Should not be NULL.
1137 */
1138static void
1139_format_clean_param(char *dst, const char *src)
1140{
1141 const char *ss;
1142 char *ds;
1143
1144 ds = dst;
1145 for (ss = src; *ss; ss++, ds++)
1146 {
1147 if ((*ss == '\\') && *(ss + 1)) ss++;
1148 *ds = *ss;
1149 }
1150 *ds = 0;
1151}
1152
1153/**
1154 * @internal
1155 * Parses the cmd and parameter and adds the parsed format to fmt.
1156 *
1157 * @param obj the evas object - should not be NULL.
1158 * @param fmt The format to populate - should not be NULL.
1159 * @param[in] cmd the command to process, should be stringshared.
1160 * @param[in] param the parameter of the command.
1161 */
1162static void
1163_format_command(Evas_Object *obj, Evas_Object_Textblock_Format *fmt, const char *cmd, const char *param)
1164{
1165 int len;
1166 char *tmp_param;
1167
1168 len = strlen(param);
1169 tmp_param = alloca(len + 1);
1170
1171 _format_clean_param(tmp_param, param);
1172
1173 /* If we are changing the font, create the fdesc. */
1174 if ((cmd == font_weightstr) || (cmd == font_widthstr) ||
1175 (cmd == font_stylestr) || (cmd == langstr) ||
1176 (cmd == fontstr) || (cmd == font_fallbacksstr))
1177 {
1178 if (!fmt->font.fdesc)
1179 {
1180 fmt->font.fdesc = evas_font_desc_new();
1181 }
1182 else if (!fmt->font.fdesc->is_new)
1183 {
1184 Evas_Font_Description *old = fmt->font.fdesc;
1185 fmt->font.fdesc = evas_font_desc_dup(fmt->font.fdesc);
1186 if (old) evas_font_desc_unref(old);
1187 }
1188 }
1189
1190
1191 if (cmd == fontstr)
1192 {
1193 evas_font_name_parse(fmt->font.fdesc, tmp_param);
1194 }
1195 else if (cmd == font_fallbacksstr)
1196 {
1197 eina_stringshare_replace(&(fmt->font.fdesc->fallbacks), tmp_param);
1198 }
1199 else if (cmd == font_sizestr)
1200 {
1201 int v;
1202
1203 v = atoi(tmp_param);
1204 if (v != fmt->font.size)
1205 {
1206 fmt->font.size = v;
1207 }
1208 }
1209 else if (cmd == font_sourcestr)
1210 {
1211 if ((!fmt->font.source) ||
1212 ((fmt->font.source) && (strcmp(fmt->font.source, tmp_param))))
1213 {
1214 if (fmt->font.source) eina_stringshare_del(fmt->font.source);
1215 fmt->font.source = eina_stringshare_add(tmp_param);
1216 }
1217 }
1218 else if (cmd == font_weightstr)
1219 {
1220 fmt->font.fdesc->weight = evas_font_style_find(tmp_param,
1221 tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_WEIGHT);
1222 }
1223 else if (cmd == font_stylestr)
1224 {
1225 fmt->font.fdesc->slant = evas_font_style_find(tmp_param,
1226 tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_SLANT);
1227 }
1228 else if (cmd == font_widthstr)
1229 {
1230 fmt->font.fdesc->width = evas_font_style_find(tmp_param,
1231 tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_WIDTH);
1232 }
1233 else if (cmd == langstr)
1234 {
1235 eina_stringshare_replace(&(fmt->font.fdesc->lang), tmp_param);
1236 }
1237 else if (cmd == colorstr)
1238 _format_color_parse(tmp_param,
1239 &(fmt->color.normal.r), &(fmt->color.normal.g),
1240 &(fmt->color.normal.b), &(fmt->color.normal.a));
1241 else if (cmd == underline_colorstr)
1242 _format_color_parse(tmp_param,
1243 &(fmt->color.underline.r), &(fmt->color.underline.g),
1244 &(fmt->color.underline.b), &(fmt->color.underline.a));
1245 else if (cmd == underline2_colorstr)
1246 _format_color_parse(tmp_param,
1247 &(fmt->color.underline2.r), &(fmt->color.underline2.g),
1248 &(fmt->color.underline2.b), &(fmt->color.underline2.a));
1249 else if (cmd == underline_dash_colorstr)
1250 _format_color_parse(tmp_param,
1251 &(fmt->color.underline_dash.r), &(fmt->color.underline_dash.g),
1252 &(fmt->color.underline_dash.b), &(fmt->color.underline_dash.a));
1253 else if (cmd == outline_colorstr)
1254 _format_color_parse(tmp_param,
1255 &(fmt->color.outline.r), &(fmt->color.outline.g),
1256 &(fmt->color.outline.b), &(fmt->color.outline.a));
1257 else if (cmd == shadow_colorstr)
1258 _format_color_parse(tmp_param,
1259 &(fmt->color.shadow.r), &(fmt->color.shadow.g),
1260 &(fmt->color.shadow.b), &(fmt->color.shadow.a));
1261 else if (cmd == glow_colorstr)
1262 _format_color_parse(tmp_param,
1263 &(fmt->color.glow.r), &(fmt->color.glow.g),
1264 &(fmt->color.glow.b), &(fmt->color.glow.a));
1265 else if (cmd == glow2_colorstr)
1266 _format_color_parse(tmp_param,
1267 &(fmt->color.glow2.r), &(fmt->color.glow2.g),
1268 &(fmt->color.glow2.b), &(fmt->color.glow2.a));
1269 else if (cmd == backing_colorstr)
1270 _format_color_parse(tmp_param,
1271 &(fmt->color.backing.r), &(fmt->color.backing.g),
1272 &(fmt->color.backing.b), &(fmt->color.backing.a));
1273 else if (cmd == strikethrough_colorstr)
1274 _format_color_parse(tmp_param,
1275 &(fmt->color.strikethrough.r), &(fmt->color.strikethrough.g),
1276 &(fmt->color.strikethrough.b), &(fmt->color.strikethrough.a));
1277 else if (cmd == alignstr)
1278 {
1279 if (!strcmp(tmp_param, "auto"))
1280 {
1281 fmt->halign_auto = EINA_TRUE;
1282 }
1283 else
1284 {
1285 if (!strcmp(tmp_param, "middle")) fmt->halign = 0.5;
1286 else if (!strcmp(tmp_param, "center")) fmt->halign = 0.5;
1287 else if (!strcmp(tmp_param, "left")) fmt->halign = 0.0;
1288 else if (!strcmp(tmp_param, "right")) fmt->halign = 1.0;
1289 else
1290 {
1291 char *endptr = NULL;
1292 double val = strtod(tmp_param, &endptr);
1293 if (endptr)
1294 {
1295 while (*endptr && _is_white(*endptr))
1296 endptr++;
1297 if (*endptr == '%')
1298 val /= 100.0;
1299 }
1300 fmt->halign = val;
1301 if (fmt->halign < 0.0) fmt->halign = 0.0;
1302 else if (fmt->halign > 1.0) fmt->halign = 1.0;
1303 }
1304 fmt->halign_auto = EINA_FALSE;
1305 }
1306 }
1307 else if (cmd == valignstr)
1308 {
1309 if (!strcmp(tmp_param, "top")) fmt->valign = 0.0;
1310 else if (!strcmp(tmp_param, "middle")) fmt->valign = 0.5;
1311 else if (!strcmp(tmp_param, "center")) fmt->valign = 0.5;
1312 else if (!strcmp(tmp_param, "bottom")) fmt->valign = 1.0;
1313 else if (!strcmp(tmp_param, "baseline")) fmt->valign = -1.0;
1314 else if (!strcmp(tmp_param, "base")) fmt->valign = -1.0;
1315 else
1316 {
1317 char *endptr = NULL;
1318 double val = strtod(tmp_param, &endptr);
1319 if (endptr)
1320 {
1321 while (*endptr && _is_white(*endptr))
1322 endptr++;
1323 if (*endptr == '%')
1324 val /= 100.0;
1325 }
1326 fmt->valign = val;
1327 if (fmt->valign < 0.0) fmt->valign = 0.0;
1328 else if (fmt->valign > 1.0) fmt->valign = 1.0;
1329 }
1330 }
1331 else if (cmd == wrapstr)
1332 {
1333 if (!strcmp(tmp_param, "word"))
1334 {
1335 fmt->wrap_word = 1;
1336 fmt->wrap_char = fmt->wrap_mixed = 0;
1337 }
1338 else if (!strcmp(tmp_param, "char"))
1339 {
1340 fmt->wrap_word = fmt->wrap_mixed = 0;
1341 fmt->wrap_char = 1;
1342 }
1343 else if (!strcmp(tmp_param, "mixed"))
1344 {
1345 fmt->wrap_word = fmt->wrap_char = 0;
1346 fmt->wrap_mixed = 1;
1347 }
1348 else
1349 {
1350 fmt->wrap_word = fmt->wrap_mixed = fmt->wrap_char = 0;
1351 }
1352 }
1353 else if (cmd == left_marginstr)
1354 {
1355 if (!strcmp(tmp_param, "reset"))
1356 fmt->margin.l = 0;
1357 else
1358 {
1359 if (tmp_param[0] == '+')
1360 fmt->margin.l += atoi(&(tmp_param[1]));
1361 else if (tmp_param[0] == '-')
1362 fmt->margin.l -= atoi(&(tmp_param[1]));
1363 else
1364 fmt->margin.l = atoi(tmp_param);
1365 if (fmt->margin.l < 0) fmt->margin.l = 0;
1366 }
1367 }
1368 else if (cmd == right_marginstr)
1369 {
1370 if (!strcmp(tmp_param, "reset"))
1371 fmt->margin.r = 0;
1372 else
1373 {
1374 if (tmp_param[0] == '+')
1375 fmt->margin.r += atoi(&(tmp_param[1]));
1376 else if (tmp_param[0] == '-')
1377 fmt->margin.r -= atoi(&(tmp_param[1]));
1378 else
1379 fmt->margin.r = atoi(tmp_param);
1380 if (fmt->margin.r < 0) fmt->margin.r = 0;
1381 }
1382 }
1383 else if (cmd == underlinestr)
1384 {
1385 if (!strcmp(tmp_param, "off"))
1386 {
1387 fmt->underline = 0;
1388 fmt->underline2 = 0;
1389 }
1390 else if ((!strcmp(tmp_param, "on")) ||
1391 (!strcmp(tmp_param, "single")))
1392 {
1393 fmt->underline = 1;
1394 fmt->underline2 = 0;
1395 }
1396 else if (!strcmp(tmp_param, "double"))
1397 {
1398 fmt->underline = 1;
1399 fmt->underline2 = 1;
1400 }
1401 else if (!strcmp(tmp_param, "dashed"))
1402 fmt->underline_dash = 1;
1403 }
1404 else if (cmd == strikethroughstr)
1405 {
1406 if (!strcmp(tmp_param, "off"))
1407 fmt->strikethrough = 0;
1408 else if (!strcmp(tmp_param, "on"))
1409 fmt->strikethrough = 1;
1410 }
1411 else if (cmd == backingstr)
1412 {
1413 if (!strcmp(tmp_param, "off"))
1414 fmt->backing = 0;
1415 else if (!strcmp(tmp_param, "on"))
1416 fmt->backing = 1;
1417 }
1418 else if (cmd == stylestr)
1419 {
1420 char *p1, *p2, *p, *pp;
1421
1422 p1 = alloca(len + 1);
1423 *p1 = 0;
1424 p2 = alloca(len + 1);
1425 *p2 = 0;
1426 /* no comma */
1427 if (!strstr(tmp_param, ",")) p1 = tmp_param;
1428 else
1429 {
1430 /* split string "str1,str2" into p1 and p2 (if we have more than
1431 * 1 str2 eg "str1,str2,str3,str4" then we don't care. p2 just
1432 * ends up being the last one as right now it's only valid to have
1433 * 1 comma and 2 strings */
1434 pp = p1;
1435 for (p = tmp_param; *p; p++)
1436 {
1437 if (*p == ',')
1438 {
1439 *pp = 0;
1440 pp = p2;
1441 continue;
1442 }
1443 *pp = *p;
1444 pp++;
1445 }
1446 *pp = 0;
1447 }
1448 if (!strcmp(p1, "off")) fmt->style = EVAS_TEXT_STYLE_PLAIN;
1449 else if (!strcmp(p1, "none")) fmt->style = EVAS_TEXT_STYLE_PLAIN;
1450 else if (!strcmp(p1, "plain")) fmt->style = EVAS_TEXT_STYLE_PLAIN;
1451 else if (!strcmp(p1, "shadow")) fmt->style = EVAS_TEXT_STYLE_SHADOW;
1452 else if (!strcmp(p1, "outline")) fmt->style = EVAS_TEXT_STYLE_OUTLINE;
1453 else if (!strcmp(p1, "soft_outline")) fmt->style = EVAS_TEXT_STYLE_SOFT_OUTLINE;
1454 else if (!strcmp(p1, "outline_shadow")) fmt->style = EVAS_TEXT_STYLE_OUTLINE_SHADOW;
1455 else if (!strcmp(p1, "outline_soft_shadow")) fmt->style = EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW;
1456 else if (!strcmp(p1, "glow")) fmt->style = EVAS_TEXT_STYLE_GLOW;
1457 else if (!strcmp(p1, "far_shadow")) fmt->style = EVAS_TEXT_STYLE_FAR_SHADOW;
1458 else if (!strcmp(p1, "soft_shadow")) fmt->style = EVAS_TEXT_STYLE_SOFT_SHADOW;
1459 else if (!strcmp(p1, "far_soft_shadow")) fmt->style = EVAS_TEXT_STYLE_FAR_SOFT_SHADOW;
1460 else fmt->style = EVAS_TEXT_STYLE_PLAIN;
1461
1462 if (*p2)
1463 {
1464 if (!strcmp(p2, "bottom_right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT);
1465 else if (!strcmp(p2, "bottom")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM);
1466 else if (!strcmp(p2, "bottom_left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT);
1467 else if (!strcmp(p2, "left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT);
1468 else if (!strcmp(p2, "top_left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT);
1469 else if (!strcmp(p2, "top")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP);
1470 else if (!strcmp(p2, "top_right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT);
1471 else if (!strcmp(p2, "right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT);
1472 else EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT);
1473 }
1474 }
1475 else if (cmd == tabstopsstr)
1476 {
1477 fmt->tabstops = atoi(tmp_param);
1478 if (fmt->tabstops < 1) fmt->tabstops = 1;
1479 }
1480 else if (cmd == linesizestr)
1481 {
1482 fmt->linesize = atoi(tmp_param);
1483 fmt->linerelsize = 0.0;
1484 }
1485 else if (cmd == linerelsizestr)
1486 {
1487 char *endptr = NULL;
1488 double val = strtod(tmp_param, &endptr);
1489 if (endptr)
1490 {
1491 while (*endptr && _is_white(*endptr))
1492 endptr++;
1493 if (*endptr == '%')
1494 {
1495 fmt->linerelsize = val / 100.0;
1496 fmt->linesize = 0;
1497 if (fmt->linerelsize < 0.0) fmt->linerelsize = 0.0;
1498 }
1499 }
1500 }
1501 else if (cmd == linegapstr)
1502 {
1503 fmt->linegap = atoi(tmp_param);
1504 fmt->linerelgap = 0.0;
1505 }
1506 else if (cmd == linerelgapstr)
1507 {
1508 char *endptr = NULL;
1509 double val = strtod(tmp_param, &endptr);
1510 if (endptr)
1511 {
1512 while (*endptr && _is_white(*endptr))
1513 endptr++;
1514 if (*endptr == '%')
1515 {
1516 fmt->linerelgap = val / 100.0;
1517 fmt->linegap = 0;
1518 if (fmt->linerelgap < 0.0) fmt->linerelgap = 0.0;
1519 }
1520 }
1521 }
1522 else if (cmd == itemstr)
1523 {
1524 // itemstr == replacement object items in textblock - inline imges
1525 // for example
1526 }
1527 else if (cmd == linefillstr)
1528 {
1529 char *endptr = NULL;
1530 double val = strtod(tmp_param, &endptr);
1531 if (endptr)
1532 {
1533 while (*endptr && _is_white(*endptr))
1534 endptr++;
1535 if (*endptr == '%')
1536 {
1537 fmt->linefill = val / 100.0;
1538 if (fmt->linefill < 0.0) fmt->linefill = 0.0;
1539 }
1540 }
1541 }
1542 else if (cmd == ellipsisstr)
1543 {
1544 char *endptr = NULL;
1545 fmt->ellipsis = strtod(tmp_param, &endptr);
1546 if ((fmt->ellipsis < 0.0) || (fmt->ellipsis > 1.0))
1547 fmt->ellipsis = -1.0;
1548 else
1549 {
1550 Evas_Object_Textblock *o;
1551
1552 o = (Evas_Object_Textblock *)(obj->object_data);
1553 o->have_ellipsis = 1;
1554 }
1555 }
1556 else if (cmd == passwordstr)
1557 {
1558 if (!strcmp(tmp_param, "off"))
1559 fmt->password = 0;
1560 else if (!strcmp(tmp_param, "on"))
1561 fmt->password = 1;
1562 }
1563 else if (cmd == underline_dash_widthstr)
1564 {
1565 fmt->underline_dash_width = atoi(tmp_param);
1566 if (fmt->underline_dash_width <= 0) fmt->underline_dash_width = 1;
1567 }
1568 else if (cmd == underline_dash_gapstr)
1569 {
1570 fmt->underline_dash_gap = atoi(tmp_param);
1571 if (fmt->underline_dash_gap <= 0) fmt->underline_dash_gap = 1;
1572 }
1573}
1574
1575/**
1576 * @internal
1577 * Returns #EINA_TRUE if the item is a format parameter, #EINA_FALSE otherwise.
1578 *
1579 * @param[in] item the item to check - Not NULL.
1580 */
1581static Eina_Bool
1582_format_is_param(const char *item)
1583{
1584 if (strchr(item, '=')) return EINA_TRUE;
1585 return EINA_FALSE;
1586}
1587
1588/**
1589 * @internal
1590 * Parse the format item and populate key and val with the stringshares that
1591 * corrospond to the formats parsed.
1592 * It expects item to be of the structure:
1593 * "key=val"
1594 *
1595 * @param[in] item the item to parse - Not NULL.
1596 * @param[out] key where to store the key at - Not NULL.
1597 * @param[out] val where to store the value at - Not NULL.
1598 */
1599static void
1600_format_param_parse(const char *item, const char **key, const char **val)
1601{
1602 const char *start, *end, *quote;
1603
1604 start = strchr(item, '=');
1605 *key = eina_stringshare_add_length(item, start - item);
1606 start++; /* Advance after the '=' */
1607 /* If we can find a quote, our new delimiter is a quote, not a space. */
1608 if ((quote = strchr(start, '\'')))
1609 {
1610 start = quote + 1;
1611 end = strchr(start, '\'');
1612 }
1613 else
1614 {
1615 end = strchr(start, ' ');
1616 }
1617
1618 /* Null terminate before the spaces */
1619 if (end)
1620 {
1621 *val = eina_stringshare_add_length(start, end - start);
1622 }
1623 else
1624 {
1625 *val = eina_stringshare_add(start);
1626 }
1627}
1628
1629/**
1630 * @internal
1631 * This function parses the format passed in *s and advances s to point to the
1632 * next format item, while returning the current one as the return value.
1633 * @param s The current and returned position in the format string.
1634 * @return the current item parsed from the string.
1635 */
1636static const char *
1637_format_parse(const char **s)
1638{
1639 const char *p;
1640 const char *s1 = NULL, *s2 = NULL;
1641 Eina_Bool quote = EINA_FALSE;;
1642
1643 p = *s;
1644 if (*p == 0) return NULL;
1645 for (;;)
1646 {
1647 if (!s1)
1648 {
1649 if (*p != ' ') s1 = p;
1650 if (*p == 0) break;
1651 }
1652 else if (!s2)
1653 {
1654 if (*p == '\'')
1655 {
1656 quote = !quote;
1657 }
1658
1659 if ((p > *s) && (p[-1] != '\\') && (!quote))
1660 {
1661 if (*p == ' ') s2 = p;
1662 }
1663 if (*p == 0) s2 = p;
1664 }
1665 p++;
1666 if (s1 && s2)
1667 {
1668 *s = s2;
1669 return s1;
1670 }
1671 }
1672 *s = p;
1673 return NULL;
1674}
1675
1676/**
1677 * @internal
1678 * Parse the format str and populate fmt with the formats found.
1679 *
1680 * @param obj The evas object - Not NULL.
1681 * @param[out] fmt The format to populate - Not NULL.
1682 * @param[in] str the string to parse.- Not NULL.
1683 */
1684static void
1685_format_fill(Evas_Object *obj, Evas_Object_Textblock_Format *fmt, const char *str)
1686{
1687 const char *s;
1688 const char *item;
1689
1690 s = str;
1691
1692 /* get rid of anything +s or -s off the start of the string */
1693 while ((*s == ' ') || (*s == '+') || (*s == '-')) s++;
1694
1695 while ((item = _format_parse(&s)))
1696 {
1697 if (_format_is_param(item))
1698 {
1699 const char *key = NULL, *val = NULL;
1700
1701 _format_param_parse(item, &key, &val);
1702 _format_command(obj, fmt, key, val);
1703 eina_stringshare_del(key);
1704 eina_stringshare_del(val);
1705 }
1706 else
1707 {
1708 /* immediate - not handled here */
1709 }
1710 }
1711}
1712
1713/**
1714 * @internal
1715 * Duplicate a format and return the duplicate.
1716 *
1717 * @param obj The evas object - Not NULL.
1718 * @param[in] fmt The format to duplicate - Not NULL.
1719 * @return the copy of the format.
1720 */
1721static Evas_Object_Textblock_Format *
1722_format_dup(Evas_Object *obj, const Evas_Object_Textblock_Format *fmt)
1723{
1724 Evas_Object_Textblock_Format *fmt2;
1725
1726 fmt2 = calloc(1, sizeof(Evas_Object_Textblock_Format));
1727 memcpy(fmt2, fmt, sizeof(Evas_Object_Textblock_Format));
1728 fmt2->ref = 1;
1729 fmt2->font.fdesc = evas_font_desc_ref(fmt->font.fdesc);
1730
1731 if (fmt->font.source) fmt2->font.source = eina_stringshare_add(fmt->font.source);
1732
1733 /* FIXME: just ref the font here... */
1734 fmt2->font.font = evas_font_load(obj->layer->evas, fmt2->font.fdesc,
1735 fmt2->font.source, (int)(((double) fmt2->font.size) * obj->cur.scale));
1736 return fmt2;
1737}
1738
1739
1740
1741
1742/**
1743 * @internal
1744 * @typedef Ctxt
1745 *
1746 * A pack of information that needed to be passed around in the layout engine,
1747 * packed for easier access.
1748 */
1749typedef struct _Ctxt Ctxt;
1750
1751struct _Ctxt
1752{
1753 Evas_Object *obj;
1754 Evas_Object_Textblock *o;
1755
1756 Evas_Object_Textblock_Paragraph *paragraphs;
1757 Evas_Object_Textblock_Paragraph *par;
1758 Evas_Object_Textblock_Line *ln;
1759
1760
1761 Eina_List *format_stack;
1762 Evas_Object_Textblock_Format *fmt;
1763
1764 int x, y;
1765 int w, h;
1766 int wmax, hmax;
1767 int maxascent, maxdescent;
1768 int marginl, marginr;
1769 int line_no;
1770 int underline_extend;
1771 int have_underline, have_underline2;
1772 double align, valign;
1773 Eina_Bool align_auto : 1;
1774 Eina_Bool width_changed : 1;
1775};
1776
1777static void _layout_text_add_logical_item(Ctxt *c, Evas_Object_Textblock_Text_Item *ti, Eina_List *rel);
1778static void _text_item_update_sizes(Ctxt *c, Evas_Object_Textblock_Text_Item *ti);
1779static 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, Eina_Bool create_item);
1780/**
1781 * @internal
1782 * Adjust the ascent/descent of the format and context.
1783 *
1784 * @param maxascent The ascent to update - Not NUL.
1785 * @param maxdescent The descent to update - Not NUL.
1786 * @param fmt The format to adjust - NOT NULL.
1787 */
1788static void
1789_layout_format_ascent_descent_adjust(const Evas_Object *obj,
1790 Evas_Coord *maxascent, Evas_Coord *maxdescent,
1791 Evas_Object_Textblock_Format *fmt)
1792{
1793 int ascent, descent;
1794
1795 if (fmt->font.font)
1796 {
1797 // ascent = c->ENFN->font_max_ascent_get(c->ENDT, fmt->font.font);
1798 // descent = c->ENFN->font_max_descent_get(c->ENDT, fmt->font.font);
1799 ascent = ENFN->font_ascent_get(ENDT, fmt->font.font);
1800 descent = ENFN->font_descent_get(ENDT, fmt->font.font);
1801 if (fmt->linesize > 0)
1802 {
1803 if ((ascent + descent) < fmt->linesize)
1804 {
1805 ascent = ((fmt->linesize * ascent) / (ascent + descent));
1806 descent = fmt->linesize - ascent;
1807 }
1808 }
1809 else if (fmt->linerelsize > 0.0)
1810 {
1811 descent = descent * fmt->linerelsize;
1812 ascent = ascent * fmt->linerelsize;
1813 }
1814 descent += fmt->linegap;
1815 descent += ((ascent + descent) * fmt->linerelgap);
1816 if (*maxascent < ascent) *maxascent = ascent;
1817 if (*maxdescent < descent) *maxdescent = descent;
1818 if (fmt->linefill > 0.0)
1819 {
1820 int dh;
1821
1822 dh = obj->cur.geometry.h - (*maxascent + *maxdescent);
1823 if (dh < 0) dh = 0;
1824 dh = fmt->linefill * dh;
1825 *maxdescent += dh / 2;
1826 *maxascent += dh - (dh / 2);
1827 // FIXME: set flag that says "if heigh changes - reformat"
1828 }
1829 }
1830}
1831
1832/**
1833 * @internal
1834 * Create a new line using the info from the format and update the format
1835 * and context.
1836 *
1837 * @param c The context to work on - Not NULL.
1838 * @param fmt The format to use info from - NOT NULL.
1839 */
1840static void
1841_layout_line_new(Ctxt *c, Evas_Object_Textblock_Format *fmt)
1842{
1843 c->ln = calloc(1, sizeof(Evas_Object_Textblock_Line));
1844 c->align = fmt->halign;
1845 c->align_auto = fmt->halign_auto;
1846 c->marginl = fmt->margin.l;
1847 c->marginr = fmt->margin.r;
1848 c->par->lines = (Evas_Object_Textblock_Line *)eina_inlist_append(EINA_INLIST_GET(c->par->lines), EINA_INLIST_GET(c->ln));
1849 c->x = 0;
1850 c->maxascent = c->maxdescent = 0;
1851 c->ln->line_no = -1;
1852 c->ln->par = c->par;
1853}
1854
1855static inline Evas_Object_Textblock_Paragraph *
1856_layout_find_paragraph_by_y(Evas_Object_Textblock *o, Evas_Coord y)
1857{
1858 Evas_Object_Textblock_Paragraph *start, *par;
1859 int i;
1860
1861 start = o->paragraphs;
1862
1863 for (i = 0 ; i < TEXTBLOCK_PAR_INDEX_SIZE ; i++)
1864 {
1865 if (!o->par_index[i] || (o->par_index[i]->y > y))
1866 {
1867 break;
1868 }
1869 start = o->par_index[i];
1870 }
1871
1872 EINA_INLIST_FOREACH(start, par)
1873 {
1874 if ((par->y <= y) && (y < par->y + par->h))
1875 return par;
1876 }
1877
1878 return NULL;
1879}
1880
1881static inline Evas_Object_Textblock_Paragraph *
1882_layout_find_paragraph_by_line_no(Evas_Object_Textblock *o, int line_no)
1883{
1884 Evas_Object_Textblock_Paragraph *start, *par;
1885 int i;
1886
1887 start = o->paragraphs;
1888
1889 for (i = 0 ; i < TEXTBLOCK_PAR_INDEX_SIZE ; i++)
1890 {
1891 if (!o->par_index[i] || (o->par_index[i]->line_no > line_no))
1892 {
1893 break;
1894 }
1895 start = o->par_index[i];
1896 }
1897
1898 EINA_INLIST_FOREACH(start, par)
1899 {
1900 Evas_Object_Textblock_Paragraph *npar =
1901 (Evas_Object_Textblock_Paragraph *) EINA_INLIST_GET(par)->next;
1902 if ((par->line_no <= line_no) &&
1903 (!npar || (line_no < npar->line_no)))
1904 return par;
1905 }
1906
1907 return NULL;
1908}
1909/* End of rbtree index functios */
1910
1911/**
1912 * @internal
1913 * Create a new layout paragraph.
1914 * If c->par is not NULL, the paragraph is appended/prepended according
1915 * to the append parameter. If it is NULL, the paragraph is appended at
1916 * the end of the list.
1917 *
1918 * @param c The context to work on - Not NULL.
1919 * @param n the associated text node
1920 * @param append true to append, false to prpend.
1921 */
1922static void
1923_layout_paragraph_new(Ctxt *c, Evas_Object_Textblock_Node_Text *n,
1924 Eina_Bool append)
1925{
1926 Evas_Object_Textblock_Paragraph *rel_par = c->par;
1927 c->par = calloc(1, sizeof(Evas_Object_Textblock_Paragraph));
1928 if (append || !rel_par)
1929 c->paragraphs = (Evas_Object_Textblock_Paragraph *)
1930 eina_inlist_append_relative(EINA_INLIST_GET(c->paragraphs),
1931 EINA_INLIST_GET(c->par),
1932 EINA_INLIST_GET(rel_par));
1933 else
1934 c->paragraphs = (Evas_Object_Textblock_Paragraph *)
1935 eina_inlist_prepend_relative(EINA_INLIST_GET(c->paragraphs),
1936 EINA_INLIST_GET(c->par),
1937 EINA_INLIST_GET(rel_par));
1938
1939 c->ln = NULL;
1940 c->par->text_node = n;
1941 if (n)
1942 n->par = c->par;
1943 c->par->line_no = -1;
1944 c->par->visible = 1;
1945 c->o->num_paragraphs++;
1946}
1947
1948#ifdef BIDI_SUPPORT
1949/**
1950 * @internal
1951 * Update bidi paragraph props.
1952 *
1953 * @param par The paragraph to update
1954 */
1955static inline void
1956_layout_update_bidi_props(const Evas_Object_Textblock *o,
1957 Evas_Object_Textblock_Paragraph *par)
1958{
1959 if (par->text_node)
1960 {
1961 const Eina_Unicode *text;
1962 int *segment_idxs = NULL;
1963 text = eina_ustrbuf_string_get(par->text_node->unicode);
1964
1965 if (o->bidi_delimiters)
1966 segment_idxs = evas_bidi_segment_idxs_get(text, o->bidi_delimiters);
1967
1968 evas_bidi_paragraph_props_unref(par->bidi_props);
1969 par->bidi_props = evas_bidi_paragraph_props_get(text,
1970 eina_ustrbuf_length_get(par->text_node->unicode),
1971 segment_idxs);
1972 par->direction = EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(par->bidi_props) ?
1973 EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR;
1974 par->is_bidi = !!par->bidi_props;
1975 if (segment_idxs) free(segment_idxs);
1976 }
1977}
1978#endif
1979
1980
1981/**
1982 * @internal
1983 * Free the visual lines in the paragraph (logical items are kept)
1984 */
1985static void
1986_paragraph_clear(const Evas_Object *obj __UNUSED__,
1987 Evas_Object_Textblock_Paragraph *par)
1988{
1989 while (par->lines)
1990 {
1991 Evas_Object_Textblock_Line *ln;
1992
1993 ln = (Evas_Object_Textblock_Line *) par->lines;
1994 par->lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(par->lines), EINA_INLIST_GET(par->lines));
1995 _line_free(ln);
1996 }
1997}
1998
1999/**
2000 * @internal
2001 * Free the layout paragraph and all of it's lines and logical items.
2002 */
2003static void
2004_paragraph_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par)
2005{
2006 Evas_Object_Textblock *o;
2007 o = (Evas_Object_Textblock *)(obj->object_data);
2008 _paragraph_clear(obj, par);
2009
2010 {
2011 Eina_List *i, *i_prev;
2012 Evas_Object_Textblock_Item *it;
2013 EINA_LIST_FOREACH_SAFE(par->logical_items, i, i_prev, it)
2014 {
2015 _item_free(obj, NULL, it);
2016 }
2017 eina_list_free(par->logical_items);
2018 }
2019#ifdef BIDI_SUPPORT
2020 if (par->bidi_props)
2021 evas_bidi_paragraph_props_unref(par->bidi_props);
2022#endif
2023 /* If we are the active par of the text node, set to NULL */
2024 if (par->text_node && (par->text_node->par == par))
2025 par->text_node->par = NULL;
2026
2027 o->num_paragraphs--;
2028
2029 free(par);
2030}
2031
2032/**
2033 * @internal
2034 * Clear all the paragraphs from the inlist pars.
2035 *
2036 * @param obj the evas object - Not NULL.
2037 * @param pars the paragraphs to clean - Not NULL.
2038 */
2039static void
2040_paragraphs_clear(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars)
2041{
2042 Evas_Object_Textblock_Paragraph *par;
2043
2044 EINA_INLIST_FOREACH(EINA_INLIST_GET(pars), par)
2045 {
2046 _paragraph_clear(obj, par);
2047 }
2048}
2049
2050/**
2051 * @internal
2052 * Free the paragraphs from the inlist pars, the difference between this and
2053 * _paragraphs_clear is that the latter keeps the logical items and the par
2054 * items, while the former frees them as well.
2055 *
2056 * @param obj the evas object - Not NULL.
2057 * @param pars the paragraphs to clean - Not NULL.
2058 */
2059static void
2060_paragraphs_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars)
2061{
2062 Evas_Object_Textblock *o;
2063 o = (Evas_Object_Textblock *)(obj->object_data);
2064
2065 o->num_paragraphs = 0;
2066
2067 while (pars)
2068 {
2069 Evas_Object_Textblock_Paragraph *par;
2070
2071 par = (Evas_Object_Textblock_Paragraph *) pars;
2072 pars = (Evas_Object_Textblock_Paragraph *)eina_inlist_remove(EINA_INLIST_GET(pars), EINA_INLIST_GET(par));
2073 _paragraph_free(obj, par);
2074 }
2075}
2076
2077/**
2078 * @internal
2079 * Push fmt to the format stack, if fmt is NULL, will fush a default item.
2080 *
2081 * @param c the context to work on - Not NULL.
2082 * @param fmt the format to push.
2083 * @see _layout_format_pop()
2084 */
2085static Evas_Object_Textblock_Format *
2086_layout_format_push(Ctxt *c, Evas_Object_Textblock_Format *fmt,
2087 Evas_Object_Textblock_Node_Format *fnode)
2088{
2089 if (fmt)
2090 {
2091 fmt = _format_dup(c->obj, fmt);
2092 c->format_stack = eina_list_prepend(c->format_stack, fmt);
2093 fmt->fnode = fnode;
2094 }
2095 else
2096 {
2097 fmt = calloc(1, sizeof(Evas_Object_Textblock_Format));
2098 c->format_stack = eina_list_prepend(c->format_stack, fmt);
2099 fmt->ref = 1;
2100 fmt->halign = 0.0;
2101 fmt->halign_auto = EINA_TRUE;
2102 fmt->valign = -1.0;
2103 fmt->style = EVAS_TEXT_STYLE_PLAIN;
2104 fmt->tabstops = 32;
2105 fmt->linesize = 0;
2106 fmt->linerelsize = 0.0;
2107 fmt->linegap = 0;
2108 fmt->underline_dash_width = 6;
2109 fmt->underline_dash_gap = 2;
2110 fmt->linerelgap = 0.0;
2111 fmt->password = 1;
2112 }
2113 return fmt;
2114}
2115
2116/**
2117 * @internal
2118 * Pop fmt to the format stack, if there's something in the stack free fmt
2119 * and set it to point to the next item instead, else return fmt.
2120 *
2121 * @param c the context to work on - Not NULL.
2122 * @param format - the text of the format to free (assured to start with '-').
2123 * @return the next format in the stack, or format if there's none.
2124 * @see _layout_format_push()
2125 */
2126static Evas_Object_Textblock_Format *
2127_layout_format_pop(Ctxt *c, const char *format)
2128{
2129 Evas_Object_Textblock_Format *fmt = eina_list_data_get(c->format_stack);
2130
2131 if ((c->format_stack) && (c->format_stack->next))
2132 {
2133 Eina_List *redo_nodes = NULL;
2134 format++; /* Skip the '-' */
2135
2136 /* Generic pop, should just pop. */
2137 if (((format[0] == ' ') && !format[1]) ||
2138 !format[0])
2139 {
2140 _format_unref_free(c->obj, fmt);
2141 c->format_stack =
2142 eina_list_remove_list(c->format_stack, c->format_stack);
2143 }
2144 else
2145 {
2146 size_t len = strlen(format);
2147 Eina_List *i, *i_next;
2148 /* Remove only the matching format. */
2149 EINA_LIST_FOREACH_SAFE(c->format_stack, i, i_next, fmt)
2150 {
2151 /* Stop when we reach the base item */
2152 if (!i_next)
2153 break;
2154
2155 c->format_stack =
2156 eina_list_remove_list(c->format_stack, c->format_stack);
2157
2158 /* Make sure the ending tag matches the starting tag.
2159 * I.e whole of the ending tag matches the start of the
2160 * starting tag, and the starting tag's next char is either
2161 * NULL or white. Skip the starting '+'. */
2162 if (_FORMAT_IS_CLOSER_OF(
2163 fmt->fnode->orig_format, format, len))
2164 {
2165 _format_unref_free(c->obj, fmt);
2166 break;
2167 }
2168 else
2169 {
2170 redo_nodes = eina_list_prepend(redo_nodes, fmt->fnode);
2171 _format_unref_free(c->obj, fmt);
2172 }
2173 }
2174 }
2175
2176 /* Redo all the nodes needed to be redone */
2177 {
2178 Evas_Object_Textblock_Node_Format *fnode;
2179 Eina_List *i, *i_next;
2180
2181 EINA_LIST_FOREACH_SAFE(redo_nodes, i, i_next, fnode)
2182 {
2183 /* FIXME: Actually do something with the new acquired padding,
2184 * the can be different and affect our padding! */
2185 Evas_Coord style_pad_l, style_pad_r, style_pad_t, style_pad_b;
2186 style_pad_l = style_pad_r = style_pad_t = style_pad_b = 0;
2187 redo_nodes = eina_list_remove_list(redo_nodes, i);
2188 fmt = eina_list_data_get(c->format_stack);
2189 _layout_do_format(c->obj, c, &fmt, fnode,
2190 &style_pad_l, &style_pad_r,
2191 &style_pad_t, &style_pad_b, EINA_FALSE);
2192 }
2193 }
2194
2195 fmt = eina_list_data_get(c->format_stack);
2196 }
2197 return fmt;
2198}
2199
2200/**
2201 * @internal
2202 * Parse item and fill fmt with the item.
2203 *
2204 * @param c the context to work on - Not NULL.
2205 * @param fmt the format to fill - not null.
2206 */
2207static void
2208_layout_format_value_handle(Ctxt *c, Evas_Object_Textblock_Format *fmt, const char *item)
2209{
2210 const char *key = NULL, *val = NULL;
2211
2212 _format_param_parse(item, &key, &val);
2213 if ((key) && (val)) _format_command(c->obj, fmt, key, val);
2214 if (key) eina_stringshare_del(key);
2215 if (val) eina_stringshare_del(val);
2216 c->align = fmt->halign;
2217 c->align_auto = fmt->halign_auto;
2218 c->marginl = fmt->margin.l;
2219 c->marginr = fmt->margin.r;
2220}
2221
2222#define VSIZE_FULL 0
2223#define VSIZE_ASCENT 1
2224
2225#define SIZE 0
2226#define SIZE_ABS 1
2227#define SIZE_REL 2
2228
2229/**
2230 * @internal
2231 * Get the current line's alignment from the context.
2232 *
2233 * @param c the context to work on - Not NULL.
2234 */
2235static inline double
2236_layout_line_align_get(Ctxt *c)
2237{
2238#ifdef BIDI_SUPPORT
2239 if (c->align_auto && c->ln)
2240 {
2241 if (c->ln->items && c->ln->items->text_node &&
2242 (c->ln->par->direction == EVAS_BIDI_DIRECTION_RTL))
2243 {
2244 /* Align right*/
2245 return 1.0;
2246 }
2247 else
2248 {
2249 /* Align left */
2250 return 0.0;
2251 }
2252 }
2253#endif
2254 return c->align;
2255}
2256
2257#ifdef BIDI_SUPPORT
2258/**
2259 * @internal
2260 * Reorder the items in visual order
2261 *
2262 * @param line the line to reorder
2263 */
2264static void
2265_layout_line_reorder(Evas_Object_Textblock_Line *line)
2266{
2267 /*FIXME: do it a bit more efficient - not very efficient ATM. */
2268 Evas_Object_Textblock_Item *it;
2269 EvasBiDiStrIndex *v_to_l = NULL;
2270 Evas_Coord x;
2271 size_t start, end;
2272 size_t len;
2273
2274 if (line->items && line->items->text_node &&
2275 line->par->bidi_props)
2276 {
2277 Evas_BiDi_Paragraph_Props *props;
2278 props = line->par->bidi_props;
2279 start = end = line->items->text_pos;
2280
2281 /* Find the first and last positions in the line */
2282
2283 EINA_INLIST_FOREACH(line->items, it)
2284 {
2285 if (it->text_pos < start)
2286 {
2287 start = it->text_pos;
2288 }
2289 else
2290 {
2291 int tlen;
2292 tlen = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ?
2293 _ITEM_TEXT(it)->text_props.text_len : 1;
2294 if (it->text_pos + tlen > end)
2295 {
2296 end = it->text_pos + tlen;
2297 }
2298 }
2299 }
2300
2301 len = end - start;
2302 evas_bidi_props_reorder_line(NULL, start, len, props, &v_to_l);
2303
2304 /* Update visual pos */
2305 {
2306 Evas_Object_Textblock_Item *i;
2307 i = line->items;
2308 while (i)
2309 {
2310 i->visual_pos = evas_bidi_position_logical_to_visual(
2311 v_to_l, len, i->text_pos - start);
2312 i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(i)->next;
2313 }
2314 }
2315
2316 /*FIXME: not very efficient, sort the items arrays. Anyhow, should only
2317 * reorder if it's a bidi paragraph */
2318 {
2319 Evas_Object_Textblock_Item *i, *j, *min;
2320 i = line->items;
2321 while (i)
2322 {
2323 min = i;
2324 EINA_INLIST_FOREACH(i, j)
2325 {
2326 if (j->visual_pos < min->visual_pos)
2327 {
2328 min = j;
2329 }
2330 }
2331 if (min != i)
2332 {
2333 line->items = (Evas_Object_Textblock_Item *) eina_inlist_remove(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min));
2334 line->items = (Evas_Object_Textblock_Item *) eina_inlist_prepend_relative(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min), EINA_INLIST_GET(i));
2335 }
2336
2337 i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(min)->next;
2338 }
2339 }
2340 }
2341
2342 if (v_to_l) free(v_to_l);
2343 x = 0;
2344 EINA_INLIST_FOREACH(line->items, it)
2345 {
2346 it->x = x;
2347 x += it->adv;
2348 }
2349}
2350#endif
2351
2352/* FIXME: doc */
2353static void
2354_layout_calculate_format_item_size(const Evas_Object *obj,
2355 const Evas_Object_Textblock_Format_Item *fi,
2356 Evas_Coord *maxascent, Evas_Coord *maxdescent,
2357 Evas_Coord *_y, Evas_Coord *_w, Evas_Coord *_h)
2358{
2359 /* Adjust sizes according to current line height/scale */
2360 Evas_Coord w, h;
2361 const char *p, *s;
2362
2363 s = fi->item;
2364 w = fi->parent.w;
2365 h = fi->parent.h;
2366 switch (fi->size)
2367 {
2368 case SIZE:
2369 p = strstr(s, " size=");
2370 if (p)
2371 {
2372 p += 6;
2373 if (sscanf(p, "%ix%i", &w, &h) == 2)
2374 {
2375 w = w * obj->cur.scale;
2376 h = h * obj->cur.scale;
2377 }
2378 }
2379 break;
2380 case SIZE_REL:
2381 p = strstr((char *) s, " relsize=");
2382 p += 9;
2383 if (sscanf(p, "%ix%i", &w, &h) == 2)
2384 {
2385 int sz = 1;
2386 if (fi->vsize == VSIZE_FULL)
2387 {
2388 sz = *maxdescent + *maxascent;
2389 }
2390 else if (fi->vsize == VSIZE_ASCENT)
2391 {
2392 sz = *maxascent;
2393 }
2394 w = (w * sz) / h;
2395 h = sz;
2396 }
2397 break;
2398 case SIZE_ABS:
2399 /* Nothing to do */
2400 default:
2401 break;
2402 }
2403
2404 switch (fi->size)
2405 {
2406 case SIZE:
2407 case SIZE_ABS:
2408 switch (fi->vsize)
2409 {
2410 case VSIZE_FULL:
2411 if (h > (*maxdescent + *maxascent))
2412 {
2413 *maxascent += h - (*maxdescent + *maxascent);
2414 *_y = -*maxascent;
2415 }
2416 else
2417 *_y = -(h - *maxdescent);
2418 break;
2419 case VSIZE_ASCENT:
2420 if (h > *maxascent)
2421 {
2422 *maxascent = h;
2423 *_y = -h;
2424 }
2425 else
2426 *_y = -h;
2427 break;
2428 default:
2429 break;
2430 }
2431 break;
2432 case SIZE_REL:
2433 switch (fi->vsize)
2434 {
2435 case VSIZE_FULL:
2436 case VSIZE_ASCENT:
2437 *_y = -*maxascent;
2438 break;
2439 default:
2440 break;
2441 }
2442 break;
2443 default:
2444 break;
2445 }
2446
2447 *_w = w;
2448 *_h = h;
2449}
2450
2451/**
2452 * @internal
2453 * Order the items in the line, update it's properties and update it's
2454 * corresponding paragraph.
2455 *
2456 * @param c the context to work on - Not NULL.
2457 * @param fmt the format to use.
2458 * @param add_line true if we should create a line, false otherwise.
2459 */
2460static void
2461_layout_line_finalize(Ctxt *c, Evas_Object_Textblock_Format *fmt)
2462{
2463 Evas_Object_Textblock_Item *it;
2464 Evas_Coord x = 0;
2465
2466 /* If there are no text items yet, calc ascent/descent
2467 * according to the current format. */
2468 if (c->maxascent + c->maxdescent == 0)
2469 _layout_format_ascent_descent_adjust(c->obj, &c->maxascent,
2470 &c->maxdescent, fmt);
2471
2472 /* Adjust all the item sizes according to the final line size,
2473 * and update the x positions of all the items of the line. */
2474 EINA_INLIST_FOREACH(c->ln->items, it)
2475 {
2476 if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT)
2477 {
2478 Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it);
2479 if (!fi->formatme) goto loop_advance;
2480 _layout_calculate_format_item_size(c->obj, fi, &c->maxascent,
2481 &c->maxdescent, &fi->y, &fi->parent.w, &fi->parent.h);
2482 fi->parent.adv = fi->parent.w;
2483 }
2484
2485loop_advance:
2486 it->x = x;
2487 x += it->adv;
2488
2489 if ((it->x + it->adv) > c->ln->w) c->ln->w = it->x + it->adv;
2490 }
2491
2492 c->ln->y = (c->y - c->par->y) + c->o->style_pad.t;
2493 c->ln->h = c->maxascent + c->maxdescent;
2494 c->ln->baseline = c->maxascent;
2495 if (c->have_underline2)
2496 {
2497 if (c->maxdescent < 4) c->underline_extend = 4 - c->maxdescent;
2498 }
2499 else if (c->have_underline)
2500 {
2501 if (c->maxdescent < 2) c->underline_extend = 2 - c->maxdescent;
2502 }
2503 c->ln->line_no = c->line_no - c->ln->par->line_no;
2504 c->line_no++;
2505 c->y += c->maxascent + c->maxdescent;
2506 if (c->w >= 0)
2507 {
2508 c->ln->x = c->marginl + c->o->style_pad.l +
2509 ((c->w - c->ln->w -
2510 c->o->style_pad.l - c->o->style_pad.r -
2511 c->marginl - c->marginr) * _layout_line_align_get(c));
2512 }
2513 else
2514 {
2515 c->ln->x = c->marginl + c->o->style_pad.l;
2516 }
2517
2518 c->par->h = c->ln->y + c->ln->h;
2519 if (c->ln->w > c->par->w)
2520 c->par->w = c->ln->w;
2521
2522 {
2523 Evas_Coord new_wmax = c->ln->w +
2524 c->marginl + c->marginr - (c->o->style_pad.l + c->o->style_pad.r);
2525 if (new_wmax > c->wmax)
2526 c->wmax = new_wmax;
2527 }
2528}
2529
2530/**
2531 * @internal
2532 * Create a new line and append it to the lines in the context.
2533 *
2534 * @param c the context to work on - Not NULL.
2535 * @param fmt the format to use.
2536 * @param add_line true if we should create a line, false otherwise.
2537 */
2538static void
2539_layout_line_advance(Ctxt *c, Evas_Object_Textblock_Format *fmt)
2540{
2541 _layout_line_finalize(c, fmt);
2542 _layout_line_new(c, fmt);
2543}
2544
2545/**
2546 * @internal
2547 * Create a new text layout item from the string and the format.
2548 *
2549 * @param c the context to work on - Not NULL.
2550 * @param fmt the format to use.
2551 * @param str the string to use.
2552 * @param len the length of the string.
2553 */
2554static Evas_Object_Textblock_Text_Item *
2555_layout_text_item_new(Ctxt *c __UNUSED__, Evas_Object_Textblock_Format *fmt)
2556{
2557 Evas_Object_Textblock_Text_Item *ti;
2558
2559 ti = calloc(1, sizeof(Evas_Object_Textblock_Text_Item));
2560 ti->parent.format = fmt;
2561 ti->parent.format->ref++;
2562 ti->parent.type = EVAS_TEXTBLOCK_ITEM_TEXT;
2563 return ti;
2564}
2565
2566/**
2567 * @internal
2568 * Return the cutoff of the text in the text item.
2569 *
2570 * @param c the context to work on - Not NULL.
2571 * @param fmt the format to use. - Not NULL.
2572 * @param it the item to check - Not null.
2573 * @return -1 if there is no cutoff (either because there is really none,
2574 * or because of an error), cutoff index on success.
2575 */
2576static int
2577_layout_text_cutoff_get(Ctxt *c, Evas_Object_Textblock_Format *fmt,
2578 const Evas_Object_Textblock_Text_Item *ti)
2579{
2580 if (fmt->font.font)
2581 {
2582 Evas_Coord x;
2583 x = c->w - c->o->style_pad.l - c->o->style_pad.r - c->marginl -
2584 c->marginr - c->x - ti->x_adjustment;
2585 if (x < 0)
2586 x = 0;
2587 return c->ENFN->font_last_up_to_pos(c->ENDT, fmt->font.font,
2588 &ti->text_props, x, 0);
2589 }
2590 return -1;
2591}
2592
2593/**
2594 * @internal
2595 * Split before cut, and strip if str[cut - 1] is a whitespace.
2596 *
2597 * @param c the context to work on - Not NULL.
2598 * @param ti the item to cut - not null.
2599 * @param lti the logical list item of the item.
2600 * @param cut the cut index.
2601 * @return the second (newly created) item.
2602 */
2603static Evas_Object_Textblock_Text_Item *
2604_layout_item_text_split_strip_white(Ctxt *c,
2605 Evas_Object_Textblock_Text_Item *ti, Eina_List *lti, size_t cut)
2606{
2607 const Eina_Unicode *ts;
2608 Evas_Object_Textblock_Text_Item *new_ti = NULL, *white_ti = NULL;
2609
2610 ts = GET_ITEM_TEXT(ti);
2611
2612 if (!IS_AT_END(ti, cut) && (ti->text_props.text_len > 0))
2613 {
2614 new_ti = _layout_text_item_new(c, ti->parent.format);
2615 new_ti->parent.text_node = ti->parent.text_node;
2616 new_ti->parent.text_pos = ti->parent.text_pos + cut;
2617 new_ti->parent.merge = EINA_TRUE;
2618
2619 evas_common_text_props_split(&ti->text_props,
2620 &new_ti->text_props, cut);
2621 _layout_text_add_logical_item(c, new_ti, lti);
2622 }
2623
2624 /* Strip the previous white if needed */
2625 if ((cut >= 1) && _is_white(ts[cut - 1]) && (ti->text_props.text_len > 0))
2626 {
2627 if (cut - 1 > 0)
2628 {
2629 size_t white_cut = cut - 1;
2630 white_ti = _layout_text_item_new(c, ti->parent.format);
2631 white_ti->parent.text_node = ti->parent.text_node;
2632 white_ti->parent.text_pos = ti->parent.text_pos + white_cut;
2633 white_ti->parent.merge = EINA_TRUE;
2634 white_ti->parent.visually_deleted = EINA_TRUE;
2635
2636 evas_common_text_props_split(&ti->text_props,
2637 &white_ti->text_props, white_cut);
2638 _layout_text_add_logical_item(c, white_ti, lti);
2639 }
2640 else
2641 {
2642 /* Mark this one as the visually deleted. */
2643 ti->parent.visually_deleted = EINA_TRUE;
2644 }
2645 }
2646
2647 if (new_ti || white_ti)
2648 {
2649 _text_item_update_sizes(c, ti);
2650 }
2651 return new_ti;
2652}
2653
2654/**
2655 * @internal
2656 * Merge item2 into item1 and free item2.
2657 *
2658 * @param c the context to work on - Not NULL.
2659 * @param item1 the item to copy to
2660 * @param item2 the item to copy from
2661 */
2662static void
2663_layout_item_merge_and_free(Ctxt *c,
2664 Evas_Object_Textblock_Text_Item *item1,
2665 Evas_Object_Textblock_Text_Item *item2)
2666{
2667 evas_common_text_props_merge(&item1->text_props,
2668 &item2->text_props);
2669
2670 _text_item_update_sizes(c, item1);
2671
2672 item1->parent.merge = EINA_FALSE;
2673 item1->parent.visually_deleted = EINA_FALSE;
2674
2675 _item_free(c->obj, NULL, _ITEM(item2));
2676}
2677
2678/**
2679 * @internal
2680 * Calculates an item's size.
2681 *
2682 * @param c the context
2683 * @param it the item itself.
2684 */
2685static void
2686_text_item_update_sizes(Ctxt *c, Evas_Object_Textblock_Text_Item *ti)
2687{
2688 int tw, th, inset, advw;
2689 const Evas_Object_Textblock_Format *fmt = ti->parent.format;
2690 int shad_sz = 0, shad_dst = 0, out_sz = 0;
2691 int dx = 0, minx = 0, maxx = 0, shx1, shx2;
2692
2693 tw = th = 0;
2694 if (fmt->font.font)
2695 c->ENFN->font_string_size_get(c->ENDT, fmt->font.font,
2696 &ti->text_props, &tw, &th);
2697 inset = 0;
2698 if (fmt->font.font)
2699 inset = c->ENFN->font_inset_get(c->ENDT, fmt->font.font,
2700 &ti->text_props);
2701 advw = 0;
2702 if (fmt->font.font)
2703 advw = c->ENFN->font_h_advance_get(c->ENDT, fmt->font.font,
2704 &ti->text_props);
2705
2706
2707 /* These adjustments are calculated and thus heavily linked to those in
2708 * textblock_render!!! Don't change one without the other. */
2709
2710 switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_BASIC)
2711 {
2712 case EVAS_TEXT_STYLE_SHADOW:
2713 shad_dst = 1;
2714 break;
2715 case EVAS_TEXT_STYLE_OUTLINE_SHADOW:
2716 case EVAS_TEXT_STYLE_FAR_SHADOW:
2717 shad_dst = 2;
2718 out_sz = 1;
2719 break;
2720 case EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW:
2721 shad_dst = 1;
2722 shad_sz = 2;
2723 out_sz = 1;
2724 break;
2725 case EVAS_TEXT_STYLE_FAR_SOFT_SHADOW:
2726 shad_dst = 2;
2727 shad_sz = 2;
2728 break;
2729 case EVAS_TEXT_STYLE_SOFT_SHADOW:
2730 shad_dst = 1;
2731 shad_sz = 2;
2732 break;
2733 case EVAS_TEXT_STYLE_GLOW:
2734 case EVAS_TEXT_STYLE_SOFT_OUTLINE:
2735 out_sz = 2;
2736 break;
2737 case EVAS_TEXT_STYLE_OUTLINE:
2738 out_sz = 1;
2739 break;
2740 default:
2741 break;
2742 }
2743 switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_SHADOW_DIRECTION)
2744 {
2745 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT:
2746 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT:
2747 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT:
2748 dx = -1;
2749 break;
2750 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT:
2751 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT:
2752 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT:
2753 dx = 1;
2754 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP:
2755 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM:
2756 default:
2757 dx = 0;
2758 break;
2759 }
2760 minx = -out_sz;
2761 maxx = out_sz;
2762 shx1 = dx * shad_dst;
2763 shx1 -= shad_sz;
2764 shx2 = dx * shad_dst;
2765 shx2 += shad_sz;
2766 if (shx1 < minx) minx = shx1;
2767 if (shx2 > maxx) maxx = shx2;
2768 inset += -minx;
2769 ti->x_adjustment = maxx - minx;
2770
2771 ti->inset = inset;
2772 ti->parent.w = tw + ti->x_adjustment;
2773 ti->parent.h = th;
2774 ti->parent.adv = advw;
2775 ti->parent.x = 0;
2776}
2777
2778/**
2779 * @internal
2780 * Adds the item to the list, updates the item's properties (e.g, x,w,h)
2781 *
2782 * @param c the context
2783 * @param it the item itself.
2784 * @param rel item ti will be appened after, NULL = last.
2785 */
2786static void
2787_layout_text_add_logical_item(Ctxt *c, Evas_Object_Textblock_Text_Item *ti,
2788 Eina_List *rel)
2789{
2790 _text_item_update_sizes(c, ti);
2791
2792 c->par->logical_items = eina_list_append_relative_list(
2793 c->par->logical_items, ti, rel);
2794}
2795
2796/**
2797 * @internal
2798 * Appends the text from node n starting at start ending at off to the layout.
2799 * It uses the fmt for the formatting.
2800 *
2801 * @param c the current context- NOT NULL.
2802 * @param fmt the format to use.
2803 * @param n the text node. - Not null.
2804 * @param start the start position. - in range.
2805 * @param off the offset - start + offset in range. if offset is -1, it'll add everything to the end of the string if offset = 0 it'll return with doing nothing.
2806 * @param repch a replacement char to print instead of the original string, for example, * when working with passwords.
2807 */
2808static void
2809_layout_text_append(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Node_Text *n, int start, int off, const char *repch)
2810{
2811 const Eina_Unicode *str = EINA_UNICODE_EMPTY_STRING;
2812 const Eina_Unicode *tbase;
2813 Evas_Object_Textblock_Text_Item *ti;
2814 size_t cur_len = 0;
2815 Eina_Unicode urepch = 0;
2816
2817 /* prepare a working copy of the string, either filled by the repch or
2818 * filled with the true values */
2819 if (n)
2820 {
2821 int len;
2822 int orig_off = off;
2823
2824 /* Figure out if we want to bail, work with an empty string,
2825 * or continue with a slice of the passed string */
2826 len = eina_ustrbuf_length_get(n->unicode);
2827 if (off == 0) return;
2828 else if (off < 0) off = len - start;
2829
2830 if (start < 0)
2831 {
2832 start = 0;
2833 }
2834 else if ((start == 0) && (off == 0) && (orig_off == -1))
2835 {
2836 /* Special case that means that we need to add an empty
2837 * item */
2838 str = EINA_UNICODE_EMPTY_STRING;
2839 goto skip;
2840 }
2841 else if ((start >= len) || (start + off > len))
2842 {
2843 return;
2844 }
2845
2846 /* If we work with a replacement char, create a string which is the same
2847 * but with replacement chars instead of regular chars. */
2848 if ((fmt->password) && (repch) && (eina_ustrbuf_length_get(n->unicode)))
2849 {
2850 int i, ind;
2851 Eina_Unicode *ptr;
2852
2853 tbase = str = ptr = alloca((off + 1) * sizeof(Eina_Unicode));
2854 ind = 0;
2855 urepch = eina_unicode_utf8_get_next(repch, &ind);
2856 for (i = 0 ; i < off; ptr++, i++)
2857 *ptr = urepch;
2858 *ptr = 0;
2859 }
2860 /* Use the string, just cut the relevant parts */
2861 else
2862 {
2863 str = eina_ustrbuf_string_get(n->unicode) + start;
2864 }
2865
2866 cur_len = off;
2867 }
2868
2869skip:
2870 tbase = str;
2871
2872 /* If there's no parent text node, only create an empty item */
2873 if (!n)
2874 {
2875 ti = _layout_text_item_new(c, fmt);
2876 ti->parent.text_node = NULL;
2877 ti->parent.text_pos = 0;
2878 _layout_text_add_logical_item(c, ti, NULL);
2879
2880 return;
2881 }
2882
2883 while (cur_len > 0)
2884 {
2885 Evas_Font_Instance *script_fi = NULL;
2886 int script_len, tmp_cut;
2887 Evas_Script_Type script;
2888
2889 script_len = cur_len;
2890
2891 tmp_cut = evas_common_language_script_end_of_run_get(str,
2892 c->par->bidi_props, start + str - tbase, script_len);
2893 if (tmp_cut > 0)
2894 {
2895 script_len = tmp_cut;
2896 }
2897 cur_len -= script_len;
2898
2899 script = evas_common_language_script_type_get(str, script_len);
2900
2901
2902 while (script_len > 0)
2903 {
2904 Evas_Font_Instance *cur_fi = NULL;
2905 int run_len = script_len;
2906 ti = _layout_text_item_new(c, fmt);
2907 ti->parent.text_node = n;
2908 ti->parent.text_pos = start + str - tbase;
2909
2910 if (ti->parent.format->font.font)
2911 {
2912 run_len = c->ENFN->font_run_end_get(c->ENDT,
2913 ti->parent.format->font.font, &script_fi, &cur_fi,
2914 script, str, script_len);
2915 }
2916
2917 evas_common_text_props_bidi_set(&ti->text_props,
2918 c->par->bidi_props, ti->parent.text_pos);
2919 evas_common_text_props_script_set(&ti->text_props, script);
2920
2921 if (cur_fi)
2922 {
2923 c->ENFN->font_text_props_info_create(c->ENDT,
2924 cur_fi, str, &ti->text_props, c->par->bidi_props,
2925 ti->parent.text_pos, run_len);
2926 }
2927 str += run_len;
2928 script_len -= run_len;
2929
2930 _layout_text_add_logical_item(c, ti, NULL);
2931 }
2932 }
2933}
2934
2935/**
2936 * @internal
2937 * Add a format item from the format node n and the item item.
2938 *
2939 * @param c the current context- NOT NULL.
2940 * @param n the source format node - not null.
2941 * @param item the format text.
2942 *
2943 * @return the new format item.
2944 */
2945static Evas_Object_Textblock_Format_Item *
2946_layout_format_item_add(Ctxt *c, Evas_Object_Textblock_Node_Format *n, const char *item, Evas_Object_Textblock_Format *fmt)
2947{
2948 Evas_Object_Textblock_Format_Item *fi;
2949
2950 fi = calloc(1, sizeof(Evas_Object_Textblock_Format_Item));
2951 fi->item = eina_stringshare_add(item);
2952 fi->parent.type = EVAS_TEXTBLOCK_ITEM_FORMAT;
2953 fi->parent.format = fmt;
2954 fi->parent.format->ref++;
2955 c->par->logical_items = eina_list_append(c->par->logical_items, fi);
2956 if (n)
2957 {
2958 fi->parent.text_node = n->text_node;
2959 /* FIXME: make it more efficient */
2960 fi->parent.text_pos = _evas_textblock_node_format_pos_get(n);
2961#ifdef BIDI_SUPPORT
2962 fi->bidi_dir = (evas_bidi_is_rtl_char(
2963 c->par->bidi_props,
2964 0,
2965 fi->parent.text_pos)) ?
2966 EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR;
2967#else
2968 fi->bidi_dir = EVAS_BIDI_DIRECTION_LTR;
2969#endif
2970 }
2971 return fi;
2972}
2973
2974/**
2975 * @internal
2976 * Should be call after we finish filling a format.
2977 * FIXME: doc.
2978 */
2979static void
2980_format_finalize(Evas_Object *obj, Evas_Object_Textblock_Format *fmt)
2981{
2982 void *of;
2983
2984 of = fmt->font.font;
2985
2986 fmt->font.font = evas_font_load(obj->layer->evas, fmt->font.fdesc,
2987 fmt->font.source, (int)(((double) fmt->font.size) * obj->cur.scale));
2988 if (of) evas_font_free(obj->layer->evas, of);
2989}
2990
2991/**
2992 * @internal
2993 * Returns true if the item is a tab
2994 * @def _IS_TAB(item)
2995 */
2996#define _IS_TAB(item) \
2997 (!strcmp(item, "\t") || !strcmp(item, "\\t"))
2998/**
2999 * @internal
3000 * Returns true if the item is a line spearator, false otherwise
3001 * @def _IS_LINE_SEPARATOR(item)
3002 */
3003#define _IS_LINE_SEPARATOR(item) \
3004 (!strcmp(item, "\n") || !strcmp(item, "\\n"))
3005/**
3006 * @internal
3007 * Returns true if the item is a paragraph separator, false otherwise
3008 * @def _IS_PARAGRAPH_SEPARATOR(item)
3009 */
3010#define _IS_PARAGRAPH_SEPARATOR(o, item) \
3011 (!strcmp(item, "ps") || \
3012 (o->legacy_newline && _IS_LINE_SEPARATOR(item))) /* Paragraph separator */
3013
3014/**
3015 * @internal
3016 * Handles a format by processing a format node. It returns the relevant format
3017 * through _fmt and updates the padding through style_pad_*. If needed,
3018 * it creates a format item.
3019 *
3020 * @param obj the evas object - NOT NULL.
3021 * @param c the current context- NOT NULL.
3022 * @param _fmt the format that holds the result.
3023 * @param n the source format node - not null.
3024 * @param style_pad_l the pad to update.
3025 * @param style_pad_r the pad to update.
3026 * @param style_pad_t the pad to update.
3027 * @param style_pad_b the pad to update.
3028 * @param create_item Create a new format item if true, only process otherwise.
3029 */
3030static void
3031_layout_do_format(const Evas_Object *obj __UNUSED__, Ctxt *c,
3032 Evas_Object_Textblock_Format **_fmt, Evas_Object_Textblock_Node_Format *n,
3033 int *style_pad_l, int *style_pad_r, int *style_pad_t, int *style_pad_b,
3034 Eina_Bool create_item)
3035{
3036 Evas_Object_Textblock_Format *fmt = *_fmt;
3037 /* FIXME: comment the algo */
3038
3039 const char *s;
3040 const char *item;
3041 int handled = 0;
3042
3043 s = n->format;
3044 if (!strncmp(s, "+ item ", 7))
3045 {
3046 // one of:
3047 // item size=20x10 href=name
3048 // item relsize=20x10 href=name
3049 // item abssize=20x10 href=name
3050 //
3051 // optional arguments:
3052 // vsize=full
3053 // vsize=ascent
3054 //
3055 // size == item size (modifies line size) - can be multiplied by
3056 // scale factor
3057 // relsize == relative size (height is current font height, width
3058 // modified accordingly keeping aspect)
3059 // abssize == absolute size (modifies line size) - never mulitplied by
3060 // scale factor
3061 // href == name of item - to be found and matched later and used for
3062 // positioning
3063 Evas_Object_Textblock_Format_Item *fi;
3064 int w = 1, h = 1;
3065 int vsize = 0, size = 0;
3066 char *p;
3067
3068 // don't care
3069 //href = strstr(s, " href=");
3070 p = strstr(s, " vsize=");
3071 if (p)
3072 {
3073 p += 7;
3074 if (!strncmp(p, "full", 4)) vsize = VSIZE_FULL;
3075 else if (!strncmp(p, "ascent", 6)) vsize = VSIZE_ASCENT;
3076 }
3077 p = strstr(s, " size=");
3078 if (p)
3079 {
3080 p += 6;
3081 if (sscanf(p, "%ix%i", &w, &h) == 2)
3082 {
3083 /* this is handled somewhere else because it depends
3084 * on the current scaling factor of the object which
3085 * may change and break because the results of this
3086 * function are cached */
3087 size = SIZE;
3088 }
3089 }
3090 else
3091 {
3092 p = strstr(s, " absize=");
3093 if (p)
3094 {
3095 p += 8;
3096 if (sscanf(p, "%ix%i", &w, &h) == 2)
3097 {
3098 size = SIZE_ABS;
3099 }
3100 }
3101 else
3102 {
3103 p = strstr(s, " relsize=");
3104 if (p)
3105 {
3106 /* this is handled somewhere else because it depends
3107 * on the line it resides in, which is not defined
3108 * at this point and will change anyway, which will
3109 * break because the results of this function are
3110 * cached */
3111 size = SIZE_REL;
3112 }
3113 }
3114 }
3115
3116 if (create_item)
3117 {
3118 fi = _layout_format_item_add(c, n, s, fmt);
3119 fi->vsize = vsize;
3120 fi->size = size;
3121 fi->formatme = 1;
3122 /* For formats items it's usually
3123 the same, we don't handle the
3124 special cases yet. */
3125 fi->parent.w = fi->parent.adv = w;
3126 fi->parent.h = h;
3127 }
3128 /* Not sure if it's the best handling, but will do it for now. */
3129 fmt = _layout_format_push(c, fmt, n);
3130 handled = 1;
3131 }
3132
3133 if (!handled)
3134 {
3135 Eina_Bool push_fmt = EINA_FALSE;
3136 if (s[0] == '+')
3137 {
3138 fmt = _layout_format_push(c, fmt, n);
3139 s++;
3140 push_fmt = EINA_TRUE;
3141 }
3142 else if (s[0] == '-')
3143 {
3144 fmt = _layout_format_pop(c, n->orig_format);
3145 s++;
3146 }
3147 while ((item = _format_parse(&s)))
3148 {
3149 if (_format_is_param(item))
3150 {
3151 /* Only handle it if it's a push format, otherwise,
3152 * don't let overwrite the format stack.. */
3153 if (push_fmt)
3154 {
3155 _layout_format_value_handle(c, fmt, item);
3156 }
3157 }
3158 else if (create_item)
3159 {
3160 if ((_IS_PARAGRAPH_SEPARATOR(c->o, item)) ||
3161 (_IS_LINE_SEPARATOR(item)))
3162 {
3163 Evas_Object_Textblock_Format_Item *fi;
3164
3165 fi = _layout_format_item_add(c, n, item, fmt);
3166
3167 fi->parent.w = fi->parent.adv = 0;
3168 }
3169 else if ((!strcmp(item, "\t")) || (!strcmp(item, "\\t")))
3170 {
3171 Evas_Object_Textblock_Format_Item *fi;
3172
3173 fi = _layout_format_item_add(c, n, item, fmt);
3174 fi->parent.w = fi->parent.adv = fmt->tabstops;
3175 fi->formatme = 1;
3176 }
3177 }
3178 }
3179 _format_finalize(c->obj, fmt);
3180 }
3181
3182 {
3183 Evas_Coord pad_l, pad_r, pad_t, pad_b;
3184 pad_l = pad_r = pad_t = pad_b = 0;
3185 evas_text_style_pad_get(fmt->style, &pad_l, &pad_r, &pad_t, &pad_b);
3186 if (pad_l > *style_pad_l) *style_pad_l = pad_l;
3187 if (pad_r > *style_pad_r) *style_pad_r = pad_r;
3188 if (pad_t > *style_pad_t) *style_pad_t = pad_t;
3189 if (pad_b > *style_pad_b) *style_pad_b = pad_b;
3190 }
3191
3192 if (fmt->underline2)
3193 c->have_underline2 = 1;
3194 else if (fmt->underline || fmt->underline_dash)
3195 c->have_underline = 1;
3196 *_fmt = fmt;
3197}
3198
3199static void
3200_layout_update_par(Ctxt *c)
3201{
3202 Evas_Object_Textblock_Paragraph *last_par;
3203 last_par = (Evas_Object_Textblock_Paragraph *)
3204 EINA_INLIST_GET(c->par)->prev;
3205 if (last_par)
3206 {
3207 c->par->y = last_par->y + last_par->h;
3208 }
3209 else
3210 {
3211 c->par->y = 0;
3212 }
3213}
3214
3215/* -1 means no wrap */
3216static int
3217_layout_get_charwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt,
3218 const Evas_Object_Textblock_Text_Item *ti, size_t line_start,
3219 const char *breaks)
3220{
3221 int wrap;
3222 size_t uwrap;
3223 size_t len = eina_ustrbuf_length_get(ti->parent.text_node->unicode);
3224 /* Currently not being used, because it doesn't contain relevant
3225 * information */
3226 (void) breaks;
3227
3228 {
3229 wrap = _layout_text_cutoff_get(c, fmt, ti);
3230 if (wrap < 0)
3231 return -1;
3232 uwrap = (size_t) wrap + ti->parent.text_pos;
3233 }
3234
3235
3236 if (uwrap == line_start)
3237 {
3238 uwrap = ti->parent.text_pos +
3239 (size_t) evas_common_text_props_cluster_next(&ti->text_props, wrap);
3240 }
3241 if ((uwrap <= line_start) || (uwrap > len))
3242 return -1;
3243
3244 return uwrap;
3245}
3246
3247/* -1 means no wrap */
3248#ifdef HAVE_LINEBREAK
3249
3250/* Allow break means: if we can break after the current char */
3251#define ALLOW_BREAK(i) \
3252 (breaks[i] <= LINEBREAK_ALLOWBREAK)
3253
3254#else
3255
3256#define ALLOW_BREAK(i) \
3257 (_is_white(str[i]))
3258
3259#endif
3260static int
3261_layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt,
3262 const Evas_Object_Textblock_Text_Item *ti, Eina_Bool mixed_wrap,
3263 size_t line_start, const char *breaks)
3264{
3265 Eina_Bool wrap_after = EINA_FALSE;
3266 size_t wrap;
3267 size_t orig_wrap;
3268 const Eina_Unicode *str = eina_ustrbuf_string_get(
3269 ti->parent.text_node->unicode);
3270 int item_start = ti->parent.text_pos;
3271 size_t len = eina_ustrbuf_length_get(ti->parent.text_node->unicode);
3272#ifndef HAVE_LINEBREAK
3273 /* Not used without liblinebreak ATM. */
3274 (void) breaks;
3275#endif
3276
3277 {
3278 int swrap = -1;
3279 swrap = _layout_text_cutoff_get(c, fmt, ti);
3280 /* Avoiding too small textblocks to even contain one char.
3281 * FIXME: This can cause breaking inside ligatures. */
3282
3283 if (swrap < 0)
3284 return -1;
3285
3286 orig_wrap = wrap = swrap + item_start;
3287 }
3288
3289 if (wrap > line_start)
3290 {
3291 /* The wrapping point found is the first char of the next string
3292 the rest works on the last char of the previous string.
3293 If it's a whitespace, then it's ok, and no need to go back
3294 because we'll remove it anyway. */
3295 if (!_is_white(str[wrap]))
3296 MOVE_PREV_UNTIL(line_start, wrap);
3297 /* If there's a breakable point inside the text, scan backwards until
3298 * we find it */
3299 while (wrap > line_start)
3300 {
3301 if (ALLOW_BREAK(wrap))
3302 break;
3303 wrap--;
3304 }
3305
3306 if ((wrap > line_start) ||
3307 ((wrap == line_start) && (ALLOW_BREAK(wrap)) && (wrap < len)))
3308 {
3309 /* We found a suitable wrapping point, break here. */
3310 MOVE_NEXT_UNTIL(len, wrap);
3311 return wrap;
3312 }
3313 else
3314 {
3315 if (mixed_wrap)
3316 {
3317 return ((orig_wrap >= line_start) && (orig_wrap < len)) ?
3318 ((int) orig_wrap) : -1;
3319 }
3320 else
3321 {
3322 /* Scan forward to find the next wrapping point */
3323 wrap = orig_wrap;
3324 wrap_after = EINA_TRUE;
3325 }
3326 }
3327 }
3328
3329 /* If we need to find the position after the cutting point */
3330 if ((wrap == line_start) || (wrap_after))
3331 {
3332 if (mixed_wrap)
3333 {
3334 return _layout_get_charwrap(c, fmt, ti,
3335 line_start, breaks);
3336 }
3337 else
3338 {
3339 while (wrap < len)
3340 {
3341 if (ALLOW_BREAK(wrap))
3342 break;
3343 wrap++;
3344 }
3345
3346
3347 if ((wrap < len) && (wrap > line_start))
3348 {
3349 MOVE_NEXT_UNTIL(len, wrap);
3350 return wrap;
3351 }
3352 else
3353 {
3354 return -1;
3355 }
3356 }
3357 }
3358
3359 return -1;
3360}
3361
3362/* -1 means no wrap */
3363static int
3364_layout_get_wordwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt,
3365 const Evas_Object_Textblock_Text_Item *ti, size_t line_start,
3366 const char *breaks)
3367{
3368 return _layout_get_word_mixwrap_common(c, fmt, ti, EINA_FALSE, line_start,
3369 breaks);
3370}
3371
3372/* -1 means no wrap */
3373static int
3374_layout_get_mixedwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt,
3375 const Evas_Object_Textblock_Text_Item *ti, size_t line_start,
3376 const char *breaks)
3377{
3378 return _layout_get_word_mixwrap_common(c, fmt, ti, EINA_TRUE, line_start,
3379 breaks);
3380}
3381
3382/* Should be moved inside _layout_ellipsis_item_new once we fix the hack in
3383 * textblock render */
3384static const Eina_Unicode _ellip_str[2] = { 0x2026, '\0' };
3385
3386static Evas_Object_Textblock_Text_Item *
3387_layout_ellipsis_item_new(Ctxt *c, const Evas_Object_Textblock_Item *cur_it)
3388{
3389 Evas_Object_Textblock_Text_Item *ellip_ti;
3390 Evas_Script_Type script;
3391 Evas_Font_Instance *script_fi = NULL, *cur_fi;
3392 size_t len = 1; /* The length of _ellip_str */
3393
3394 /* We can free it here, cause there's only one ellipsis item per tb. */
3395 if (c->o->ellip_ti) _item_free(c->obj, NULL, _ITEM(c->o->ellip_ti));
3396 c->o->ellip_ti = ellip_ti = _layout_text_item_new(c,
3397 eina_list_data_get(eina_list_last(c->format_stack)));
3398 ellip_ti->parent.text_node = cur_it->text_node;
3399 ellip_ti->parent.text_pos = cur_it->text_pos;
3400 script = evas_common_language_script_type_get(_ellip_str, len);
3401
3402 evas_common_text_props_bidi_set(&ellip_ti->text_props,
3403 c->par->bidi_props, ellip_ti->parent.text_pos);
3404 evas_common_text_props_script_set (&ellip_ti->text_props, script);
3405
3406 if (ellip_ti->parent.format->font.font)
3407 {
3408 /* It's only 1 char anyway, we don't need the run end. */
3409 (void) c->ENFN->font_run_end_get(c->ENDT,
3410 ellip_ti->parent.format->font.font, &script_fi, &cur_fi,
3411 script, _ellip_str, len);
3412
3413 c->ENFN->font_text_props_info_create(c->ENDT,
3414 cur_fi, _ellip_str, &ellip_ti->text_props,
3415 c->par->bidi_props, ellip_ti->parent.text_pos, len);
3416 }
3417
3418 _text_item_update_sizes(c, ellip_ti);
3419
3420 if (cur_it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
3421 {
3422 ellip_ti->parent.text_pos += _ITEM_TEXT(cur_it)->text_props.text_len
3423 - 1;
3424 }
3425 else
3426 {
3427 ellip_ti->parent.text_pos++;
3428 }
3429
3430 return ellip_ti;
3431}
3432
3433/**
3434 * @internel
3435 * Handle ellipsis
3436 */
3437static inline void
3438_layout_handle_ellipsis(Ctxt *c, Evas_Object_Textblock_Item *it, Eina_List *i)
3439{
3440 Evas_Object_Textblock_Text_Item *ellip_ti;
3441 Evas_Object_Textblock_Item *last_it;
3442 Evas_Coord save_cx;
3443 int wrap;
3444 ellip_ti = _layout_ellipsis_item_new(c, it);
3445 last_it = it;
3446
3447 save_cx = c->x;
3448 c->w -= ellip_ti->parent.w;
3449
3450 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
3451 {
3452 Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it);
3453
3454 wrap = _layout_text_cutoff_get(c, last_it->format, ti);
3455 if ((wrap > 0) && !IS_AT_END(ti, (size_t) wrap))
3456 {
3457 _layout_item_text_split_strip_white(c, ti, i, wrap);
3458 }
3459 else if ((wrap == 0) && (c->ln->items))
3460 {
3461 last_it = _ITEM(EINA_INLIST_GET(c->ln->items)->last);
3462 }
3463 }
3464 else if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT)
3465 {
3466 /* We don't want to add this format item. */
3467 last_it = NULL;
3468 }
3469
3470 c->x = save_cx;
3471 c->w += ellip_ti->parent.w;
3472 /* If we should add this item, do it */
3473 if (last_it == it)
3474 {
3475 c->ln->items = (Evas_Object_Textblock_Item *)
3476 eina_inlist_append(EINA_INLIST_GET(c->ln->items),
3477 EINA_INLIST_GET(it));
3478 if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT)
3479 {
3480 Evas_Object_Textblock_Format_Item *fi;
3481 fi = _ITEM_FORMAT(it);
3482 fi->y = c->y;
3483 }
3484 }
3485 c->ln->items = (Evas_Object_Textblock_Item *)
3486 eina_inlist_append(EINA_INLIST_GET(c->ln->items),
3487 EINA_INLIST_GET(_ITEM(ellip_ti)));
3488 _layout_line_finalize(c, ellip_ti->parent.format);
3489}
3490
3491#ifdef BIDI_SUPPORT
3492static void
3493_layout_paragraph_reorder_lines(Evas_Object_Textblock_Paragraph *par)
3494{
3495 Evas_Object_Textblock_Line *ln;
3496
3497 EINA_INLIST_FOREACH(EINA_INLIST_GET(par->lines), ln)
3498 {
3499 _layout_line_reorder(ln);
3500 }
3501}
3502#endif
3503
3504static void
3505_layout_paragraph_render(Evas_Object_Textblock *o,
3506 Evas_Object_Textblock_Paragraph *par)
3507{
3508 if (par->rendered)
3509 return;
3510 par->rendered = EINA_TRUE;
3511
3512#ifdef BIDI_SUPPORT
3513 if (par->is_bidi)
3514 {
3515 _layout_update_bidi_props(o, par);
3516 _layout_paragraph_reorder_lines(par);
3517 /* Clear the bidi props because we don't need them anymore. */
3518 if (par->bidi_props)
3519 {
3520 evas_bidi_paragraph_props_unref(par->bidi_props);
3521 par->bidi_props = NULL;
3522 }
3523 }
3524#endif
3525}
3526
3527/* 0 means go ahead, 1 means break without an error, 2 means
3528 * break with an error, should probably clean this a bit (enum/macro)
3529 * FIXME ^ */
3530static int
3531_layout_par(Ctxt *c)
3532{
3533 Evas_Object_Textblock_Item *it;
3534 Eina_List *i;
3535 int ret = 0;
3536 int wrap = -1;
3537 char *line_breaks = NULL;
3538
3539 if (!c->par->logical_items)
3540 return 2;
3541
3542 /* We want to show it. */
3543 c->par->visible = 1;
3544
3545 /* Check if we need to skip this paragraph because it's already layouted
3546 * correctly, and mark handled nodes as dirty. */
3547 c->par->line_no = c->line_no;
3548
3549 if (c->par->text_node)
3550 {
3551 /* Skip this paragraph if width is the same, there is no ellipsis
3552 * and we aren't just calculating. */
3553 if (!c->par->text_node->is_new && !c->par->text_node->dirty &&
3554 !c->width_changed && c->par->lines &&
3555 !c->o->have_ellipsis)
3556 {
3557 Evas_Object_Textblock_Line *ln;
3558 /* Update c->line_no */
3559 ln = (Evas_Object_Textblock_Line *)
3560 EINA_INLIST_GET(c->par->lines)->last;
3561 if (ln)
3562 c->line_no = c->par->line_no + ln->line_no + 1;
3563 return 0;
3564 }
3565 c->par->text_node->dirty = EINA_FALSE;
3566 c->par->text_node->is_new = EINA_FALSE;
3567 c->par->rendered = EINA_FALSE;
3568
3569 /* Merge back and clear the paragraph */
3570 {
3571 Eina_List *itr, *itr_next;
3572 Evas_Object_Textblock_Item *ititr, *prev_it = NULL;
3573 _paragraph_clear(c->obj, c->par);
3574 EINA_LIST_FOREACH_SAFE(c->par->logical_items, itr, itr_next, ititr)
3575 {
3576 if (ititr->merge && prev_it &&
3577 (prev_it->type == EVAS_TEXTBLOCK_ITEM_TEXT) &&
3578 (ititr->type == EVAS_TEXTBLOCK_ITEM_TEXT))
3579 {
3580 _layout_item_merge_and_free(c, _ITEM_TEXT(prev_it),
3581 _ITEM_TEXT(ititr));
3582 c->par->logical_items =
3583 eina_list_remove_list(c->par->logical_items, itr);
3584 }
3585 else
3586 {
3587 prev_it = ititr;
3588 }
3589 }
3590 }
3591 }
3592
3593 c->y = c->par->y;
3594
3595 it = _ITEM(eina_list_data_get(c->par->logical_items));
3596 _layout_line_new(c, it->format);
3597 /* We walk on our own because we want to be able to add items from
3598 * inside the list and then walk them on the next iteration. */
3599 for (i = c->par->logical_items ; i ; )
3600 {
3601 int adv_line = 0;
3602 int redo_item = 0;
3603 it = _ITEM(eina_list_data_get(i));
3604 /* Skip visually deleted items */
3605 if (it->visually_deleted)
3606 {
3607 i = eina_list_next(i);
3608 continue;
3609 }
3610
3611 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
3612 {
3613 Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it);
3614 _layout_format_ascent_descent_adjust(c->obj, &c->maxascent,
3615 &c->maxdescent, ti->parent.format);
3616 }
3617 else
3618 {
3619 Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it);
3620 if (fi->formatme)
3621 {
3622 /* If there are no text items yet, calc ascent/descent
3623 * according to the current format. */
3624 if (c->maxascent + c->maxdescent == 0)
3625 _layout_format_ascent_descent_adjust(c->obj, &c->maxascent,
3626 &c->maxdescent, it->format);
3627
3628 _layout_calculate_format_item_size(c->obj, fi, &c->maxascent,
3629 &c->maxdescent, &fi->y, &fi->parent.w, &fi->parent.h);
3630 fi->parent.adv = fi->parent.w;
3631 }
3632 }
3633
3634
3635 /* Check if we need to wrap, i.e the text is bigger than the width,
3636 or we already found a wrap point. */
3637 if ((c->w >= 0) &&
3638 (((c->x + it->adv) >
3639 (c->w - c->o->style_pad.l - c->o->style_pad.r -
3640 c->marginl - c->marginr)) || (wrap > 0)))
3641 {
3642 /* Handle ellipsis here. If we don't have more width left
3643 * and no height left, or no more width left and no wrapping. */
3644 if ((it->format->ellipsis == 1.0) && (c->h >= 0) &&
3645 ((2 * it->h + c->y >
3646 c->h - c->o->style_pad.t - c->o->style_pad.b) ||
3647 (!it->format->wrap_word && !it->format->wrap_char &&
3648 !it->format->wrap_mixed)))
3649 {
3650 _layout_handle_ellipsis(c, it, i);
3651 ret = 1;
3652 goto end;
3653 }
3654 /* If we want to wrap and it's worth checking for wrapping
3655 * (i.e there's actually text). */
3656 else if ((it->format->wrap_word || it->format->wrap_char ||
3657 it->format->wrap_mixed) && it->text_node)
3658 {
3659 if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT)
3660 {
3661 /* Don't wrap if it's the only item */
3662 if (c->ln->items)
3663 {
3664 /*FIXME: I should handle format correctly,
3665 i.e verify we are allowed to break here */
3666 _layout_line_advance(c, it->format);
3667 wrap = -1;
3668 }
3669 }
3670 else
3671 {
3672 Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it);
3673 size_t line_start;
3674
3675#ifdef HAVE_LINEBREAK
3676 /* If we haven't calculated the linebreaks yet,
3677 * do */
3678 if (!line_breaks)
3679 {
3680 /* Only relevant in those cases */
3681 if (it->format->wrap_word || it->format->wrap_mixed)
3682 {
3683 const char *lang;
3684 lang = (it->format->font.fdesc) ?
3685 it->format->font.fdesc->lang : "";
3686 size_t len =
3687 eina_ustrbuf_length_get(
3688 it->text_node->unicode);
3689 line_breaks = malloc(len);
3690 set_linebreaks_utf32((const utf32_t *)
3691 eina_ustrbuf_string_get(
3692 it->text_node->unicode),
3693 len, lang, line_breaks);
3694 }
3695 }
3696#endif
3697 if (c->ln->items)
3698 line_start = c->ln->items->text_pos;
3699 else
3700 line_start = ti->parent.text_pos;
3701
3702 adv_line = 1;
3703 /* If we don't already have a wrap point from before */
3704 if (wrap < 0)
3705 {
3706 if (it->format->wrap_word)
3707 wrap = _layout_get_wordwrap(c, it->format, ti,
3708 line_start, line_breaks);
3709 else if (it->format->wrap_char)
3710 wrap = _layout_get_charwrap(c, it->format, ti,
3711 line_start, line_breaks);
3712 else if (it->format->wrap_mixed)
3713 wrap = _layout_get_mixedwrap(c, it->format, ti,
3714 line_start, line_breaks);
3715 else
3716 wrap = -1;
3717 }
3718
3719 /* If it's before the item, rollback and apply.
3720 if it's in the item, cut.
3721 If it's after the item, delay the cut */
3722 if (wrap > 0)
3723 {
3724 size_t uwrap = (size_t) wrap;
3725 if (uwrap < ti->parent.text_pos)
3726 {
3727 /* Rollback latest additions, and cut that
3728 item */
3729 i = eina_list_prev(i);
3730 it = eina_list_data_get(i);
3731 while (uwrap < it->text_pos)
3732 {
3733 c->ln->items = _ITEM(
3734 eina_inlist_remove(
3735 EINA_INLIST_GET(c->ln->items),
3736 EINA_INLIST_GET(it)));
3737 i = eina_list_prev(i);
3738 it = eina_list_data_get(i);
3739 }
3740 c->x = it->x;
3741 c->ln->items = _ITEM(
3742 eina_inlist_remove(
3743 EINA_INLIST_GET(c->ln->items),
3744 EINA_INLIST_GET(it)));
3745 continue;
3746 }
3747 /* If it points to the end, it means the previous
3748 * char is a whitespace we should remove, so this
3749 * is a wanted cutting point. */
3750 else if (uwrap > ti->parent.text_pos +
3751 ti->text_props.text_len)
3752 wrap = -1; /* Delay the cut in a smart way
3753 i.e use the item_pos as the line_start, because
3754 there's already no cut before*/
3755 else
3756 wrap -= ti->parent.text_pos; /* Cut here */
3757 }
3758
3759 if (wrap > 0)
3760 {
3761 _layout_item_text_split_strip_white(c, ti, i, wrap);
3762 }
3763 else if (wrap == 0)
3764 {
3765 /* Should wrap before the item */
3766 adv_line = 0;
3767 redo_item = 1;
3768 _layout_line_advance(c, it->format);
3769 }
3770 /* Reset wrap */
3771 wrap = -1;
3772 }
3773 }
3774 }
3775
3776 if (!redo_item && !it->visually_deleted)
3777 {
3778 c->ln->items = (Evas_Object_Textblock_Item *)
3779 eina_inlist_append(EINA_INLIST_GET(c->ln->items),
3780 EINA_INLIST_GET(it));
3781 if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT)
3782 {
3783 Evas_Object_Textblock_Format_Item *fi;
3784 fi = _ITEM_FORMAT(it);
3785 fi->y = c->y;
3786 /* If it's a newline, and we are not in newline compat
3787 * mode, or we are in newline compat mode, and this is
3788 * not used as a paragraph separator, advance */
3789 if (fi->item && _IS_LINE_SEPARATOR(fi->item) &&
3790 (!c->o->legacy_newline ||
3791 eina_list_next(i)))
3792 {
3793 adv_line = 1;
3794 }
3795 }
3796 c->x += it->adv;
3797 i = eina_list_next(i);
3798 }
3799 if (adv_line)
3800 {
3801 /* Each line is according to the first item in it, and here
3802 * i is already the next item (or the current if we redo it) */
3803 if (i)
3804 {
3805 it = _ITEM(eina_list_data_get(i));
3806 }
3807 _layout_line_advance(c, it->format);
3808 }
3809 }
3810 if (c->ln->items)
3811 {
3812 /* Here 'it' is the last format used */
3813 _layout_line_finalize(c, it->format);
3814 }
3815
3816end:
3817#ifdef HAVE_LINEBREAK
3818 if (line_breaks)
3819 free(line_breaks);
3820#endif
3821
3822 return ret;
3823}
3824
3825/**
3826 * @internal
3827 * Invalidate text nodes according to format changes
3828 * This goes through all the new format changes and marks the text nodes
3829 * that should be invalidated because of format changes.
3830 *
3831 * @param c the working context.
3832 */
3833static inline void
3834_format_changes_invalidate_text_nodes(Ctxt *c)
3835{
3836 Evas_Object_Textblock_Node_Format *fnode = c->o->format_nodes;
3837 Evas_Object_Textblock_Node_Text *start_n = NULL;
3838 Eina_List *fstack = NULL;
3839 int balance = 0;
3840 while (fnode)
3841 {
3842 if (fnode->is_new)
3843 {
3844 const char *fstr = fnode->orig_format;
3845 /* balance < 0 means we gave up and everything should be
3846 * invalidated */
3847 if (*fstr == '+')
3848 {
3849 balance++;
3850 if (!fstack)
3851 start_n = fnode->text_node;
3852 fstack = eina_list_prepend(fstack, fnode);
3853 }
3854 else if (*fstr == '-')
3855 {
3856 size_t fstr_len;
3857 /* Skip the '-' */
3858 fstr++;
3859 fstr_len = strlen(fstr);
3860 /* Generic popper, just pop */
3861 if (((fstr[0] == ' ') && !fstr[1]) || !fstr[0])
3862 {
3863 fstack = eina_list_remove_list(fstack, fstack);
3864 balance--;
3865 }
3866 /* Find the matching format and pop it, if the matching format
3867 * is out format, i.e the last one, pop and break. */
3868 else
3869 {
3870 Eina_List *i;
3871 Evas_Object_Textblock_Node_Format *fnode2;
3872 EINA_LIST_FOREACH(fstack, i, fnode2)
3873 {
3874 if (_FORMAT_IS_CLOSER_OF(
3875 fnode2->orig_format, fstr, fstr_len))
3876 {
3877 fstack = eina_list_remove_list(fstack, i);
3878 break;
3879 }
3880 }
3881 balance--;
3882 }
3883
3884 if (!fstack)
3885 {
3886 Evas_Object_Textblock_Node_Text *f_tnode =
3887 fnode->text_node;
3888 while (start_n)
3889 {
3890 start_n->dirty = EINA_TRUE;
3891 if (start_n == f_tnode)
3892 break;
3893 start_n =
3894 _NODE_TEXT(EINA_INLIST_GET(start_n)->next);
3895 }
3896 start_n = NULL;
3897 }
3898 }
3899 else if (!fnode->visible)
3900 balance = -1;
3901
3902 if (balance < 0)
3903 {
3904 /* if we don't already have a starting point, use the
3905 * current paragraph. */
3906 if (!start_n)
3907 start_n = fnode->text_node;
3908 break;
3909 }
3910 }
3911 fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
3912 }
3913
3914 if (balance != 0)
3915 {
3916 while (start_n)
3917 {
3918 start_n->dirty = EINA_TRUE;
3919 start_n = _NODE_TEXT(EINA_INLIST_GET(start_n)->next);
3920 }
3921 }
3922}
3923
3924
3925/** FIXME: Document */
3926static void
3927_layout_pre(Ctxt *c, int *style_pad_l, int *style_pad_r, int *style_pad_t,
3928 int *style_pad_b)
3929{
3930 Evas_Object *obj = c->obj;
3931 Evas_Object_Textblock *o = c->o;
3932 /* Mark text nodes as dirty if format have changed. */
3933 if (c->o->format_changed)
3934 {
3935 _format_changes_invalidate_text_nodes(c);
3936 }
3937
3938 if (o->content_changed)
3939 {
3940 Evas_Object_Textblock_Node_Text *n;
3941 c->o->have_ellipsis = 0;
3942 c->par = c->paragraphs = o->paragraphs;
3943 /* Go through all the text nodes to create the logical layout */
3944 EINA_INLIST_FOREACH(c->o->text_nodes, n)
3945 {
3946 Evas_Object_Textblock_Node_Format *fnode;
3947 size_t start;
3948 int off;
3949
3950 /* If it's not a new paragraph, either update it or skip it.
3951 * Remove all the paragraphs that were deleted */
3952 if (!n->is_new)
3953 {
3954 /* Remove all the deleted paragraphs at this point */
3955 while (c->par->text_node != n)
3956 {
3957 Evas_Object_Textblock_Paragraph *tmp_par =
3958 (Evas_Object_Textblock_Paragraph *)
3959 EINA_INLIST_GET(c->par)->next;
3960
3961 c->paragraphs = (Evas_Object_Textblock_Paragraph *)
3962 eina_inlist_remove(EINA_INLIST_GET(c->paragraphs),
3963 EINA_INLIST_GET(c->par));
3964 _paragraph_free(obj, c->par);
3965
3966 c->par = tmp_par;
3967 }
3968
3969 /* If it's dirty, remove and recreate, if it's clean,
3970 * skip to the next. */
3971 if (n->dirty)
3972 {
3973 Evas_Object_Textblock_Paragraph *prev_par = c->par;
3974
3975 _layout_paragraph_new(c, n, EINA_TRUE);
3976
3977 c->paragraphs = (Evas_Object_Textblock_Paragraph *)
3978 eina_inlist_remove(EINA_INLIST_GET(c->paragraphs),
3979 EINA_INLIST_GET(prev_par));
3980 _paragraph_free(obj, prev_par);
3981 }
3982 else
3983 {
3984 c->par = (Evas_Object_Textblock_Paragraph *)
3985 EINA_INLIST_GET(c->par)->next;
3986
3987 /* Update the format stack according to the node's
3988 * formats */
3989 fnode = n->format_node;
3990 while (fnode && (fnode->text_node == n))
3991 {
3992 /* Only do this if this actually changes format */
3993 if (fnode->format_change)
3994 _layout_do_format(obj, c, &c->fmt, fnode,
3995 style_pad_l, style_pad_r,
3996 style_pad_t, style_pad_b, EINA_FALSE);
3997 fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
3998 }
3999 continue;
4000 }
4001 }
4002 else
4003 {
4004 /* If it's a new paragraph, just add it. */
4005 _layout_paragraph_new(c, n, EINA_FALSE);
4006 }
4007
4008#ifdef BIDI_SUPPORT
4009 _layout_update_bidi_props(c->o, c->par);
4010#endif
4011
4012 /* For each text node to thorugh all of it's format nodes
4013 * append text from the start to the offset of the next format
4014 * using the last format got. if needed it also creates format
4015 * items this is the core algorithm of the layout mechanism.
4016 * Skip the unicode replacement chars when there are because
4017 * we don't want to print them. */
4018 fnode = n->format_node;
4019 start = off = 0;
4020 while (fnode && (fnode->text_node == n))
4021 {
4022 off += fnode->offset;
4023 /* No need to skip on the first run, or a non-visible one */
4024 _layout_text_append(c, c->fmt, n, start, off, o->repch);
4025 _layout_do_format(obj, c, &c->fmt, fnode, style_pad_l,
4026 style_pad_r, style_pad_t, style_pad_b, EINA_TRUE);
4027 if ((c->have_underline2) || (c->have_underline))
4028 {
4029 if (*style_pad_b < c->underline_extend)
4030 *style_pad_b = c->underline_extend;
4031 c->have_underline = 0;
4032 c->have_underline2 = 0;
4033 c->underline_extend = 0;
4034 }
4035 start += off;
4036 if (fnode->visible)
4037 {
4038 off = -1;
4039 start++;
4040 }
4041 else
4042 {
4043 off = 0;
4044 }
4045 fnode->is_new = EINA_FALSE;
4046 fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
4047 }
4048 _layout_text_append(c, c->fmt, n, start, -1, o->repch);
4049#ifdef BIDI_SUPPORT
4050 /* Clear the bidi props because we don't need them anymore. */
4051 if (c->par->bidi_props)
4052 {
4053 evas_bidi_paragraph_props_unref(c->par->bidi_props);
4054 c->par->bidi_props = NULL;
4055 }
4056#endif
4057 c->par = (Evas_Object_Textblock_Paragraph *)
4058 EINA_INLIST_GET(c->par)->next;
4059 }
4060
4061 /* Delete the rest of the layout paragraphs */
4062 while (c->par)
4063 {
4064 Evas_Object_Textblock_Paragraph *tmp_par =
4065 (Evas_Object_Textblock_Paragraph *)
4066 EINA_INLIST_GET(c->par)->next;
4067
4068 c->paragraphs = (Evas_Object_Textblock_Paragraph *)
4069 eina_inlist_remove(EINA_INLIST_GET(c->paragraphs),
4070 EINA_INLIST_GET(c->par));
4071 _paragraph_free(obj, c->par);
4072
4073 c->par = tmp_par;
4074 }
4075 o->paragraphs = c->paragraphs;
4076 c->par = NULL;
4077 }
4078
4079}
4080
4081/**
4082 * @internal
4083 * Create the layout from the nodes.
4084 *
4085 * @param obj the evas object - NOT NULL.
4086 * @param calc_only true if should only calc sizes false if should also create the layout.. It assumes native size is being calculated, doesn't support formatted size atm.
4087 * @param w the object's w, -1 means no wrapping (i.e infinite size)
4088 * @param h the object's h, -1 means inifinte size.
4089 * @param w_ret the object's calculated w.
4090 * @param h_ret the object's calculated h.
4091 */
4092static void
4093_layout(const Evas_Object *obj, int w, int h, int *w_ret, int *h_ret)
4094{
4095 Evas_Object_Textblock *o;
4096 Ctxt ctxt, *c;
4097 int style_pad_l = 0, style_pad_r = 0, style_pad_t = 0, style_pad_b = 0;
4098
4099 /* setup context */
4100 o = (Evas_Object_Textblock *)(obj->object_data);
4101 c = &ctxt;
4102 c->obj = (Evas_Object *)obj;
4103 c->o = o;
4104 c->paragraphs = c->par = NULL;
4105 c->format_stack = NULL;
4106 c->fmt = NULL;
4107 c->x = c->y = 0;
4108 c->w = w;
4109 c->h = h;
4110 c->wmax = c->hmax = 0;
4111 c->maxascent = c->maxdescent = 0;
4112 c->marginl = c->marginr = 0;
4113 c->have_underline = 0;
4114 c->have_underline2 = 0;
4115 c->underline_extend = 0;
4116 c->line_no = 0;
4117 c->align = 0.0;
4118 c->align_auto = EINA_TRUE;
4119 c->ln = NULL;
4120 c->width_changed = (obj->cur.geometry.w != o->last_w);
4121
4122 /* Start of logical layout creation */
4123 /* setup default base style */
4124 if ((c->o->style) && (c->o->style->default_tag))
4125 {
4126 c->fmt = _layout_format_push(c, NULL, NULL);
4127 _format_fill(c->obj, c->fmt, c->o->style->default_tag);
4128 _format_finalize(c->obj, c->fmt);
4129 }
4130 if (!c->fmt)
4131 {
4132 if (w_ret) *w_ret = 0;
4133 if (h_ret) *h_ret = 0;
4134 return;
4135 }
4136
4137 _layout_pre(c, &style_pad_l, &style_pad_r, &style_pad_t, &style_pad_b);
4138 c->paragraphs = o->paragraphs;
4139
4140 /* If there are no paragraphs, create the minimum needed,
4141 * if the last paragraph has no lines/text, create that as well */
4142 if (!c->paragraphs)
4143 {
4144 _layout_paragraph_new(c, NULL, EINA_TRUE);
4145 o->paragraphs = c->paragraphs;
4146 }
4147 c->par = (Evas_Object_Textblock_Paragraph *)
4148 EINA_INLIST_GET(c->paragraphs)->last;
4149 if (!c->par->logical_items)
4150 {
4151 Evas_Object_Textblock_Text_Item *ti;
4152 ti = _layout_text_item_new(c, c->fmt);
4153 ti->parent.text_node = c->par->text_node;
4154 ti->parent.text_pos = 0;
4155 _layout_text_add_logical_item(c, ti, NULL);
4156 }
4157
4158 /* End of logical layout creation */
4159
4160 /* Start of visual layout creation */
4161 {
4162 Evas_Object_Textblock_Paragraph *last_vis_par = NULL;
4163 int par_index_step = o->num_paragraphs / TEXTBLOCK_PAR_INDEX_SIZE;
4164 int par_count = 1; /* Force it to take the first one */
4165 int par_index_pos = 0;
4166
4167 if (par_index_step == 0) par_index_step = 1;
4168
4169 /* Clear all of the index */
4170 memset(o->par_index, 0, sizeof(o->par_index));
4171
4172 EINA_INLIST_FOREACH(c->paragraphs, c->par)
4173 {
4174 _layout_update_par(c);
4175
4176 /* Break if we should stop here. */
4177 if (_layout_par(c))
4178 {
4179 last_vis_par = c->par;
4180 break;
4181 }
4182
4183 if ((par_index_pos < TEXTBLOCK_PAR_INDEX_SIZE) && (--par_count == 0))
4184 {
4185 par_count = par_index_step;
4186
4187 o->par_index[par_index_pos++] = c->par;
4188 }
4189 }
4190
4191 /* Mark all the rest of the paragraphs as invisible */
4192 if (c->par)
4193 {
4194 c->par = (Evas_Object_Textblock_Paragraph *)
4195 EINA_INLIST_GET(c->par)->next;
4196 while (c->par)
4197 {
4198 c->par->visible = 0;
4199 c->par = (Evas_Object_Textblock_Paragraph *)
4200 EINA_INLIST_GET(c->par)->next;
4201 }
4202 }
4203
4204 /* Get the last visible paragraph in the layout */
4205 if (!last_vis_par && c->paragraphs)
4206 last_vis_par = (Evas_Object_Textblock_Paragraph *)
4207 EINA_INLIST_GET(c->paragraphs)->last;
4208
4209 if (last_vis_par)
4210 c->hmax = last_vis_par->y + last_vis_par->h;
4211 }
4212
4213 /* Clean the rest of the format stack */
4214 while (c->format_stack)
4215 {
4216 c->fmt = c->format_stack->data;
4217 c->format_stack = eina_list_remove_list(c->format_stack, c->format_stack);
4218 _format_unref_free(c->obj, c->fmt);
4219 }
4220
4221 if (w_ret) *w_ret = c->wmax;
4222 if (h_ret) *h_ret = c->hmax;
4223
4224 /* Vertically align the textblock */
4225 if ((o->valign > 0.0) && (c->h > c->hmax))
4226 {
4227 Evas_Coord adjustment = (c->h - c->hmax) * o->valign;
4228 Evas_Object_Textblock_Paragraph *par;
4229 EINA_INLIST_FOREACH(c->paragraphs, par)
4230 {
4231 par->y += adjustment;
4232 }
4233 }
4234
4235 if ((o->style_pad.l != style_pad_l) || (o->style_pad.r != style_pad_r) ||
4236 (o->style_pad.t != style_pad_t) || (o->style_pad.b != style_pad_b))
4237 {
4238 o->style_pad.l = style_pad_l;
4239 o->style_pad.r = style_pad_r;
4240 o->style_pad.t = style_pad_t;
4241 o->style_pad.b = style_pad_b;
4242 _paragraphs_clear(obj, c->paragraphs);
4243 _layout(obj, w, h, w_ret, h_ret);
4244 }
4245}
4246
4247/*
4248 * @internal
4249 * Relayout the object according to current object size.
4250 *
4251 * @param obj the evas object - NOT NULL.
4252 */
4253static void
4254_relayout(const Evas_Object *obj)
4255{
4256 Evas_Object_Textblock *o;
4257
4258 o = (Evas_Object_Textblock *)(obj->object_data);
4259 _layout(obj, obj->cur.geometry.w, obj->cur.geometry.h,
4260 &o->formatted.w, &o->formatted.h);
4261 o->formatted.valid = 1;
4262 o->last_w = obj->cur.geometry.w;
4263 o->last_h = obj->cur.geometry.h;
4264 o->changed = 0;
4265 o->content_changed = 0;
4266 o->format_changed = EINA_FALSE;
4267 o->redraw = 1;
4268}
4269
4270/**
4271 * @internal
4272 * Find the layout item and line that match the text node and position passed.
4273 *
4274 * @param obj the evas object - NOT NULL.
4275 * @param n the text node - Not null.
4276 * @param pos the position to look for - valid.
4277 * @param[out] lnr the line found - not null.
4278 * @param[out] tir the item found - not null.
4279 * @see _find_layout_format_item_line_match()
4280 */
4281static void
4282_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)
4283{
4284 Evas_Object_Textblock_Paragraph *found_par;
4285 Evas_Object_Textblock_Line *ln;
4286 Evas_Object_Textblock *o;
4287
4288 o = (Evas_Object_Textblock *)(obj->object_data);
4289 if (!o->formatted.valid) _relayout(obj);
4290
4291 found_par = n->par;
4292 if (found_par)
4293 {
4294 _layout_paragraph_render(o, found_par);
4295 EINA_INLIST_FOREACH(found_par->lines, ln)
4296 {
4297 Evas_Object_Textblock_Item *it;
4298
4299 EINA_INLIST_FOREACH(ln->items, it)
4300 {
4301 /* FIXME: p should be size_t, same goes for pos */
4302 int p = (int) it->text_pos;
4303
4304 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
4305 {
4306 Evas_Object_Textblock_Text_Item *ti =
4307 _ITEM_TEXT(it);
4308
4309 p += (int) ti->text_props.text_len;
4310 }
4311 else
4312 {
4313 p++;
4314 }
4315
4316 if (((pos >= (int) it->text_pos) && (pos < p)))
4317 {
4318 *lnr = ln;
4319 *itr = it;
4320 return;
4321 }
4322 else if (p == pos)
4323 {
4324 *lnr = ln;
4325 *itr = it;
4326 }
4327 }
4328 }
4329 }
4330}
4331
4332/**
4333 * @internal
4334 * Return the line number 'line'.
4335 *
4336 * @param obj the evas object - NOT NULL.
4337 * @param line the line to find
4338 * @return the line of line number or NULL if no line found.
4339 */
4340static Evas_Object_Textblock_Line *
4341_find_layout_line_num(const Evas_Object *obj, int line)
4342{
4343 Evas_Object_Textblock_Paragraph *par;
4344 Evas_Object_Textblock_Line *ln;
4345 Evas_Object_Textblock *o;
4346
4347 o = (Evas_Object_Textblock *)(obj->object_data);
4348
4349 par = _layout_find_paragraph_by_line_no(o, line);
4350 if (par)
4351 {
4352 _layout_paragraph_render(o, par);
4353 EINA_INLIST_FOREACH(par->lines, ln)
4354 {
4355 if (par->line_no + ln->line_no == line) return ln;
4356 }
4357 }
4358 return NULL;
4359}
4360
4361EAPI Evas_Object *
4362evas_object_textblock_add(Evas *e)
4363{
4364 Evas_Object *obj;
4365
4366 MAGIC_CHECK(e, Evas, MAGIC_EVAS);
4367 return NULL;
4368 MAGIC_CHECK_END();
4369 obj = evas_object_new(e);
4370 evas_object_textblock_init(obj);
4371 evas_object_inject(obj, e);
4372 return obj;
4373}
4374
4375EAPI Evas_Textblock_Style *
4376evas_textblock_style_new(void)
4377{
4378 Evas_Textblock_Style *ts;
4379
4380 ts = calloc(1, sizeof(Evas_Textblock_Style));
4381 return ts;
4382}
4383
4384EAPI void
4385evas_textblock_style_free(Evas_Textblock_Style *ts)
4386{
4387 if (!ts) return;
4388 if (ts->objects)
4389 {
4390 ts->delete_me = 1;
4391 return;
4392 }
4393 _style_clear(ts);
4394 free(ts);
4395}
4396
4397EAPI void
4398evas_textblock_style_set(Evas_Textblock_Style *ts, const char *text)
4399{
4400 Eina_List *l;
4401 Evas_Object *obj;
4402
4403 if (!ts) return;
4404 /* If the style wasn't really changed, abort. */
4405 if ((!ts->style_text && !text) ||
4406 (ts->style_text && text && !strcmp(text, ts->style_text)))
4407 return;
4408
4409 EINA_LIST_FOREACH(ts->objects, l, obj)
4410 {
4411 Evas_Object_Textblock *o;
4412
4413 o = (Evas_Object_Textblock *)(obj->object_data);
4414 _evas_textblock_invalidate_all(o);
4415 _evas_textblock_changed(o, obj);
4416 }
4417
4418 _style_replace(ts, text);
4419
4420 if (ts->style_text)
4421 {
4422 // format MUST be KEY='VALUE'[KEY='VALUE']...
4423 const char *p;
4424 const char *key_start, *key_stop, *val_start, *val_stop;
4425
4426 key_start = key_stop = val_start = val_stop = NULL;
4427 p = ts->style_text;
4428 while (*p)
4429 {
4430 if (!key_start)
4431 {
4432 if (!isspace(*p))
4433 key_start = p;
4434 }
4435 else if (!key_stop)
4436 {
4437 if ((*p == '=') || (isspace(*p)))
4438 key_stop = p;
4439 }
4440 else if (!val_start)
4441 {
4442 if (((*p) == '\'') && (*(p + 1)))
4443 val_start = p + 1;
4444 }
4445 else if (!val_stop)
4446 {
4447 if (((*p) == '\'') && (p > ts->style_text) && (p[-1] != '\\'))
4448 val_stop = p;
4449 }
4450 if ((key_start) && (key_stop) && (val_start) && (val_stop))
4451 {
4452 char *tags, *replaces;
4453 Evas_Object_Style_Tag *tag;
4454 size_t tag_len = key_stop - key_start;
4455 size_t replace_len = val_stop - val_start;
4456
4457 tags = malloc(tag_len + 1);
4458 if (tags)
4459 {
4460 memcpy(tags, key_start, tag_len);
4461 tags[tag_len] = 0;
4462 }
4463
4464 replaces = malloc(replace_len + 1);
4465 if (replaces)
4466 {
4467 memcpy(replaces, val_start, replace_len);
4468 replaces[replace_len] = 0;
4469 }
4470 if ((tags) && (replaces))
4471 {
4472 if (!strcmp(tags, "DEFAULT"))
4473 {
4474 ts->default_tag = replaces;
4475 free(tags);
4476 }
4477 else
4478 {
4479 tag = calloc(1, sizeof(Evas_Object_Style_Tag));
4480 if (tag)
4481 {
4482 tag->tag = tags;
4483 tag->replace = replaces;
4484 tag->tag_len = tag_len;
4485 tag->replace_len = replace_len;
4486 ts->tags = (Evas_Object_Style_Tag *)eina_inlist_append(EINA_INLIST_GET(ts->tags), EINA_INLIST_GET(tag));
4487 }
4488 else
4489 {
4490 free(tags);
4491 free(replaces);
4492 }
4493 }
4494 }
4495 else
4496 {
4497 if (tags) free(tags);
4498 if (replaces) free(replaces);
4499 }
4500 key_start = key_stop = val_start = val_stop = NULL;
4501 }
4502 p++;
4503 }
4504 }
4505}
4506
4507EAPI const char *
4508evas_textblock_style_get(const Evas_Textblock_Style *ts)
4509{
4510 if (!ts) return NULL;
4511 return ts->style_text;
4512}
4513
4514/* textblock styles */
4515EAPI void
4516evas_object_textblock_style_set(Evas_Object *obj, Evas_Textblock_Style *ts)
4517{
4518 TB_HEAD();
4519 if (ts == o->style) return;
4520 if ((ts) && (ts->delete_me)) return;
4521 if (o->style)
4522 {
4523 Evas_Textblock_Style *old_ts;
4524 if (o->markup_text)
4525 {
4526 free(o->markup_text);
4527 o->markup_text = NULL;
4528 }
4529
4530 old_ts = o->style;
4531 old_ts->objects = eina_list_remove(old_ts->objects, obj);
4532 if ((old_ts->delete_me) && (!old_ts->objects))
4533 evas_textblock_style_free(old_ts);
4534 }
4535 if (ts)
4536 {
4537 ts->objects = eina_list_append(ts->objects, obj);
4538 }
4539 o->style = ts;
4540
4541 _evas_textblock_invalidate_all(o);
4542 _evas_textblock_changed(o, obj);
4543}
4544
4545EAPI const Evas_Textblock_Style *
4546evas_object_textblock_style_get(const Evas_Object *obj)
4547{
4548 TB_HEAD_RETURN(NULL);
4549 return o->style;
4550}
4551
4552EAPI void
4553evas_object_textblock_replace_char_set(Evas_Object *obj, const char *ch)
4554{
4555 TB_HEAD();
4556 if (o->repch) eina_stringshare_del(o->repch);
4557 if (ch) o->repch = eina_stringshare_add(ch);
4558 else o->repch = NULL;
4559 _evas_textblock_invalidate_all(o);
4560 _evas_textblock_changed(o, obj);
4561}
4562
4563EAPI void
4564evas_object_textblock_legacy_newline_set(Evas_Object *obj, Eina_Bool mode)
4565{
4566 TB_HEAD();
4567 if (o->legacy_newline == mode)
4568 return;
4569
4570 o->legacy_newline = mode;
4571 /* FIXME: Should recreate all the textnodes... For now, it's just
4572 * for new text inserted. */
4573}
4574
4575EAPI Eina_Bool
4576evas_object_textblock_legacy_newline_get(const Evas_Object *obj)
4577{
4578 TB_HEAD_RETURN(EINA_FALSE);
4579 return o->legacy_newline;
4580}
4581
4582EAPI void
4583evas_object_textblock_valign_set(Evas_Object *obj, double align)
4584{
4585 TB_HEAD();
4586 if (align < 0.0) align = 0.0;
4587 else if (align > 1.0) align = 1.0;
4588 if (o->valign == align) return;
4589 o->valign = align;
4590 _evas_textblock_changed(o, obj);
4591}
4592
4593EAPI double
4594evas_object_textblock_valign_get(const Evas_Object *obj)
4595{
4596 TB_HEAD_RETURN(0.0);
4597 return o->valign;
4598}
4599
4600EAPI void
4601evas_object_textblock_bidi_delimiters_set(Evas_Object *obj, const char *delim)
4602{
4603 TB_HEAD();
4604 eina_stringshare_replace(&o->bidi_delimiters, delim);
4605}
4606
4607EAPI const char *
4608evas_object_textblock_bidi_delimiters_get(const Evas_Object *obj)
4609{
4610 TB_HEAD_RETURN(NULL);
4611 return o->bidi_delimiters;
4612}
4613
4614EAPI const char *
4615evas_object_textblock_replace_char_get(Evas_Object *obj)
4616{
4617 TB_HEAD_RETURN(NULL);
4618 return o->repch;
4619}
4620
4621/**
4622 * @internal
4623 * Advance p_buff to point after the end of the string. It's used with the
4624 * @ref escaped_strings[] variable.
4625 *
4626 * @param p_buff the pointer to the current string.
4627 */
4628static inline void
4629_escaped_advance_after_end_of_string(const char **p_buf)
4630{
4631 while (**p_buf != 0) (*p_buf)++;
4632 (*p_buf)++;
4633}
4634
4635/**
4636 * @internal
4637 * Advance p_buff to point after the end of the string. It's used with the
4638 * @ref escaped_strings[] variable. Also chec if matches.
4639 * FIXME: doc.
4640 *
4641 * @param p_buff the pointer to the current string.
4642 */
4643static inline int
4644_escaped_is_eq_and_advance(const char *s, const char *s_end,
4645 const char **p_m, const char *m_end)
4646{
4647 Eina_Bool reached_end;
4648 for (;((s < s_end) && (*p_m < m_end)); s++, (*p_m)++)
4649 {
4650 if (*s != **p_m)
4651 {
4652 _escaped_advance_after_end_of_string(p_m);
4653 return 0;
4654 }
4655 }
4656
4657 reached_end = !**p_m;
4658 if (*p_m < m_end)
4659 _escaped_advance_after_end_of_string(p_m);
4660
4661 return ((s == s_end) && reached_end);
4662}
4663
4664/**
4665 * @internal
4666 *
4667 * @param s the string to match
4668 */
4669static inline const char *
4670_escaped_char_match(const char *s, int *adv)
4671{
4672 const char *map_itr, *map_end, *mc, *sc;
4673
4674 map_itr = escape_strings;
4675 map_end = map_itr + sizeof(escape_strings);
4676
4677 while (map_itr < map_end)
4678 {
4679 const char *escape;
4680 int match;
4681
4682 escape = map_itr;
4683 _escaped_advance_after_end_of_string(&map_itr);
4684 if (map_itr >= map_end) break;
4685
4686 mc = map_itr;
4687 sc = s;
4688 match = 1;
4689 while ((*mc) && (*sc))
4690 {
4691 if ((unsigned char)*sc < (unsigned char)*mc) return NULL;
4692 if (*sc != *mc) match = 0;
4693 mc++;
4694 sc++;
4695 }
4696 if (match)
4697 {
4698 *adv = mc - map_itr;
4699 return escape;
4700 }
4701 _escaped_advance_after_end_of_string(&map_itr);
4702 }
4703 return NULL;
4704}
4705
4706/**
4707 * @internal
4708 * FIXME: TBD.
4709 *
4710 * @param s the string to match
4711 */
4712static inline const char *
4713_escaped_char_get(const char *s, const char *s_end)
4714{
4715 /* Handle numeric escape codes. */
4716 if (s[1] == '#')
4717 {
4718 static char utf8_escape[7]; /* Support up to 6 bytes utf8 */
4719 char ustr[10];
4720 Eina_Unicode uchar[2] = { 0, 0 };
4721 char *utf8_char;
4722 size_t len = 0;
4723 int base = 10;
4724 s += 2; /* Skip "&#" */
4725
4726 if (tolower(*s) == 'x')
4727 {
4728 s++;
4729 base = 16;
4730 }
4731
4732 len = s_end - s;
4733 if (len >= sizeof(ustr) + 1)
4734 len = sizeof(ustr);
4735
4736 memcpy(ustr, s, len);
4737 ustr[len] = '\0';
4738 uchar[0] = strtol(ustr, NULL, base);
4739
4740 if (uchar[0] == 0)
4741 return NULL;
4742
4743 utf8_char = eina_unicode_unicode_to_utf8(uchar, NULL);
4744 strcpy(utf8_escape, utf8_char);
4745 free(utf8_char);
4746
4747 return utf8_escape;
4748 }
4749 else
4750 {
4751 const char *map_itr, *map_end;
4752
4753 map_itr = escape_strings;
4754 map_end = map_itr + sizeof(escape_strings);
4755
4756 while (map_itr < map_end)
4757 {
4758 if (_escaped_is_eq_and_advance(s, s_end, &map_itr, map_end))
4759 return map_itr;
4760 if (map_itr < map_end)
4761 _escaped_advance_after_end_of_string(&map_itr);
4762 }
4763 }
4764
4765 return NULL;
4766}
4767
4768EAPI const char *
4769evas_textblock_escape_string_get(const char *escape)
4770{
4771 /* &amp; -> & */
4772 return _escaped_char_get(escape, escape + strlen(escape));
4773}
4774
4775EAPI const char *
4776evas_textblock_escape_string_range_get(const char *escape_start, const char *escape_end)
4777{
4778 return _escaped_char_get(escape_start, escape_end);
4779}
4780
4781EAPI const char *
4782evas_textblock_string_escape_get(const char *string, int *len_ret)
4783{
4784 /* & -> &amp; */
4785 return _escaped_char_match(string, len_ret);
4786}
4787
4788/**
4789 * @internal
4790 * Appends the escaped char beteewn s and s_end to the curosr
4791 *
4792 *
4793 * @param s the start of the string
4794 * @param s_end the end of the string.
4795 */
4796static inline void
4797_append_escaped_char(Evas_Textblock_Cursor *cur, const char *s,
4798 const char *s_end)
4799{
4800 const char *escape;
4801
4802 escape = _escaped_char_get(s, s_end);
4803 if (escape)
4804 evas_textblock_cursor_text_append(cur, escape);
4805}
4806
4807/**
4808 * @internal
4809 * prepends the escaped char beteewn s and s_end to the curosr
4810 *
4811 *
4812 * @param s the start of the string
4813 * @param s_end the end of the string.
4814 */
4815static inline void
4816_prepend_escaped_char(Evas_Textblock_Cursor *cur, const char *s,
4817 const char *s_end)
4818{
4819 const char *escape;
4820
4821 escape = _escaped_char_get(s, s_end);
4822 if (escape)
4823 evas_textblock_cursor_text_prepend(cur, escape);
4824}
4825
4826
4827EAPI void
4828evas_object_textblock_text_markup_set(Evas_Object *obj, const char *text)
4829{
4830 TB_HEAD();
4831 if ((text != o->markup_text) && (o->markup_text))
4832 {
4833 free(o->markup_text);
4834 o->markup_text = NULL;
4835 }
4836 _nodes_clear(obj);
4837 if (!o->style)
4838 {
4839 if (text != o->markup_text)
4840 {
4841 if (text) o->markup_text = strdup(text);
4842 }
4843 return;
4844 }
4845 evas_textblock_cursor_paragraph_first(o->cursor);
4846
4847 evas_object_textblock_text_markup_prepend(o->cursor, text);
4848 /* Point all the cursors to the starrt */
4849 {
4850 Eina_List *l;
4851 Evas_Textblock_Cursor *data;
4852
4853 evas_textblock_cursor_paragraph_first(o->cursor);
4854 EINA_LIST_FOREACH(o->cursors, l, data)
4855 evas_textblock_cursor_paragraph_first(data);
4856 }
4857}
4858
4859EAPI void
4860evas_object_textblock_text_markup_prepend(Evas_Textblock_Cursor *cur, const char *text)
4861{
4862 Evas_Object *obj = cur->obj;
4863 TB_HEAD();
4864 if (text)
4865 {
4866 char *s, *p;
4867 char *tag_start, *tag_end, *esc_start, *esc_end;
4868
4869 tag_start = tag_end = esc_start = esc_end = NULL;
4870 p = (char *)text;
4871 s = p;
4872 /* This loop goes through all of the mark up text until it finds format
4873 * tags, escape sequences or the terminating NULL. When it finds either
4874 * of those, it appends the text found up until that point to the textblock
4875 * proccesses whatever found. It repeats itself until the termainating
4876 * NULL is reached. */
4877 for (;;)
4878 {
4879 /* If we got to the end of string or just finished/started tag
4880 * or escape sequence handling. */
4881 if ((*p == 0) ||
4882 (tag_end) || (esc_end) ||
4883 (tag_start) || (esc_start))
4884 {
4885 if (tag_end)
4886 {
4887 /* If we reached to a tag ending, analyze the tag */
4888 char *ttag;
4889 size_t ttag_len = tag_end - tag_start;
4890
4891
4892 ttag = malloc(ttag_len + 1);
4893 if (ttag)
4894 {
4895 memcpy(ttag, tag_start, ttag_len);
4896 ttag[ttag_len] = 0;
4897 evas_textblock_cursor_format_prepend(cur, ttag);
4898 free(ttag);
4899 }
4900 tag_start = tag_end = NULL;
4901 }
4902 else if (esc_end)
4903 {
4904 _prepend_escaped_char(cur, esc_start, esc_end + 1);
4905 esc_start = esc_end = NULL;
4906 }
4907 else if (*p == 0)
4908 {
4909 _prepend_text_run(cur, s, p);
4910 s = NULL;
4911 }
4912 if (*p == 0)
4913 break;
4914 }
4915 if (*p == '<')
4916 {
4917 if (!esc_start)
4918 {
4919 /* Append the text prior to this to the textblock and mark
4920 * the start of the tag */
4921 tag_start = p;
4922 tag_end = NULL;
4923 _prepend_text_run(cur, s, p);
4924 s = NULL;
4925 }
4926 }
4927 else if (*p == '>')
4928 {
4929 if (tag_start)
4930 {
4931 tag_end = p + 1;
4932 s = p + 1;
4933 }
4934 }
4935 else if (*p == '&')
4936 {
4937 if (!tag_start)
4938 {
4939 /* Append the text prior to this to the textblock and mark
4940 * the start of the escape sequence */
4941 esc_start = p;
4942 esc_end = NULL;
4943 _prepend_text_run(cur, s, p);
4944 s = NULL;
4945 }
4946 }
4947 else if (*p == ';')
4948 {
4949 if (esc_start)
4950 {
4951 esc_end = p;
4952 s = p + 1;
4953 }
4954 }
4955 /* Unicode object replcament char */
4956 else if (!strncmp("\xEF\xBF\xBC", p, 3))
4957 {
4958 /*FIXME: currently just remove them, maybe do something
4959 * fancier in the future, atm it breaks if this char
4960 * is inside <> */
4961 _prepend_text_run(cur, s, p);
4962 p += 2; /* it's also advanced later in this loop need +3
4963 * in total*/
4964 s = p + 1; /* One after the end of the replacement char */
4965 }
4966 p++;
4967 }
4968 }
4969 _evas_textblock_changed(o, obj);
4970}
4971
4972
4973/**
4974 * @internal
4975 * An helper function to markup get. Appends the format from fnode to the strbugf txt.
4976 *
4977 * @param o the textblock object.
4978 * @param txt the strbuf to append to.
4979 * @param fnode the format node to process.
4980 */
4981static void
4982_markup_get_format_append(Evas_Object_Textblock *o __UNUSED__, Eina_Strbuf *txt, Evas_Object_Textblock_Node_Format *fnode)
4983{
4984 eina_strbuf_append_char(txt, '<');
4985 {
4986 const char *s;
4987 int pop = 0;
4988
4989 // FIXME: need to escape
4990 s = fnode->orig_format;
4991 if (*s == '-') pop = 1;
4992 while ((*s == ' ') || (*s == '+') || (*s == '-')) s++;
4993 if (pop) eina_strbuf_append_char(txt, '/');
4994 eina_strbuf_append(txt, s);
4995 }
4996 eina_strbuf_append_char(txt, '>');
4997}
4998
4999/**
5000 * @internal
5001 * An helper function to markup get. Appends the text in text.
5002 *
5003 * @param txt the strbuf to append to.
5004 * @param text the text to process.
5005 */
5006static void
5007_markup_get_text_append(Eina_Strbuf *txt, const Eina_Unicode *text)
5008{
5009 char *p = eina_unicode_unicode_to_utf8(text, NULL);
5010 char *base = p;
5011 while (*p)
5012 {
5013 const char *escape;
5014 int adv;
5015
5016 escape = _escaped_char_match(p, &adv);
5017 if (escape)
5018 {
5019 p += adv;
5020 eina_strbuf_append(txt, escape);
5021 }
5022 else
5023 {
5024 eina_strbuf_append_char(txt, *p);
5025 p++;
5026 }
5027 }
5028 free(base);
5029}
5030EAPI const char *
5031evas_object_textblock_text_markup_get(const Evas_Object *obj)
5032{
5033 Evas_Object_Textblock_Node_Text *n;
5034 Eina_Strbuf *txt = NULL;
5035
5036 TB_HEAD_RETURN(NULL);
5037 if (o->markup_text) return(o->markup_text);
5038 txt = eina_strbuf_new();
5039 EINA_INLIST_FOREACH(o->text_nodes, n)
5040 {
5041 Evas_Object_Textblock_Node_Format *fnode;
5042 Eina_Unicode *text_base, *text;
5043 int off;
5044
5045 /* For each text node to thorugh all of it's format nodes
5046 * append text from the start to the offset of the next format
5047 * using the last format got. if needed it also creates format items
5048 * this is the core algorithm of the layout mechanism.
5049 * Skip the unicode replacement chars when there are because
5050 * we don't want to print them. */
5051 text_base = text =
5052 eina_unicode_strndup(eina_ustrbuf_string_get(n->unicode),
5053 eina_ustrbuf_length_get(n->unicode));
5054 fnode = n->format_node;
5055 off = 0;
5056 while (fnode && (fnode->text_node == n))
5057 {
5058 Eina_Unicode tmp_ch;
5059 off += fnode->offset;
5060 /* No need to skip on the first run */
5061 tmp_ch = text[off];
5062 text[off] = 0; /* Null terminate the part of the string */
5063 _markup_get_text_append(txt, text);
5064 _markup_get_format_append(o, txt, fnode);
5065 text[off] = tmp_ch; /* Restore the char */
5066 text += off;
5067 if (fnode->visible)
5068 {
5069 off = -1;
5070 text++;
5071 }
5072 else
5073 {
5074 off = 0;
5075 }
5076 fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
5077 }
5078 /* Add the rest, skip replacement */
5079 _markup_get_text_append(txt, text);
5080 free(text_base);
5081 }
5082
5083
5084 o->markup_text = eina_strbuf_string_steal(txt);
5085 eina_strbuf_free(txt);
5086 return o->markup_text;
5087}
5088
5089/* cursors */
5090
5091/**
5092 * @internal
5093 * Merge the current node with the next, no need to remove PS, already
5094 * not there.
5095 *
5096 * @param o the text block object.
5097 * @param to merge into to.
5098 */
5099static void
5100_evas_textblock_nodes_merge(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *to)
5101{
5102 Evas_Object_Textblock_Node_Format *itr;
5103 Evas_Object_Textblock_Node_Format *pnode;
5104 Evas_Object_Textblock_Node_Text *from;
5105 const Eina_Unicode *text;
5106 int to_len, len;
5107
5108 if (!to) return;
5109 from = _NODE_TEXT(EINA_INLIST_GET(to)->next);
5110
5111 to_len = eina_ustrbuf_length_get(to->unicode);
5112 text = eina_ustrbuf_string_get(from->unicode);
5113 len = eina_ustrbuf_length_get(from->unicode);
5114 eina_ustrbuf_append_length(to->unicode, text, len);
5115
5116 itr = from->format_node;
5117 if (itr && (itr->text_node == from))
5118 {
5119 pnode = _NODE_FORMAT(EINA_INLIST_GET(itr)->prev);
5120 if (pnode && (pnode->text_node == to))
5121 {
5122 itr->offset += to_len - _evas_textblock_node_format_pos_get(pnode);
5123 }
5124 else
5125 {
5126 itr->offset += to_len;
5127 }
5128 }
5129
5130 while (itr && (itr->text_node == from))
5131 {
5132 itr->text_node = to;
5133 itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next);
5134 }
5135 if (!to->format_node || (to->format_node->text_node != to))
5136 {
5137 to->format_node = from->format_node;
5138 }
5139
5140 /* When it comes to how we handle it, merging is like removing both nodes
5141 * and creating a new one, se we need to do the needed cleanups. */
5142 if (to->par)
5143 to->par->text_node = NULL;
5144 to->par = NULL;
5145
5146 to->is_new = EINA_TRUE;
5147
5148 _evas_textblock_cursors_set_node(o, from, to);
5149 _evas_textblock_node_text_remove(o, from);
5150}
5151
5152/**
5153 * @internal
5154 * Merge the current node with the next, no need to remove PS, already
5155 * not there.
5156 *
5157 * @param cur the cursor that points to the current node
5158 */
5159static void
5160_evas_textblock_cursor_nodes_merge(Evas_Textblock_Cursor *cur)
5161{
5162 Evas_Object_Textblock_Node_Text *nnode;
5163 Evas_Object_Textblock *o;
5164 int len;
5165 if (!cur) return;
5166
5167 len = eina_ustrbuf_length_get(cur->node->unicode);
5168
5169 o = (Evas_Object_Textblock *)(cur->obj->object_data);
5170 nnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->next);
5171 _evas_textblock_nodes_merge(o, cur->node);
5172 _evas_textblock_cursors_update_offset(cur, nnode, 0, len);
5173 _evas_textblock_cursors_set_node(o, nnode, cur->node);
5174 if (nnode == o->cursor->node)
5175 {
5176 o->cursor->node = cur->node;
5177 o->cursor->pos += len;
5178 }
5179}
5180
5181/**
5182 * @internal
5183 * Return the format at a specific position.
5184 *
5185 * @param cur the cursor to the position.
5186 * @return the format node at the specific position or NULL if not found.
5187 */
5188static Evas_Object_Textblock_Node_Format *
5189_evas_textblock_cursor_node_format_at_pos_get(const Evas_Textblock_Cursor *cur)
5190{
5191 Evas_Object_Textblock_Node_Format *node;
5192 Evas_Object_Textblock_Node_Format *itr;
5193 int position = 0;
5194
5195 if (!cur->node) return NULL;
5196
5197 node = cur->node->format_node;
5198 if (!node) return NULL;
5199 /* If there is no exclusive format node to this paragraph return the
5200 * previous's node */
5201 /* Find the main format node */
5202 EINA_INLIST_FOREACH(node, itr)
5203 {
5204 if (itr->text_node != cur->node)
5205 {
5206 return NULL;
5207 }
5208 if ((position + itr->offset) == cur->pos)
5209 {
5210 return itr;
5211 }
5212 position += itr->offset;
5213 }
5214 return NULL;
5215}
5216
5217/**
5218 * @internal
5219 * Return the last format node at the position of the format node n.
5220 *
5221 * @param n a format node at the position.
5222 * @return the last format node at the position of n.
5223 */
5224static Evas_Object_Textblock_Node_Format *
5225_evas_textblock_node_format_last_at_off(const Evas_Object_Textblock_Node_Format *n)
5226{
5227 const Evas_Object_Textblock_Node_Format *nnode;
5228 const Evas_Object_Textblock_Node_Text *tnode;
5229 if (!n) return NULL;
5230 nnode = n;
5231 tnode = n->text_node;
5232 do
5233 {
5234 n = nnode;
5235 nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next);
5236 }
5237 while (nnode && (nnode->text_node == tnode) && (nnode->offset == 0));
5238
5239 return (Evas_Object_Textblock_Node_Format *) n;
5240}
5241
5242/**
5243 * @internal
5244 * Returns the visible format at a specific location.
5245 *
5246 * @param n a format at the specific position.
5247 * @return the format node at the specific position or NULL if not found.
5248 */
5249static Evas_Object_Textblock_Node_Format *
5250_evas_textblock_node_visible_at_pos_get(const Evas_Object_Textblock_Node_Format *n)
5251{
5252 const Evas_Object_Textblock_Node_Format *nnode;
5253 if (!n) return NULL;
5254 /* The visible format is the last one, because it inserts a replacement
5255 * char that advances the next formats. */
5256
5257 nnode = n;
5258 do
5259 {
5260 n = nnode;
5261 if (n->visible) return (Evas_Object_Textblock_Node_Format *) n;
5262 nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next);
5263 }
5264 while (nnode && (nnode->offset == 0));
5265
5266 return NULL;
5267}
5268
5269/**
5270 * @internal
5271 * Return the last format that applies to a specific cursor or at the specific
5272 * position the cursor points to. This means either a cursor at or before the
5273 * position of the cursor in the text node is returned or the previous's text
5274 * node's format node.
5275 *
5276 * @param cur the position to look at.
5277 * @return the format node found.
5278 */
5279static Evas_Object_Textblock_Node_Format *
5280_evas_textblock_cursor_node_format_before_or_at_pos_get(const Evas_Textblock_Cursor *cur)
5281{
5282 Evas_Object_Textblock_Node_Format *node, *pitr = NULL;
5283 Evas_Object_Textblock_Node_Format *itr;
5284 size_t position = 0;
5285
5286 if (!cur->node) return NULL;
5287
5288 node = cur->node->format_node;
5289 if (!node) return NULL;
5290 /* If there is no exclusive format node to this paragraph return the
5291 * previous's node */
5292 if (node->text_node != cur->node)
5293 {
5294 return node;
5295 }
5296 else if (node->offset > cur->pos)
5297 {
5298 return _NODE_FORMAT(EINA_INLIST_GET(node)->prev);
5299 }
5300 /* Find the main format node */
5301 pitr = _NODE_FORMAT(EINA_INLIST_GET(node)->prev);
5302 EINA_INLIST_FOREACH(node, itr)
5303 {
5304 if ((itr->text_node != cur->node) ||
5305 ((position + itr->offset) > cur->pos))
5306 {
5307 return pitr;
5308 }
5309 else if ((position + itr->offset) == cur->pos)
5310 {
5311 return itr;
5312 }
5313 pitr = itr;
5314 position += itr->offset;
5315 }
5316 return pitr;
5317}
5318
5319/**
5320 * @internal
5321 * Find the layout item and line that match the cursor.
5322 *
5323 * @param cur the cursor we are currently at. - NOT NULL.
5324 * @param[out] lnr the line found - not null.
5325 * @param[out] itr the item found - not null.
5326 * @return EINA_TRUE if we matched the previous format, EINA_FALSE otherwise.
5327 */
5328static Eina_Bool
5329_find_layout_item_match(const Evas_Textblock_Cursor *cur, Evas_Object_Textblock_Line **lnr, Evas_Object_Textblock_Item **itr)
5330{
5331 Evas_Textblock_Cursor cur2;
5332 Eina_Bool previous_format = EINA_FALSE;
5333
5334 cur2.obj = cur->obj;
5335 evas_textblock_cursor_copy(cur, &cur2);
5336 if (cur2.pos > 0)
5337 {
5338 cur2.pos--;
5339 }
5340
5341 if (_evas_textblock_cursor_is_at_the_end(cur) &&
5342 evas_textblock_cursor_format_is_visible_get(&cur2))
5343 {
5344 _find_layout_item_line_match(cur2.obj, cur2.node, cur2.pos, lnr, itr);
5345 previous_format = EINA_TRUE;
5346 }
5347 else
5348 {
5349 _find_layout_item_line_match(cur->obj, cur->node, cur->pos, lnr, itr);
5350 }
5351 return previous_format;
5352}
5353
5354EAPI Evas_Textblock_Cursor *
5355evas_object_textblock_cursor_get(const Evas_Object *obj)
5356{
5357 TB_HEAD_RETURN(NULL);
5358 return o->cursor;
5359}
5360
5361EAPI Evas_Textblock_Cursor *
5362evas_object_textblock_cursor_new(const Evas_Object *obj)
5363{
5364 Evas_Textblock_Cursor *cur;
5365
5366 TB_HEAD_RETURN(NULL);
5367 cur = calloc(1, sizeof(Evas_Textblock_Cursor));
5368 cur->obj = (Evas_Object *) obj;
5369 cur->node = o->text_nodes;
5370 cur->pos = 0;
5371
5372 o->cursors = eina_list_append(o->cursors, cur);
5373 return cur;
5374}
5375
5376EAPI void
5377evas_textblock_cursor_free(Evas_Textblock_Cursor *cur)
5378{
5379 Evas_Object_Textblock *o;
5380
5381 if (!cur) return;
5382 o = (Evas_Object_Textblock *)(cur->obj->object_data);
5383 if (cur == o->cursor) return;
5384 o->cursors = eina_list_remove(o->cursors, cur);
5385 free(cur);
5386}
5387
5388EAPI Eina_Bool
5389evas_textblock_cursor_is_format(const Evas_Textblock_Cursor *cur)
5390{
5391 if (!cur || !cur->node) return EINA_FALSE;
5392 if (evas_textblock_cursor_format_is_visible_get(cur)) return EINA_TRUE;
5393 return (_evas_textblock_cursor_node_format_at_pos_get(cur)) ?
5394 EINA_TRUE : EINA_FALSE;
5395}
5396
5397EAPI const Eina_List *
5398evas_textblock_node_format_list_get(const Evas_Object *obj, const char *anchor)
5399{
5400 TB_HEAD_RETURN(NULL);
5401 if (!strcmp(anchor, "a"))
5402 return o->anchors_a;
5403 else if (!strcmp(anchor, "item"))
5404 return o->anchors_item;
5405
5406 return NULL;
5407}
5408
5409EAPI const Evas_Object_Textblock_Node_Format *
5410evas_textblock_node_format_first_get(const Evas_Object *obj)
5411{
5412 TB_HEAD_RETURN(NULL);
5413 return o->format_nodes;
5414}
5415
5416EAPI const Evas_Object_Textblock_Node_Format *
5417evas_textblock_node_format_last_get(const Evas_Object *obj)
5418{
5419 TB_HEAD_RETURN(NULL);
5420 if (o->format_nodes)
5421 {
5422 return _NODE_FORMAT(EINA_INLIST_GET(o->format_nodes)->last);
5423 }
5424 return NULL;
5425}
5426
5427EAPI const Evas_Object_Textblock_Node_Format *
5428evas_textblock_node_format_next_get(const Evas_Object_Textblock_Node_Format *n)
5429{
5430 return _NODE_FORMAT(EINA_INLIST_GET(n)->next);
5431}
5432
5433EAPI const Evas_Object_Textblock_Node_Format *
5434evas_textblock_node_format_prev_get(const Evas_Object_Textblock_Node_Format *n)
5435{
5436 return _NODE_FORMAT(EINA_INLIST_GET(n)->prev);
5437}
5438
5439EAPI void
5440evas_textblock_node_format_remove_pair(Evas_Object *obj,
5441 Evas_Object_Textblock_Node_Format *n)
5442{
5443 Evas_Object_Textblock_Node_Text *tnode1;
5444 Evas_Object_Textblock_Node_Format *fmt, *found_node = NULL;
5445 Eina_List *fstack = NULL;
5446 TB_HEAD();
5447
5448 if (!n) return;
5449
5450 fmt = n;
5451
5452 do
5453 {
5454 const char *fstr = fmt->orig_format;
5455
5456 if (fstr && (*fstr == '+'))
5457 {
5458 fstack = eina_list_prepend(fstack, fmt);
5459 }
5460 else if (fstr && (*fstr == '-'))
5461 {
5462 size_t fstr_len;
5463 /* Skip the '-' */
5464 fstr++;
5465 fstr_len = strlen(fstr);
5466 /* Generic popper, just pop */
5467 if (((fstr[0] == ' ') && !fstr[1]) || !fstr[0])
5468 {
5469 fstack = eina_list_remove_list(fstack, fstack);
5470 if (!fstack)
5471 {
5472 found_node = fmt;
5473 goto found;
5474 }
5475 }
5476 /* Find the matching format and pop it, if the matching format
5477 * is out format, i.e the last one, pop and break. */
5478 else
5479 {
5480 Eina_List *i;
5481 Evas_Object_Textblock_Node_Format *fnode;
5482 EINA_LIST_FOREACH(fstack, i, fnode)
5483 {
5484 if (_FORMAT_IS_CLOSER_OF(
5485 fnode->orig_format, fstr, fstr_len))
5486 {
5487 /* Last one, this is our item! */
5488 if (!eina_list_next(i))
5489 {
5490 found_node = fmt;
5491 goto found;
5492 }
5493 fstack = eina_list_remove_list(fstack, i);
5494 break;
5495 }
5496 }
5497 }
5498 }
5499
5500 fmt = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next);
5501 }
5502 while (fmt && fstack);
5503
5504found:
5505
5506 fstack = eina_list_free(fstack);
5507
5508 if (n->visible)
5509 {
5510 size_t ind = _evas_textblock_node_format_pos_get(n);
5511 const char *format = n->format;
5512 Evas_Textblock_Cursor cur;
5513 cur.obj = obj;
5514
5515 eina_ustrbuf_remove(n->text_node->unicode, ind, ind + 1);
5516 if (format && _IS_PARAGRAPH_SEPARATOR(o, format))
5517 {
5518 evas_textblock_cursor_at_format_set(&cur, n);
5519 _evas_textblock_cursor_nodes_merge(&cur);
5520 }
5521 _evas_textblock_cursors_update_offset(&cur, n->text_node, ind, -1);
5522 }
5523 tnode1 = n->text_node;
5524 _evas_textblock_node_format_remove(o, n, 0);
5525 if (found_node && (found_node != n))
5526 {
5527 Evas_Object_Textblock_Node_Text *tnode2;
5528 tnode2 = found_node->text_node;
5529 /* found_node can never be visible! (it's the closing format) */
5530 _evas_textblock_node_format_remove(o, found_node, 0);
5531
5532 /* FIXME: Should be unified in the layout, for example, added to a list
5533 * that checks this kind of removals. But until then, this is very fast
5534 * and works. */
5535 /* Mark all the text nodes in between the removed formats as dirty. */
5536 while (tnode1)
5537 {
5538 tnode1->dirty = EINA_TRUE;
5539 if (tnode1 == tnode2)
5540 break;
5541 tnode1 =
5542 _NODE_TEXT(EINA_INLIST_GET(tnode1)->next);
5543 }
5544 }
5545
5546 _evas_textblock_changed(o, obj);
5547}
5548
5549EAPI void
5550evas_textblock_cursor_paragraph_first(Evas_Textblock_Cursor *cur)
5551{
5552 Evas_Object_Textblock *o;
5553 if (!cur) return;
5554 o = (Evas_Object_Textblock *)(cur->obj->object_data);
5555 cur->node = o->text_nodes;
5556 cur->pos = 0;
5557
5558}
5559
5560EAPI void
5561evas_textblock_cursor_paragraph_last(Evas_Textblock_Cursor *cur)
5562{
5563 Evas_Object_Textblock *o;
5564 Evas_Object_Textblock_Node_Text *node;
5565
5566 if (!cur) return;
5567 o = (Evas_Object_Textblock *)(cur->obj->object_data);
5568 node = o->text_nodes;
5569 if (node)
5570 {
5571 node = _NODE_TEXT(EINA_INLIST_GET(node)->last);
5572 cur->node = node;
5573 cur->pos = 0;
5574
5575 evas_textblock_cursor_paragraph_char_last(cur);
5576 }
5577 else
5578 {
5579 cur->node = NULL;
5580 cur->pos = 0;
5581
5582 }
5583}
5584
5585EAPI Eina_Bool
5586evas_textblock_cursor_paragraph_next(Evas_Textblock_Cursor *cur)
5587{
5588 if (!cur) return EINA_FALSE;
5589 if (!cur->node) return EINA_FALSE;
5590 /* If there is a current text node, return the next text node (if exists)
5591 * otherwise, just return False. */
5592 if (cur->node)
5593 {
5594 Evas_Object_Textblock_Node_Text *nnode;
5595 nnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->next);
5596 if (nnode)
5597 {
5598 cur->node = nnode;
5599 cur->pos = 0;
5600
5601 return EINA_TRUE;
5602 }
5603 }
5604 return EINA_FALSE;
5605}
5606
5607EAPI Eina_Bool
5608evas_textblock_cursor_paragraph_prev(Evas_Textblock_Cursor *cur)
5609{
5610 Evas_Object_Textblock_Node_Text *node;
5611 if (!cur) return EINA_FALSE;
5612 if (!cur->node) return EINA_FALSE;
5613 /* If the current node is a text node, just get the prev if any,
5614 * if it's a format, get the current text node out of the format and return
5615 * the prev text node if any. */
5616 node = cur->node;
5617 /* If there is a current text node, return the prev text node
5618 * (if exists) otherwise, just return False. */
5619 if (node)
5620 {
5621 Evas_Object_Textblock_Node_Text *pnode;
5622 pnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->prev);
5623 if (pnode)
5624 {
5625 cur->node = pnode;
5626 evas_textblock_cursor_paragraph_char_last(cur);
5627 return EINA_TRUE;
5628 }
5629 }
5630 return EINA_FALSE;
5631}
5632
5633EAPI void
5634evas_textblock_cursor_set_at_format(Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Format *n)
5635{
5636 evas_textblock_cursor_at_format_set(cur, n);
5637}
5638
5639EAPI Eina_Bool
5640evas_textblock_cursor_format_next(Evas_Textblock_Cursor *cur)
5641{
5642 Evas_Object_Textblock_Node_Format *node;
5643
5644 if (!cur) return EINA_FALSE;
5645 if (!cur->node) return EINA_FALSE;
5646 /* If the current node is a format node, just get the next if any,
5647 * if it's a text, get the current format node out of the text and return
5648 * the next format node if any. */
5649 node = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur);
5650 node = _evas_textblock_node_format_last_at_off(node);
5651 if (!node)
5652 {
5653 if (cur->node->format_node)
5654 {
5655 cur->pos = _evas_textblock_node_format_pos_get(node);
5656 return EINA_TRUE;
5657 }
5658 }
5659 /* If there is a current text node, return the next format node (if exists)
5660 * otherwise, just return False. */
5661 else
5662 {
5663 Evas_Object_Textblock_Node_Format *nnode;
5664 nnode = _NODE_FORMAT(EINA_INLIST_GET(node)->next);
5665 if (nnode)
5666 {
5667 cur->node = nnode->text_node;
5668 cur->pos = _evas_textblock_node_format_pos_get(nnode);
5669
5670 return EINA_TRUE;
5671 }
5672 }
5673 return EINA_FALSE;
5674}
5675
5676EAPI Eina_Bool
5677evas_textblock_cursor_format_prev(Evas_Textblock_Cursor *cur)
5678{
5679 const Evas_Object_Textblock_Node_Format *node;
5680 if (!cur) return EINA_FALSE;
5681 if (!cur->node) return EINA_FALSE;
5682 node = evas_textblock_cursor_format_get(cur);
5683 if (!node)
5684 {
5685 node = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur);
5686 if (node)
5687 {
5688 cur->node = node->text_node;
5689 cur->pos = _evas_textblock_node_format_pos_get(node);
5690
5691 return EINA_TRUE;
5692 }
5693 }
5694 /* If there is a current text node, return the next text node (if exists)
5695 * otherwise, just return False. */
5696 if (node)
5697 {
5698 Evas_Object_Textblock_Node_Format *pnode;
5699 pnode = _NODE_FORMAT(EINA_INLIST_GET(node)->prev);
5700 if (pnode)
5701 {
5702 cur->node = pnode->text_node;
5703 cur->pos = _evas_textblock_node_format_pos_get(pnode);
5704
5705 return EINA_TRUE;
5706 }
5707 }
5708 return EINA_FALSE;
5709}
5710
5711EAPI Eina_Bool
5712evas_textblock_cursor_char_next(Evas_Textblock_Cursor *cur)
5713{
5714 int ind;
5715 const Eina_Unicode *text;
5716
5717 if (!cur) return EINA_FALSE;
5718 if (!cur->node) return EINA_FALSE;
5719
5720 ind = cur->pos;
5721 text = eina_ustrbuf_string_get(cur->node->unicode);
5722 if (text[ind]) ind++;
5723 /* Only allow pointing a null if it's the last paragraph.
5724 * because we don't have a PS there. */
5725 if (text[ind])
5726 {
5727 cur->pos = ind;
5728 return EINA_TRUE;
5729 }
5730 else
5731 {
5732 if (!evas_textblock_cursor_paragraph_next(cur))
5733 {
5734 /* If we already were at the end, that means we don't have
5735 * where to go next we should return FALSE */
5736 if (cur->pos == (size_t) ind)
5737 return EINA_FALSE;
5738
5739 cur->pos = ind;
5740 return EINA_TRUE;
5741 }
5742 else
5743 {
5744 return EINA_TRUE;
5745 }
5746 }
5747}
5748
5749EAPI Eina_Bool
5750evas_textblock_cursor_char_prev(Evas_Textblock_Cursor *cur)
5751{
5752 if (!cur) return EINA_FALSE;
5753 if (!cur->node) return EINA_FALSE;
5754
5755 if (cur->pos != 0)
5756 {
5757 cur->pos--;
5758 return EINA_TRUE;
5759 }
5760 return evas_textblock_cursor_paragraph_prev(cur);
5761}
5762
5763EAPI void
5764evas_textblock_cursor_paragraph_char_first(Evas_Textblock_Cursor *cur)
5765{
5766 if (!cur) return;
5767 cur->pos = 0;
5768
5769}
5770
5771EAPI void
5772evas_textblock_cursor_paragraph_char_last(Evas_Textblock_Cursor *cur)
5773{
5774 int ind;
5775
5776 if (!cur) return;
5777 if (!cur->node) return;
5778 ind = eina_ustrbuf_length_get(cur->node->unicode);
5779 /* If it's not the last paragraph, go back one, because we want to point
5780 * to the PS, not the NULL */
5781 if (EINA_INLIST_GET(cur->node)->next)
5782 ind--;
5783
5784 if (ind >= 0)
5785 cur->pos = ind;
5786 else
5787 cur->pos = 0;
5788
5789}
5790
5791EAPI void
5792evas_textblock_cursor_line_char_first(Evas_Textblock_Cursor *cur)
5793{
5794 Evas_Object_Textblock *o;
5795 Evas_Object_Textblock_Line *ln = NULL;
5796 Evas_Object_Textblock_Item *it = NULL;
5797
5798 if (!cur) return;
5799 if (!cur->node) return;
5800 o = (Evas_Object_Textblock *)(cur->obj->object_data);
5801 if (!o->formatted.valid) _relayout(cur->obj);
5802
5803 _find_layout_item_match(cur, &ln, &it);
5804
5805 if (!ln) return;
5806 if (ln->items)
5807 {
5808 Evas_Object_Textblock_Item *i;
5809 it = ln->items;
5810 EINA_INLIST_FOREACH(ln->items, i)
5811 {
5812 if (it->text_pos > i->text_pos)
5813 {
5814 it = i;
5815 }
5816 }
5817 }
5818 if (it)
5819 {
5820 cur->pos = it->text_pos;
5821 cur->node = it->text_node;
5822 }
5823}
5824
5825EAPI void
5826evas_textblock_cursor_line_char_last(Evas_Textblock_Cursor *cur)
5827{
5828 Evas_Object_Textblock *o;
5829 Evas_Object_Textblock_Line *ln = NULL;
5830 Evas_Object_Textblock_Item *it = NULL;
5831
5832 if (!cur) return;
5833 if (!cur->node) return;
5834 o = (Evas_Object_Textblock *)(cur->obj->object_data);
5835 if (!o->formatted.valid) _relayout(cur->obj);
5836
5837 _find_layout_item_match(cur, &ln, &it);
5838
5839 if (!ln) return;
5840 if (ln->items)
5841 {
5842 Evas_Object_Textblock_Item *i;
5843 it = ln->items;
5844 EINA_INLIST_FOREACH(ln->items, i)
5845 {
5846 if (it->text_pos < i->text_pos)
5847 {
5848 it = i;
5849 }
5850 }
5851 }
5852 if (it)
5853 {
5854 size_t ind;
5855
5856 cur->node = it->text_node;
5857 cur->pos = it->text_pos;
5858 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
5859 {
5860 ind = _ITEM_TEXT(it)->text_props.text_len - 1;
5861 if (!IS_AT_END(_ITEM_TEXT(it), ind)) ind++;
5862 cur->pos += ind;
5863 }
5864 else if (!EINA_INLIST_GET(ln)->next && !EINA_INLIST_GET(ln->par)->next)
5865 {
5866 cur->pos++;
5867 }
5868 }
5869}
5870
5871/**
5872 * @internal
5873 * checks if a format (as a string) is visible/changes format and sets the
5874 * fnode properties accordingly.
5875 *
5876 * @param fnode the format node
5877 * @param s the string.
5878 */
5879static void
5880_evas_textblock_format_is_visible(Evas_Object_Textblock_Node_Format *fnode,
5881 const char *s)
5882{
5883 const char *item;
5884 Eina_Bool is_opener = EINA_TRUE;
5885
5886 fnode->visible = fnode->format_change = EINA_FALSE;
5887 fnode->anchor = ANCHOR_NONE;
5888 if (!s) return;
5889
5890 if (s[0] == '+' || s[0] == '-')
5891 {
5892 is_opener = (s[0] == '+');
5893 s++;
5894 fnode->format_change = EINA_TRUE;
5895 }
5896
5897 while ((item = _format_parse(&s)))
5898 {
5899 int itlen = s - item;
5900 /* We care about all of the formats even after a - except for
5901 * item which we don't care after a - because it's just a standard
5902 * closing */
5903 if ((!strncmp(item, "\n", itlen) || !strncmp(item, "\\n", itlen)) ||
5904 (!strncmp(item, "\t", itlen) || !strncmp(item, "\\t", itlen)) ||
5905 (!strncmp(item, "ps", itlen) && (itlen >= 2)) ||
5906 (!strncmp(item, "item", itlen) && (itlen >= 4) && is_opener))
5907 {
5908 fnode->visible = EINA_TRUE;
5909 }
5910
5911 if (is_opener && !strncmp(item, "a", itlen))
5912 {
5913 fnode->anchor = ANCHOR_A;
5914 }
5915 else if (is_opener && !strncmp(item, "item", itlen) && (itlen >= 4))
5916 {
5917 fnode->anchor = ANCHOR_ITEM;
5918 }
5919 }
5920}
5921
5922/**
5923 * Sets the cursor to the position of where the fmt points to.
5924 *
5925 * @param cur the cursor to update.
5926 * @param fmt the format to set according to.
5927 * @return nothing.
5928 */
5929static void __UNUSED__
5930_evas_textblock_cursor_node_text_at_format(Evas_Textblock_Cursor *cur, Evas_Object_Textblock_Node_Format *fmt)
5931{
5932 Evas_Object_Textblock_Node_Text *text;
5933 Evas_Object_Textblock_Node_Format *base_format;
5934 Evas_Object_Textblock_Node_Format *itr;
5935 size_t position = 0;
5936
5937 if (!cur || !fmt) return;
5938 /* Find the main format node */
5939 text = fmt->text_node;
5940 cur->node = text;
5941 base_format = text->format_node;
5942 EINA_INLIST_FOREACH(base_format, itr)
5943 {
5944 if (itr == fmt)
5945 {
5946 break;
5947 }
5948 position += itr->offset;
5949 }
5950 cur->pos = position;
5951
5952}
5953
5954
5955/**
5956 * @internal
5957 * Remove pairs of + and - formats and also remove formats without + or -
5958 * i.e formats that pair to themselves. Only removes invisible formats
5959 * that pair themselves, if you want to remove invisible formats that pair
5960 * themselves, please first change fmt->visible to EINA_FALSE.
5961 *
5962 * @param o the textblock object.
5963 * @param fmt the current format.
5964 */
5965static void
5966_evas_textblock_node_format_remove_matching(Evas_Object_Textblock *o,
5967 Evas_Object_Textblock_Node_Format *fmt)
5968{
5969 Evas_Object_Textblock_Node_Text *tnode;
5970 Eina_List *formats = NULL;
5971 size_t offset = 0;
5972
5973 if (!fmt) return;
5974
5975 tnode = fmt->text_node;
5976
5977 do
5978 {
5979 Evas_Object_Textblock_Node_Format *nnode;
5980 const char *fstr = fmt->orig_format;
5981
5982 nnode = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next);
5983 if (nnode)
5984 {
5985 offset = nnode->offset;
5986 }
5987
5988
5989 if (fstr && (*fstr == '+'))
5990 {
5991 formats = eina_list_prepend(formats, fmt);
5992 }
5993 else if (fstr && (*fstr == '-'))
5994 {
5995 Evas_Object_Textblock_Node_Format *fnode;
5996 size_t fstr_len;
5997 /* Skip the '-' */
5998 fstr++;
5999 fstr_len = strlen(fstr);
6000 /* Generic popper, just pop */
6001 if (((fstr[0] == ' ') && !fstr[1]) || !fstr[0])
6002 {
6003 fnode = eina_list_data_get(formats);
6004 formats = eina_list_remove_list(formats, formats);
6005 _evas_textblock_node_format_remove(o, fnode, 0);
6006 _evas_textblock_node_format_remove(o, fmt, 0);
6007 }
6008 /* Find the matching format and pop it, if the matching format
6009 * is our format, i.e the last one, pop and break. */
6010 else
6011 {
6012 Eina_List *i, *next;
6013 EINA_LIST_FOREACH_SAFE(formats, i, next, fnode)
6014 {
6015 if (_FORMAT_IS_CLOSER_OF(
6016 fnode->orig_format, fstr, fstr_len))
6017 {
6018 fnode = eina_list_data_get(i);
6019 formats = eina_list_remove_list(formats, i);
6020 _evas_textblock_node_format_remove(o, fnode, 0);
6021 _evas_textblock_node_format_remove(o, fmt, 0);
6022 break;
6023 }
6024 }
6025 }
6026 }
6027 else if (!fmt->visible)
6028 {
6029 _evas_textblock_node_format_remove(o, fmt, 0);
6030 }
6031 fmt = nnode;
6032 }
6033 while (fmt && (offset == 0) && (fmt->text_node == tnode));
6034 eina_list_free(formats);
6035}
6036/**
6037 * @internal
6038 * Add the offset (may be negative) to the first node after fmt which is
6039 * pointing to the text node tnode or to o->format_nodes if fmt is null
6040 * and it points to tnode.
6041 *
6042 * @param o the textblock object.
6043 * @param tnode the text node the format should point to.
6044 * @param fmt the current format.
6045 * @param offset the offest to add (may be negative).
6046 */
6047static void
6048_evas_textblock_node_format_adjust_offset(Evas_Object_Textblock *o,
6049 Evas_Object_Textblock_Node_Text *tnode,
6050 Evas_Object_Textblock_Node_Format *fmt, int offset)
6051{
6052 if (fmt)
6053 {
6054 fmt = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next);
6055 }
6056 else
6057 {
6058 fmt = o->format_nodes;
6059 }
6060 if (fmt && (tnode == fmt->text_node))
6061 {
6062 fmt->offset += offset;
6063 }
6064}
6065
6066/**
6067 * @internal
6068 * Removes a format node updating the offset of the next format node and the
6069 * text nodes pointing to this node.
6070 *
6071 * @param o the textblock object.
6072 * @param n the fromat node to remove
6073 */
6074static void
6075_evas_textblock_node_format_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n, int visible_adjustment)
6076{
6077 /* Update the text nodes about the change */
6078 {
6079 Evas_Object_Textblock_Node_Format *nnode;
6080 nnode = _NODE_FORMAT(EINA_INLIST_GET(n)->next);
6081 /* If there's a next node that belongs to the same text node
6082 * and the curret node was the main one, advance the format node */
6083 if (nnode && (nnode->text_node == n->text_node))
6084 {
6085 if (nnode->text_node->format_node == n)
6086 {
6087 nnode->text_node->format_node = nnode;
6088 }
6089 }
6090 else
6091 {
6092 Evas_Object_Textblock_Node_Text *tnode;
6093 /* If there's no next one update the text nodes */
6094 nnode = _NODE_FORMAT(EINA_INLIST_GET(n)->prev);
6095 tnode = n->text_node;
6096 /* Even if it's not the current text_node's main node
6097 * it can still be the next's. */
6098 if (tnode && (tnode->format_node != n))
6099 {
6100 tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next);
6101 }
6102 while (tnode && (tnode->format_node == n))
6103 {
6104 tnode->format_node = nnode;
6105 tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next);
6106 }
6107 }
6108 }
6109 _evas_textblock_node_format_adjust_offset(o, n->text_node, n,
6110 n->offset - visible_adjustment);
6111
6112 o->format_nodes = _NODE_FORMAT(eina_inlist_remove(
6113 EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n)));
6114 _evas_textblock_node_format_free(o, n);
6115}
6116
6117/**
6118 * @internal
6119 * Sets all the offsets of the format nodes between start and end in the text
6120 * node n to 0 and sets visibility to EINA_FALSE.
6121 * If end == -1 end means the end of the string.
6122 * Assumes there is a prev node or the current node will be preserved.
6123 *
6124 * @param n the text node the positinos refer to.
6125 * @param start the start of where to delete from.
6126 * @param end the end of the section to delete, if end == -1 it means the end of the string.
6127 * @returns #EINA_TRUE if removed a PS, false otherwise.
6128 */
6129static Eina_Bool
6130_evas_textblock_node_text_adjust_offsets_to_start(Evas_Object_Textblock *o,
6131 Evas_Object_Textblock_Node_Text *n, size_t start, int end)
6132{
6133 Evas_Object_Textblock_Node_Format *last_node, *itr;
6134 Evas_Object_Textblock_Node_Text *new_node;
6135 int use_end = 1;
6136 int delta = 0;
6137 int first = 1;
6138 int update_format_node;
6139 size_t pos = 0;
6140 int orig_end;
6141
6142 itr = n->format_node;
6143 if (!itr || (itr->text_node != n)) return EINA_FALSE;
6144
6145 orig_end = end;
6146 if ((end < 0) || ((size_t) end == eina_ustrbuf_length_get(n->unicode)))
6147 {
6148 use_end = 0;
6149 }
6150 else if (end > 0)
6151 {
6152 /* We don't want the last one */
6153 end--;
6154 }
6155
6156 /* If we are not removing the text node, all should stay in this text
6157 * node, otherwise, everything should move to the previous node */
6158 if ((start == 0) && !use_end)
6159 {
6160 new_node = _NODE_TEXT(EINA_INLIST_GET(n)->prev);
6161 if (!new_node)
6162 {
6163 new_node = n;
6164 }
6165 }
6166 else
6167 {
6168 new_node = n;
6169 }
6170
6171 /* Find the first node after start */
6172 while (itr && (itr->text_node == n))
6173 {
6174 pos += itr->offset;
6175 if (pos >= start)
6176 {
6177 break;
6178 }
6179 itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next);
6180 }
6181
6182 if (!itr || (itr->text_node != n))
6183 {
6184 return EINA_FALSE;
6185 }
6186
6187 update_format_node = ((itr == n->format_node) && (new_node != n));
6188 delta = orig_end - pos;
6189 itr->offset -= pos - start;
6190
6191 while (itr && (itr->text_node == n))
6192 {
6193 last_node = itr;
6194 itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next);
6195
6196 if (!first)
6197 {
6198 pos += last_node->offset;
6199 }
6200
6201 /* start is negative when this gets relevant */
6202 if (use_end && (pos > (size_t) end))
6203 {
6204 last_node->offset -= delta;
6205 break;
6206 }
6207
6208 delta = orig_end - pos;
6209 if (!first)
6210 {
6211 last_node->offset = 0;
6212 }
6213 else
6214 {
6215 first = 0;
6216 }
6217 last_node->visible = EINA_FALSE;
6218
6219 if (!itr || (itr && (itr->text_node != n)))
6220 {
6221 /* Remove the PS, and return since it's the end of the node */
6222 if (_IS_PARAGRAPH_SEPARATOR(o, last_node->format))
6223 {
6224 _evas_textblock_node_format_remove(o, last_node, 0);
6225 return EINA_TRUE;
6226 }
6227
6228 }
6229 last_node->text_node = new_node;
6230 if (update_format_node)
6231 {
6232 n->format_node = last_node;
6233 }
6234 }
6235
6236 return EINA_FALSE;
6237}
6238
6239/**
6240 * @internal
6241 * Removes all the format nodes between start and end in the text node n.
6242 * This function updates the offset of the next format node and the
6243 * text nodes pointing to it. if end == -1 end means the end of the string.
6244 *
6245 * @param o the textblock object.
6246 * @param n the text node the positinos refer to.
6247 * @param start the start of where to delete from.
6248 * @param end the end of the section to delete, if end == -1 it means the end of the string.
6249 */
6250static void
6251_evas_textblock_node_text_remove_formats_between(Evas_Object_Textblock *o,
6252 Evas_Object_Textblock_Node_Text *n, int start, int end)
6253{
6254 Evas_Object_Textblock_Node_Format *itr;
6255 int use_end = 1;
6256 int offset = end - start;
6257 itr = n->format_node;
6258
6259 if (itr)
6260 start -= itr->offset;
6261 if (offset < 0) offset = 0;
6262 if (end < 0) use_end = 0;
6263 while (itr && (itr->text_node == n))
6264 {
6265 Evas_Object_Textblock_Node_Format *nnode;
6266 int tmp_offset = 0;
6267
6268 /* start is negative when this gets relevant */
6269 if ((offset + start < 0) && use_end)
6270 {
6271 break;
6272 }
6273 nnode = _NODE_FORMAT(EINA_INLIST_GET(itr)->next);
6274 if (nnode)
6275 {
6276 tmp_offset = nnode->offset;
6277 }
6278 if (start <= 0)
6279 {
6280 /* Don't do visible adjustments because we are removing the visual
6281 * chars anyway and taking those into account */
6282 _evas_textblock_node_format_remove(o, itr, 0);
6283 }
6284 start -= tmp_offset;
6285 itr = nnode;
6286 }
6287}
6288
6289/**
6290 * @internal
6291 * Returns the first format in the range between start and end in the textblock
6292 * n.
6293 *
6294 * @param o the textblock object.
6295 * @param n the text node the positinos refer to.
6296 * @param start the start of where to delete from.
6297 * @param end the end of the section to delete, if end == -1 it means the end of the string.
6298 */
6299static Evas_Object_Textblock_Node_Format *
6300_evas_textblock_node_text_get_first_format_between(
6301 Evas_Object_Textblock_Node_Text *n, int start, int end)
6302{
6303 Evas_Object_Textblock_Node_Format *itr;
6304 int use_end = 1;
6305 itr = n->format_node;
6306 if (end < 0) use_end = 0;
6307 while (itr && (itr->text_node == n))
6308 {
6309 start -= itr->offset;
6310 end -= itr->offset;
6311 if ((end <= 0) && use_end)
6312 {
6313 break;
6314 }
6315 if (start <= 0)
6316 {
6317 return itr;
6318 }
6319 itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next);
6320 }
6321 return NULL;
6322}
6323
6324/**
6325 * Removes a text node and the corresponding format nodes.
6326 *
6327 * @param o the textblock objec.t
6328 * @param n the node to remove.
6329 */
6330static void
6331_evas_textblock_node_text_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n)
6332{
6333 _evas_textblock_node_text_adjust_offsets_to_start(o, n, 0, -1);
6334
6335 o->text_nodes = _NODE_TEXT(eina_inlist_remove(
6336 EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n)));
6337 _evas_textblock_node_text_free(n);
6338}
6339
6340/**
6341 * @internal
6342 * Return the position where the formats starts at.
6343 *
6344 * @param fmt the format to return the position of.
6345 * @return the position of the format in the text node it points to.
6346 */
6347static size_t
6348_evas_textblock_node_format_pos_get(const Evas_Object_Textblock_Node_Format *fmt)
6349{
6350 Evas_Object_Textblock_Node_Text *text;
6351 Evas_Object_Textblock_Node_Format *base_format;
6352 Evas_Object_Textblock_Node_Format *itr;
6353 size_t position = 0;
6354
6355 if (!fmt) return 0;
6356 /* Find the main format node */
6357 text = fmt->text_node;
6358 base_format = text->format_node;
6359 EINA_INLIST_FOREACH(base_format, itr)
6360 {
6361 if (itr == fmt)
6362 {
6363 break;
6364 }
6365 position += itr->offset;
6366 }
6367 return position + fmt->offset;
6368}
6369
6370EAPI int
6371evas_textblock_cursor_pos_get(const Evas_Textblock_Cursor *cur)
6372{
6373 Evas_Object_Textblock *o;
6374 Evas_Object_Textblock_Node_Text *n;
6375 size_t npos = 0;
6376
6377 if (!cur) return -1;
6378 if (!cur->node) return 0;
6379 o = (Evas_Object_Textblock *)(cur->obj->object_data);
6380 n = o->text_nodes;
6381 while (n != cur->node)
6382 {
6383 npos += eina_ustrbuf_length_get(n->unicode);
6384 n = _NODE_TEXT(EINA_INLIST_GET(n)->next);
6385 }
6386 return npos + cur->pos;
6387}
6388
6389EAPI void
6390evas_textblock_cursor_pos_set(Evas_Textblock_Cursor *cur, int _pos)
6391{
6392 Evas_Object_Textblock *o;
6393 Evas_Object_Textblock_Node_Text *n;
6394 size_t pos;
6395
6396 if (!cur) return;
6397 o = (Evas_Object_Textblock *)(cur->obj->object_data);
6398
6399 if (_pos < 0)
6400 {
6401 pos = 0;
6402 }
6403 else
6404 {
6405 pos = (size_t) _pos;
6406 }
6407
6408 n = o->text_nodes;
6409 while (n && (pos >= eina_ustrbuf_length_get(n->unicode)))
6410 {
6411 pos -= eina_ustrbuf_length_get(n->unicode);
6412 n = _NODE_TEXT(EINA_INLIST_GET(n)->next);
6413 }
6414
6415 if (n)
6416 {
6417 cur->node = n;
6418 cur->pos = pos;
6419 }
6420 else if (o->text_nodes)
6421 {
6422 /* In case we went pass the last node, we need to put the cursor
6423 * at the absolute end. */
6424 Evas_Object_Textblock_Node_Text *last_n;
6425
6426 last_n = _NODE_TEXT(EINA_INLIST_GET(o->text_nodes)->last);
6427 pos = eina_ustrbuf_length_get(last_n->unicode);
6428
6429 cur->node = last_n;
6430 cur->pos = pos;
6431 }
6432
6433}
6434
6435EAPI Eina_Bool
6436evas_textblock_cursor_line_set(Evas_Textblock_Cursor *cur, int line)
6437{
6438 Evas_Object_Textblock *o;
6439 Evas_Object_Textblock_Line *ln;
6440 Evas_Object_Textblock_Item *it;
6441
6442 if (!cur) return EINA_FALSE;
6443 o = (Evas_Object_Textblock *)(cur->obj->object_data);
6444 if (!o->formatted.valid) _relayout(cur->obj);
6445
6446 ln = _find_layout_line_num(cur->obj, line);
6447 if (!ln) return EINA_FALSE;
6448 it = (Evas_Object_Textblock_Item *)ln->items;
6449 if (it)
6450 {
6451 cur->pos = it->text_pos;
6452 cur->node = it->text_node;
6453 }
6454 else
6455 {
6456 cur->pos = 0;
6457
6458 cur->node = o->text_nodes;
6459 }
6460 return EINA_TRUE;
6461}
6462
6463EAPI int
6464evas_textblock_cursor_compare(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2)
6465{
6466 Eina_Inlist *l1, *l2;
6467
6468 if (!cur1) return 0;
6469 if (!cur2) return 0;
6470 if (cur1->obj != cur2->obj) return 0;
6471 if ((!cur1->node) || (!cur2->node)) return 0;
6472 if (cur1->node == cur2->node)
6473 {
6474 if (cur1->pos < cur2->pos) return -1; /* cur1 < cur2 */
6475 else if (cur1->pos > cur2->pos) return 1; /* cur2 < cur1 */
6476 return 0;
6477 }
6478 for (l1 = EINA_INLIST_GET(cur1->node),
6479 l2 = EINA_INLIST_GET(cur1->node); (l1) || (l2);)
6480 {
6481 if (l1 == EINA_INLIST_GET(cur2->node)) return 1; /* cur2 < cur 1 */
6482 else if (l2 == EINA_INLIST_GET(cur2->node)) return -1; /* cur1 < cur 2 */
6483 else if (!l1) return -1; /* cur1 < cur 2 */
6484 else if (!l2) return 1; /* cur2 < cur 1 */
6485 l1 = l1->prev;
6486 l2 = l2->next;
6487 }
6488 return 0;
6489}
6490
6491EAPI void
6492evas_textblock_cursor_copy(const Evas_Textblock_Cursor *cur, Evas_Textblock_Cursor *cur_dest)
6493{
6494 if (!cur) return;
6495 if (!cur_dest) return;
6496 if (cur->obj != cur_dest->obj) return;
6497 cur_dest->pos = cur->pos;
6498 cur_dest->node = cur->node;
6499
6500}
6501
6502
6503/* text controls */
6504/**
6505 * @internal
6506 * Free a text node. Shouldn't be used usually, it's better to use
6507 * @ref _evas_textblock_node_text_remove for most cases .
6508 *
6509 * @param n the text node to free
6510 * @see _evas_textblock_node_text_remove
6511 */
6512static void
6513_evas_textblock_node_text_free(Evas_Object_Textblock_Node_Text *n)
6514{
6515 if (!n) return;
6516 eina_ustrbuf_free(n->unicode);
6517 if (n->utf8)
6518 free(n->utf8);
6519 if (n->par)
6520 n->par->text_node = NULL;
6521 free(n);
6522}
6523
6524/**
6525 * @internal
6526 * Create a new text node
6527 *
6528 * @return the new text node.
6529 */
6530static Evas_Object_Textblock_Node_Text *
6531_evas_textblock_node_text_new(void)
6532{
6533 Evas_Object_Textblock_Node_Text *n;
6534
6535 n = calloc(1, sizeof(Evas_Object_Textblock_Node_Text));
6536 n->unicode = eina_ustrbuf_new();
6537 /* We want to layout each paragraph at least once. */
6538 n->dirty = EINA_TRUE;
6539 n->is_new = EINA_TRUE;
6540
6541 return n;
6542}
6543
6544/**
6545 * @internal
6546 * Break a paragraph. This does not add a PS but only splits the paragraph
6547 * where a ps was just added!
6548 *
6549 * @param cur the cursor to break at.
6550 * @param fnode the format node of the PS just added.
6551 * @return Returns no value.
6552 */
6553static void
6554_evas_textblock_cursor_break_paragraph(Evas_Textblock_Cursor *cur,
6555 Evas_Object_Textblock_Node_Format *fnode)
6556{
6557 Evas_Object_Textblock *o;
6558 Evas_Object_Textblock_Node_Text *n;
6559
6560 if (!cur) return;
6561 o = (Evas_Object_Textblock *)(cur->obj->object_data);
6562
6563 n = _evas_textblock_node_text_new();
6564 o->text_nodes = _NODE_TEXT(eina_inlist_append_relative(
6565 EINA_INLIST_GET(o->text_nodes),
6566 EINA_INLIST_GET(n),
6567 EINA_INLIST_GET(cur->node)));
6568 /* Handle text and format changes. */
6569 if (cur->node)
6570 {
6571 Evas_Object_Textblock_Node_Format *nnode;
6572 size_t len, start;
6573 const Eina_Unicode *text;
6574
6575 /* If there was a format node in the delete range,
6576 * make it our format and update the text_node fields,
6577 * otherwise, use the paragraph separator
6578 * of the previous paragraph. */
6579 nnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
6580 if (nnode && (nnode->text_node == cur->node))
6581 {
6582 n->format_node = nnode;
6583 nnode->offset--; /* We don't have to take the replacement char
6584 into account anymore */
6585 while (nnode && (nnode->text_node == cur->node))
6586 {
6587 nnode->text_node = n;
6588 nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next);
6589 }
6590 }
6591 else
6592 {
6593 n->format_node = fnode;
6594 }
6595
6596 /* cur->pos now points to the PS, move after. */
6597 start = cur->pos + 1;
6598 len = eina_ustrbuf_length_get(cur->node->unicode) - start;
6599 if (len > 0)
6600 {
6601 text = eina_ustrbuf_string_get(cur->node->unicode);
6602 eina_ustrbuf_append_length(n->unicode, text + start, len);
6603 eina_ustrbuf_remove(cur->node->unicode, start, start + len);
6604 cur->node->dirty = EINA_TRUE;
6605 }
6606 }
6607 else
6608 {
6609 fnode = o->format_nodes;
6610 if (fnode)
6611 {
6612 fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->last);
6613 }
6614 n->format_node = fnode;
6615 }
6616}
6617
6618/**
6619 * @internal
6620 * Set the node and offset of all the curs after cur.
6621 *
6622 * @param cur the cursor.
6623 * @param n the current textblock node.
6624 * @param new_node the new node to set.
6625 */
6626static void
6627_evas_textblock_cursors_set_node(Evas_Object_Textblock *o,
6628 const Evas_Object_Textblock_Node_Text *n,
6629 Evas_Object_Textblock_Node_Text *new_node)
6630{
6631 Eina_List *l;
6632 Evas_Textblock_Cursor *data;
6633
6634 if (n == o->cursor->node)
6635 {
6636 o->cursor->pos = 0;
6637 o->cursor->node = new_node;
6638 }
6639 EINA_LIST_FOREACH(o->cursors, l, data)
6640 {
6641 if (n == data->node)
6642 {
6643 data->pos = 0;
6644 data->node = new_node;
6645 }
6646 }
6647}
6648
6649/**
6650 * @internal
6651 * Update the offset of all the cursors after cur.
6652 *
6653 * @param cur the cursor.
6654 * @param n the current textblock node.
6655 * @param start the starting pos.
6656 * @param offset how much to adjust (can be negative).
6657 */
6658static void
6659_evas_textblock_cursors_update_offset(const Evas_Textblock_Cursor *cur,
6660 const Evas_Object_Textblock_Node_Text *n,
6661 size_t start, int offset)
6662{
6663 Eina_List *l;
6664 Evas_Textblock_Cursor *data;
6665 Evas_Object_Textblock *o;
6666 o = (Evas_Object_Textblock *)(cur->obj->object_data);
6667
6668 if (cur != o->cursor)
6669 {
6670 if ((n == o->cursor->node) &&
6671 (o->cursor->pos > start))
6672 {
6673 if ((offset < 0) && (o->cursor->pos <= (size_t) (-1 * offset)))
6674 {
6675 o->cursor->pos = 0;
6676 }
6677 else
6678 {
6679 o->cursor->pos += offset;
6680 }
6681 }
6682 }
6683 EINA_LIST_FOREACH(o->cursors, l, data)
6684 {
6685 if (data != cur)
6686 {
6687 if ((n == data->node) &&
6688 (data->pos > start))
6689 {
6690 if ((offset < 0) && (data->pos <= (size_t) (-1 * offset)))
6691 {
6692 data->pos = 0;
6693 }
6694 else
6695 {
6696 data->pos += offset;
6697 }
6698 }
6699 else if (!data->node)
6700 {
6701 data->node = o->text_nodes;
6702 data->pos = 0;
6703 }
6704 }
6705 }
6706}
6707
6708/**
6709 * @internal
6710 * Mark that the textblock has changed.
6711 *
6712 * @param o the textblock object.
6713 * @param obj the evas object.
6714 */
6715static void
6716_evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj)
6717{
6718 o->formatted.valid = 0;
6719 o->native.valid = 0;
6720 o->content_changed = 1;
6721 if (o->markup_text)
6722 {
6723 free(o->markup_text);
6724 o->markup_text = NULL;
6725 }
6726
6727 evas_object_change(obj);
6728}
6729
6730static void
6731_evas_textblock_invalidate_all(Evas_Object_Textblock *o)
6732{
6733 Evas_Object_Textblock_Node_Text *n;
6734
6735 EINA_INLIST_FOREACH(o->text_nodes, n)
6736 {
6737 n->dirty = EINA_TRUE;
6738 }
6739}
6740
6741EAPI int
6742evas_textblock_cursor_text_append(Evas_Textblock_Cursor *cur, const char *_text)
6743{
6744 Evas_Object_Textblock *o;
6745 Evas_Object_Textblock_Node_Text *n;
6746 Evas_Object_Textblock_Node_Format *fnode = NULL;
6747 Eina_Unicode *text;
6748 int len = 0;
6749
6750 if (!cur) return 0;
6751 text = eina_unicode_utf8_to_unicode(_text, &len);
6752 o = (Evas_Object_Textblock *)(cur->obj->object_data);
6753
6754 n = cur->node;
6755 if (n)
6756 {
6757 Evas_Object_Textblock_Node_Format *nnode;
6758 fnode = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur);
6759 fnode = _evas_textblock_node_format_last_at_off(fnode);
6760 /* find the node after the current in the same paragraph
6761 * either we find one and then take the next, or we try to get
6762 * the first for the paragraph which must be after our position */
6763 if (fnode)
6764 {
6765 if (!evas_textblock_cursor_format_is_visible_get(cur))
6766 {
6767 nnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
6768 if (nnode && (nnode->text_node == n))
6769 {
6770 fnode = nnode;
6771 }
6772 else
6773 {
6774 fnode = NULL;
6775 }
6776 }
6777 }
6778 else
6779 {
6780 fnode = n->format_node;
6781 }
6782 }
6783 else if (o->text_nodes)
6784 {
6785 n = cur->node = o->text_nodes;
6786 cur->pos = 0;
6787 }
6788 else
6789 {
6790 n = _evas_textblock_node_text_new();
6791 o->text_nodes = _NODE_TEXT(eina_inlist_append(
6792 EINA_INLIST_GET(o->text_nodes),
6793 EINA_INLIST_GET(n)));
6794 cur->node = n;
6795 }
6796
6797 eina_ustrbuf_insert_length(n->unicode, text, len, cur->pos);
6798 /* Advance the formats */
6799 if (fnode && (fnode->text_node == cur->node))
6800 fnode->offset += len;
6801
6802 /* Update all the cursors after our position. */
6803 _evas_textblock_cursors_update_offset(cur, cur->node, cur->pos, len);
6804
6805 _evas_textblock_changed(o, cur->obj);
6806 n->dirty = EINA_TRUE;
6807 free(text);
6808
6809 if (!o->cursor->node)
6810 o->cursor->node = o->text_nodes;
6811 return len;
6812}
6813
6814EAPI int
6815evas_textblock_cursor_text_prepend(Evas_Textblock_Cursor *cur, const char *_text)
6816{
6817 int len;
6818 /*append is essentially prepend without advancing */
6819 len = evas_textblock_cursor_text_append(cur, _text);
6820 cur->pos += len; /*Advance */
6821 return len;
6822}
6823
6824/**
6825 * @internal
6826 * Free a format node
6827 *
6828 * @param o the textblock object
6829 * @param n the format node to free
6830 */
6831static void
6832_evas_textblock_node_format_free(Evas_Object_Textblock *o,
6833 Evas_Object_Textblock_Node_Format *n)
6834{
6835 if (!n) return;
6836 eina_stringshare_del(n->format);
6837 eina_stringshare_del(n->orig_format);
6838 if (n->anchor == ANCHOR_ITEM)
6839 o->anchors_item = eina_list_remove(o->anchors_item, n);
6840 else if (n->anchor == ANCHOR_A)
6841 o->anchors_a = eina_list_remove(o->anchors_a, n);
6842 free(n);
6843}
6844
6845/**
6846 * @internal
6847 * Create a new format node.
6848 *
6849 * @param format the text to create the format node from.
6850 * @param o the textblock object.
6851 * @return Returns the new format node
6852 */
6853static Evas_Object_Textblock_Node_Format *
6854_evas_textblock_node_format_new(Evas_Object_Textblock *o, const char *_format)
6855{
6856 Evas_Object_Textblock_Node_Format *n;
6857 const char *format = _format;
6858
6859 n = calloc(1, sizeof(Evas_Object_Textblock_Node_Format));
6860 /* Create orig_format and format */
6861 if (format[0] == '<')
6862 {
6863 const char *match;
6864 size_t format_len;
6865 size_t replace_len;
6866
6867 format++; /* Advance after '<' */
6868 format_len = strlen(format);
6869 if (format[format_len - 1] == '>')
6870 format_len--; /* We don't care about '>' */
6871
6872 match = _style_match_tag(o->style, format, format_len, &replace_len);
6873 if (match)
6874 {
6875 if ((match[0] == '+') || (match[0] == '-'))
6876 {
6877 char *norm_format;
6878 norm_format = malloc(format_len + 2 + 1);
6879 memcpy(norm_format, match, 2);
6880 memcpy(norm_format + 2, format, format_len);
6881 norm_format[format_len + 2] = '\0';
6882 n->orig_format =
6883 eina_stringshare_add_length(norm_format, format_len + 2);
6884 free(norm_format);
6885 }
6886 else
6887 {
6888 n->orig_format =
6889 eina_stringshare_add_length(format, format_len);
6890 }
6891 n->format = eina_stringshare_add(match);
6892 }
6893 else
6894 {
6895 char *norm_format;
6896
6897 norm_format = malloc(format_len + 2 + 1);
6898 if (norm_format)
6899 {
6900 if (format[0] == '/')
6901 {
6902 memcpy(norm_format, "- ", 2);
6903 memcpy(norm_format + 2, format + 1, format_len - 1);
6904 norm_format[format_len + 2 - 1] = '\0';
6905 }
6906 else
6907 {
6908 memcpy(norm_format, "+ ", 2);
6909 memcpy(norm_format + 2, format, format_len);
6910 norm_format[format_len + 2] = '\0';
6911 }
6912 n->orig_format = eina_stringshare_add(norm_format);
6913 free(norm_format);
6914 }
6915 n->format = eina_stringshare_ref(n->orig_format);
6916 }
6917 }
6918 /* Just use as is, it's a special format. */
6919 else
6920 {
6921 n->orig_format = eina_stringshare_add(format);
6922 n->format = eina_stringshare_ref(n->orig_format);
6923 }
6924
6925 format = n->format;
6926
6927 _evas_textblock_format_is_visible(n, format);
6928 if (n->anchor == ANCHOR_A)
6929 {
6930 o->anchors_a = eina_list_append(o->anchors_a, n);
6931 }
6932 else if (n->anchor == ANCHOR_ITEM)
6933 {
6934 o->anchors_item = eina_list_append(o->anchors_item, n);
6935 }
6936 n->is_new = EINA_TRUE;
6937
6938 return n;
6939}
6940
6941static Eina_Bool
6942_evas_textblock_cursor_is_at_the_end(const Evas_Textblock_Cursor *cur)
6943{
6944 const Eina_Unicode *text;
6945
6946 if (!cur) return EINA_FALSE;
6947 if (!cur->node) return EINA_FALSE;
6948 text = eina_ustrbuf_string_get(cur->node->unicode);
6949 return ((text[cur->pos] == 0) && (!EINA_INLIST_GET(cur->node)->next)) ?
6950 EINA_TRUE : EINA_FALSE;
6951}
6952
6953EAPI Eina_Bool
6954evas_textblock_cursor_format_append(Evas_Textblock_Cursor *cur, const char *format)
6955{
6956 Evas_Object_Textblock *o;
6957 Evas_Object_Textblock_Node_Format *n;
6958 Eina_Bool is_visible;
6959
6960 if (!cur) return EINA_FALSE;
6961 if ((!format) || (format[0] == 0)) return EINA_FALSE;
6962 o = (Evas_Object_Textblock *)(cur->obj->object_data);
6963 /* We should always have at least one text node */
6964 if (!o->text_nodes)
6965 {
6966 evas_textblock_cursor_text_prepend(cur, "");
6967 }
6968
6969 n = _evas_textblock_node_format_new(o, format);
6970 is_visible = n->visible;
6971 format = n->format;
6972 if (!cur->node)
6973 {
6974 o->format_nodes = _NODE_FORMAT(eina_inlist_append(
6975 EINA_INLIST_GET(o->format_nodes),
6976 EINA_INLIST_GET(n)));
6977 cur->pos = 0;
6978 n->text_node = (EINA_INLIST_GET(n)->prev) ?
6979 _NODE_FORMAT(EINA_INLIST_GET(n)->prev)->text_node :
6980 o->text_nodes;
6981 cur->node = n->text_node;
6982 }
6983 else
6984 {
6985 Evas_Object_Textblock_Node_Format *fmt;
6986 fmt = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur);
6987 n->text_node = cur->node;
6988 if (!fmt)
6989 {
6990 o->format_nodes = _NODE_FORMAT(eina_inlist_prepend(
6991 EINA_INLIST_GET(o->format_nodes),
6992 EINA_INLIST_GET(n)));
6993 n->offset = cur->pos;
6994 }
6995 else
6996 {
6997 if (evas_textblock_cursor_format_is_visible_get(cur))
6998 {
6999 o->format_nodes = _NODE_FORMAT(eina_inlist_prepend_relative(
7000 EINA_INLIST_GET(o->format_nodes),
7001 EINA_INLIST_GET(n),
7002 EINA_INLIST_GET(fmt)
7003 ));
7004 n->offset = fmt->offset;
7005 if (fmt->text_node->format_node == fmt)
7006 {
7007 fmt->text_node->format_node = n;
7008 }
7009 }
7010 else
7011 {
7012 fmt = _evas_textblock_node_format_last_at_off(fmt);
7013 o->format_nodes = _NODE_FORMAT(eina_inlist_append_relative(
7014 EINA_INLIST_GET(o->format_nodes),
7015 EINA_INLIST_GET(n),
7016 EINA_INLIST_GET(fmt)
7017 ));
7018 if (fmt->text_node != cur->node)
7019 {
7020 n->offset = cur->pos;
7021 }
7022 else
7023 {
7024 n->offset = cur->pos -
7025 _evas_textblock_node_format_pos_get(fmt);
7026 }
7027 }
7028 }
7029 /* Adjust differently if we insert a format char */
7030 if (is_visible)
7031 {
7032 _evas_textblock_node_format_adjust_offset(o, cur->node, n,
7033 -(n->offset - 1));
7034 }
7035 else
7036 {
7037 _evas_textblock_node_format_adjust_offset(o, cur->node, n,
7038 -n->offset);
7039 }
7040
7041 if (!fmt || (fmt->text_node != cur->node))
7042 {
7043 cur->node->format_node = n;
7044 }
7045 }
7046 if (is_visible && cur->node)
7047 {
7048 Eina_Unicode insert_char;
7049 /* Insert a visual representation according to the type of the
7050 format */
7051 if (_IS_PARAGRAPH_SEPARATOR(o, format))
7052 insert_char = _PARAGRAPH_SEPARATOR;
7053 else if (_IS_LINE_SEPARATOR(format))
7054 insert_char = '\n';
7055 else if (_IS_TAB(format))
7056 insert_char = '\t';
7057 else
7058 insert_char = EVAS_TEXTBLOCK_REPLACEMENT_CHAR;
7059
7060 eina_ustrbuf_insert_char(cur->node->unicode, insert_char, cur->pos);
7061
7062 /* Advance all the cursors after our cursor */
7063 _evas_textblock_cursors_update_offset(cur, cur->node, cur->pos, 1);
7064 if (_IS_PARAGRAPH_SEPARATOR(o, format))
7065 {
7066 _evas_textblock_cursor_break_paragraph(cur, n);
7067 }
7068 else
7069 {
7070 /* Handle visible format nodes here */
7071 cur->node->dirty = EINA_TRUE;
7072 n->is_new = EINA_FALSE;
7073 }
7074 }
7075 else
7076 {
7077 o->format_changed = EINA_TRUE;
7078 }
7079
7080 _evas_textblock_changed(o, cur->obj);
7081
7082 if (!o->cursor->node)
7083 o->cursor->node = o->text_nodes;
7084 return is_visible;
7085}
7086
7087EAPI Eina_Bool
7088evas_textblock_cursor_format_prepend(Evas_Textblock_Cursor *cur, const char *format)
7089{
7090 Eina_Bool is_visible;
7091 /* append is essentially prepend without advancing */
7092 is_visible = evas_textblock_cursor_format_append(cur, format);
7093 if (is_visible)
7094 {
7095 /* Advance after the replacement char */
7096 evas_textblock_cursor_char_next(cur);
7097 }
7098
7099 return is_visible;
7100}
7101
7102
7103EAPI void
7104evas_textblock_cursor_char_delete(Evas_Textblock_Cursor *cur)
7105{
7106 Evas_Object_Textblock *o;
7107 Evas_Object_Textblock_Node_Text *n, *n2;
7108 const Eina_Unicode *text;
7109 int chr, ind, ppos;
7110
7111 if (!cur || !cur->node) return;
7112 o = (Evas_Object_Textblock *)(cur->obj->object_data);
7113 n = cur->node;
7114
7115 text = eina_ustrbuf_string_get(n->unicode);
7116 ind = cur->pos;
7117 if (text[ind])
7118 chr = text[ind++];
7119 else
7120 chr = 0;
7121
7122 if (chr == 0) return;
7123 ppos = cur->pos;
7124 eina_ustrbuf_remove(n->unicode, cur->pos, ind);
7125 /* Remove a format node if needed, and remove the char only if the
7126 * fmt node is not visible */
7127 {
7128 Eina_Bool should_merge = EINA_FALSE;
7129 Evas_Object_Textblock_Node_Format *fmt, *fmt2;
7130 fmt = _evas_textblock_cursor_node_format_at_pos_get(cur);
7131 if (fmt)
7132 {
7133 const char *format = NULL;
7134 Evas_Object_Textblock_Node_Format *last_fmt;
7135 /* If there's a PS it must be the last become it delimits paragraphs */
7136 last_fmt = _evas_textblock_node_format_last_at_off(fmt);
7137 format = last_fmt->format;
7138 if (format && _IS_PARAGRAPH_SEPARATOR(o, format))
7139 {
7140 /* If it was a paragraph separator, we should merge the
7141 * current with the next, there must be a next. */
7142 should_merge = EINA_TRUE;
7143 }
7144 /* If a singnular, mark as invisible, so we'll delete it. */
7145 if (!format || ((*format != '+') && (*format != '-')))
7146 {
7147 last_fmt->visible = EINA_FALSE;
7148 }
7149 }
7150
7151 fmt2 = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur);
7152 fmt2 = _evas_textblock_node_format_last_at_off(fmt2);
7153 _evas_textblock_node_format_adjust_offset(o, cur->node, fmt2,
7154 -(ind - cur->pos));
7155
7156 if (should_merge)
7157 {
7158 _evas_textblock_cursor_nodes_merge(cur);
7159 }
7160
7161 _evas_textblock_node_format_remove_matching(o, fmt);
7162 }
7163
7164 if (cur->pos == eina_ustrbuf_length_get(n->unicode))
7165 {
7166 n2 = _NODE_TEXT(EINA_INLIST_GET(n)->next);
7167 if (n2)
7168 {
7169 cur->node = n2;
7170 cur->pos = 0;
7171 }
7172 }
7173
7174 _evas_textblock_cursors_update_offset(cur, n, ppos, -(ind - ppos));
7175 _evas_textblock_changed(o, cur->obj);
7176 cur->node->dirty = EINA_TRUE;
7177}
7178
7179EAPI void
7180evas_textblock_cursor_range_delete(Evas_Textblock_Cursor *cur1, Evas_Textblock_Cursor *cur2)
7181{
7182 Evas_Object_Textblock_Node_Format *fnode = NULL;
7183 Evas_Object_Textblock *o;
7184 Evas_Object_Textblock_Node_Text *n1, *n2;
7185 Eina_Bool should_merge = EINA_FALSE, reset_cursor = EINA_FALSE;
7186
7187 if (!cur1 || !cur1->node) return;
7188 if (!cur2 || !cur2->node) return;
7189 if (cur1->obj != cur2->obj) return;
7190 o = (Evas_Object_Textblock *)(cur1->obj->object_data);
7191 if (evas_textblock_cursor_compare(cur1, cur2) > 0)
7192 {
7193 Evas_Textblock_Cursor *tc;
7194
7195 tc = cur1;
7196 cur1 = cur2;
7197 cur2 = tc;
7198 }
7199 n1 = cur1->node;
7200 n2 = cur2->node;
7201 if ((evas_textblock_cursor_compare(o->cursor, cur1) >= 0) &&
7202 (evas_textblock_cursor_compare(cur2, o->cursor) >= 0))
7203 {
7204 reset_cursor = EINA_TRUE;
7205 }
7206
7207
7208 if (n1 == n2)
7209 {
7210 if ((cur1->pos == 0) &&
7211 (cur2->pos == eina_ustrbuf_length_get(n1->unicode)))
7212 {
7213 _evas_textblock_node_text_remove_formats_between(o, n1, 0, -1);
7214 }
7215 else
7216 {
7217 should_merge = _evas_textblock_node_text_adjust_offsets_to_start(o,
7218 n1, cur1->pos, cur2->pos);
7219 }
7220 eina_ustrbuf_remove(n1->unicode, cur1->pos, cur2->pos);
7221 _evas_textblock_cursors_update_offset(cur1, cur1->node, cur1->pos, - (cur2->pos - cur1->pos));
7222 }
7223 else
7224 {
7225 Evas_Object_Textblock_Node_Text *n;
7226 int len;
7227 _evas_textblock_node_text_adjust_offsets_to_start(o, n1, cur1->pos, -1);
7228 n = _NODE_TEXT(EINA_INLIST_GET(n1)->next);
7229 /* Remove all the text nodes between */
7230 while (n && (n != n2))
7231 {
7232 Evas_Object_Textblock_Node_Text *nnode;
7233
7234 nnode = _NODE_TEXT(EINA_INLIST_GET(n)->next);
7235 _evas_textblock_cursors_set_node(o, n, n1);
7236 _evas_textblock_node_text_remove(o, n);
7237 n = nnode;
7238 }
7239 should_merge = _evas_textblock_node_text_adjust_offsets_to_start(o, n2,
7240 0, cur2->pos);
7241
7242 /* Remove the formats and the strings in the first and last nodes */
7243 len = eina_ustrbuf_length_get(n1->unicode);
7244 eina_ustrbuf_remove(n1->unicode, cur1->pos, len);
7245 eina_ustrbuf_remove(n2->unicode, 0, cur2->pos);
7246 /* Merge the nodes because we removed the PS */
7247 _evas_textblock_cursors_update_offset(cur1, cur1->node, cur1->pos,
7248 - cur1->pos);
7249 _evas_textblock_cursors_update_offset(cur2, cur2->node, 0, - cur2->pos);
7250 _evas_textblock_nodes_merge(o, n1);
7251 }
7252 fnode = _evas_textblock_cursor_node_format_at_pos_get(cur1);
7253
7254 if (should_merge)
7255 {
7256 /* We call this function instead of the cursor one because we already
7257 * updated the cursors */
7258 _evas_textblock_nodes_merge(o, n1);
7259 }
7260 _evas_textblock_node_format_remove_matching(o, fnode);
7261
7262 evas_textblock_cursor_copy(cur1, cur2);
7263 if (reset_cursor)
7264 evas_textblock_cursor_copy(cur1, o->cursor);
7265
7266 _evas_textblock_changed(o, cur1->obj);
7267 n1->dirty = n2->dirty = EINA_TRUE;
7268}
7269
7270
7271EAPI char *
7272evas_textblock_cursor_content_get(const Evas_Textblock_Cursor *cur)
7273{
7274 const Eina_Unicode *ustr;
7275 Eina_Unicode buf[2];
7276 char *s;
7277 if (!cur || !cur->node) return NULL;
7278 if (evas_textblock_cursor_format_is_visible_get(cur))
7279 {
7280 size_t len;
7281 const char *fstr;
7282 char *ret;
7283 int pop = 0;
7284 fstr = evas_textblock_node_format_text_get(
7285 _evas_textblock_node_visible_at_pos_get(
7286 evas_textblock_cursor_format_get(cur)));
7287
7288 if (!fstr)
7289 return NULL;
7290
7291 if (*fstr == '-') pop = 1;
7292 while ((*fstr == ' ') || (*fstr == '+') || (*fstr == '-')) fstr++;
7293 len = strlen(fstr);
7294
7295 {
7296 char *tmp;
7297 if (pop)
7298 {
7299 ret = tmp = malloc(len + 3 + 1); /* </> and the null */
7300 memcpy(tmp, "</", 2);
7301 tmp += 2;
7302 }
7303 else
7304 {
7305 ret = tmp = malloc(len + 2 + 1); /* <> and the null */
7306 *tmp = '<';
7307 tmp++;
7308 }
7309 memcpy(tmp, fstr, len);
7310 memcpy(tmp + len, ">", 2); /* Including the null */
7311 }
7312
7313 return ret;
7314 }
7315
7316 ustr = eina_ustrbuf_string_get(cur->node->unicode);
7317 buf[0] = ustr[cur->pos];
7318 buf[1] = 0;
7319 s = eina_unicode_unicode_to_utf8(buf, NULL);
7320
7321 return s;
7322}
7323
7324static char *
7325_evas_textblock_cursor_range_text_markup_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *_cur2)
7326{
7327 Evas_Object_Textblock *o;
7328 Evas_Object_Textblock_Node_Text *tnode;
7329 Eina_Strbuf *buf;
7330 Evas_Textblock_Cursor *cur2;
7331 buf = eina_strbuf_new();
7332
7333 if (!cur1 || !cur1->node) return NULL;
7334 if (!_cur2 || !_cur2->node) return NULL;
7335 if (cur1->obj != _cur2->obj) return NULL;
7336 o = (Evas_Object_Textblock *)(cur1->obj->object_data);
7337 if (evas_textblock_cursor_compare(cur1, _cur2) > 0)
7338 {
7339 const Evas_Textblock_Cursor *tc;
7340
7341 tc = cur1;
7342 cur1 = _cur2;
7343 _cur2 = tc;
7344 }
7345 /* Work on a local copy of the cur */
7346 cur2 = alloca(sizeof(Evas_Textblock_Cursor));
7347 cur2->obj = _cur2->obj;
7348 evas_textblock_cursor_copy(_cur2, cur2);
7349
7350 /* Parse the text between the cursors. */
7351 for (tnode = cur1->node ; tnode ;
7352 tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next))
7353 {
7354 Evas_Object_Textblock_Node_Format *fnode;
7355 Eina_Unicode *text_base, *text;
7356 int off = 0;
7357
7358 text_base = text =
7359 eina_unicode_strndup(eina_ustrbuf_string_get(tnode->unicode),
7360 eina_ustrbuf_length_get(tnode->unicode));
7361 if (tnode == cur2->node)
7362 {
7363 fnode = _evas_textblock_node_text_get_first_format_between(tnode,
7364 cur1->pos, cur2->pos);
7365 }
7366 else if (tnode == cur1->node)
7367 {
7368 fnode = _evas_textblock_node_text_get_first_format_between(tnode,
7369 cur1->pos, -1);
7370 }
7371 else
7372 {
7373 fnode = _evas_textblock_node_text_get_first_format_between(tnode,
7374 0, -1);
7375 }
7376 /* Init the offset so the first one will count starting from cur1->pos
7377 * and not the previous format node */
7378 if (tnode == cur1->node)
7379 {
7380 if (fnode)
7381 {
7382 off = _evas_textblock_node_format_pos_get(fnode) -
7383 cur1->pos - fnode->offset;
7384 }
7385 text += cur1->pos;
7386 }
7387 else
7388 {
7389 off = 0;
7390 }
7391 while (fnode && (fnode->text_node == tnode))
7392 {
7393 Eina_Unicode tmp_ch;
7394 off += fnode->offset;
7395 if ((tnode == cur2->node) &&
7396 ((size_t) (text - text_base + off) >= cur2->pos))
7397 {
7398 break;
7399 }
7400 /* No need to skip on the first run */
7401 tmp_ch = text[off];
7402 text[off] = 0; /* Null terminate the part of the string */
7403 _markup_get_text_append(buf, text);
7404 _markup_get_format_append(o, buf, fnode);
7405 text[off] = tmp_ch; /* Restore the char */
7406 text += off;
7407 if (fnode->visible)
7408 {
7409 off = -1;
7410 text++;
7411 }
7412 else
7413 {
7414 off = 0;
7415 }
7416 fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
7417 }
7418 /* If we got to the last node, stop and add the rest outside */
7419 if (cur2->node == tnode)
7420 {
7421 /* Add the rest, skip replacement */
7422 /* Don't go past the second cursor pos */
7423 text_base[cur2->pos] = '\0';
7424 _markup_get_text_append(buf, text);
7425 free(text_base);
7426 break;
7427 }
7428 else
7429 {
7430 /* Add the rest, skip replacement */
7431 _markup_get_text_append(buf, text);
7432 free(text_base);
7433 }
7434 }
7435 /* return the string */
7436 {
7437 char *ret;
7438 ret = eina_strbuf_string_steal(buf);
7439 eina_strbuf_free(buf);
7440 return ret;
7441 }
7442}
7443
7444static char *
7445_evas_textblock_cursor_range_text_plain_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *_cur2)
7446{
7447 Eina_UStrbuf *buf;
7448 Evas_Object_Textblock_Node_Text *n1, *n2;
7449 Evas_Textblock_Cursor *cur2;
7450
7451 buf = eina_ustrbuf_new();
7452
7453 if (!cur1 || !cur1->node) return NULL;
7454 if (!_cur2 || !_cur2->node) return NULL;
7455 if (cur1->obj != _cur2->obj) return NULL;
7456 if (evas_textblock_cursor_compare(cur1, _cur2) > 0)
7457 {
7458 const Evas_Textblock_Cursor *tc;
7459
7460 tc = cur1;
7461 cur1 = _cur2;
7462 _cur2 = tc;
7463 }
7464 n1 = cur1->node;
7465 n2 = _cur2->node;
7466 /* Work on a local copy of the cur */
7467 cur2 = alloca(sizeof(Evas_Textblock_Cursor));
7468 cur2->obj = _cur2->obj;
7469 evas_textblock_cursor_copy(_cur2, cur2);
7470
7471
7472 if (n1 == n2)
7473 {
7474 const Eina_Unicode *tmp;
7475 tmp = eina_ustrbuf_string_get(n1->unicode);
7476 eina_ustrbuf_append_length(buf, tmp + cur1->pos, cur2->pos - cur1->pos);
7477 }
7478 else
7479 {
7480 const Eina_Unicode *tmp;
7481 tmp = eina_ustrbuf_string_get(n1->unicode);
7482 eina_ustrbuf_append(buf, tmp + cur1->pos);
7483 n1 = _NODE_TEXT(EINA_INLIST_GET(n1)->next);
7484 while (n1 != n2)
7485 {
7486 tmp = eina_ustrbuf_string_get(n1->unicode);
7487 eina_ustrbuf_append_length(buf, tmp,
7488 eina_ustrbuf_length_get(n1->unicode));
7489 n1 = _NODE_TEXT(EINA_INLIST_GET(n1)->next);
7490 }
7491 tmp = eina_ustrbuf_string_get(n2->unicode);
7492 eina_ustrbuf_append_length(buf, tmp, cur2->pos);
7493 }
7494
7495 /* Free and return */
7496 {
7497 char *ret;
7498 ret = eina_unicode_unicode_to_utf8(eina_ustrbuf_string_get(buf), NULL);
7499 eina_ustrbuf_free(buf);
7500 return ret;
7501 }
7502}
7503
7504EAPI Eina_List *
7505evas_textblock_cursor_range_formats_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2)
7506{
7507 Evas_Object *obj = cur1->obj;
7508 Eina_List *ret = NULL;
7509 Evas_Object_Textblock_Node_Text *n1, *n2;
7510 Evas_Object_Textblock_Node_Format *first, *last;
7511 TB_HEAD_RETURN(NULL);
7512 if (!cur1 || !cur1->node) return NULL;
7513 if (!cur2 || !cur2->node) return NULL;
7514 if (cur1->obj != cur2->obj) return NULL;
7515 if (evas_textblock_cursor_compare(cur1, cur2) > 0)
7516 {
7517 const Evas_Textblock_Cursor *tc;
7518
7519 tc = cur1;
7520 cur1 = cur2;
7521 cur2 = tc;
7522 }
7523 n1 = cur1->node;
7524 n2 = cur2->node;
7525
7526 /* FIXME: Change first and last getting to format_before_or_at_pos_get */
7527
7528 last = n2->format_node;
7529
7530 /* If n2->format_node is NULL, we don't have formats in the tb/range. */
7531 if (!last)
7532 return NULL;
7533 /* If the found format is on our text node, we should go to the last
7534 * one, otherwise, the one we found is good enough. */
7535 if (last->text_node == n2)
7536 {
7537 Evas_Object_Textblock_Node_Format *fnode = last;
7538 while (fnode && (fnode->text_node == n2))
7539 {
7540 last = fnode;
7541 fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
7542 }
7543 }
7544
7545 /* If the first format node is within the range (i.e points to n1) or if
7546 * we have other formats in the range, go through them */
7547 first = n1->format_node;
7548 if ((first->text_node == n1) || (first != last))
7549 {
7550 Evas_Object_Textblock_Node_Format *fnode = first;
7551 /* Go to the first one in the range */
7552 if (first->text_node != n1)
7553 {
7554 first = _NODE_FORMAT(EINA_INLIST_GET(first)->next);
7555 }
7556
7557 while (fnode)
7558 {
7559 ret = eina_list_append(ret, fnode);
7560 if (fnode == last)
7561 break;
7562 fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next);
7563 }
7564 }
7565
7566 return ret;
7567
7568}
7569
7570EAPI char *
7571evas_textblock_cursor_range_text_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2, Evas_Textblock_Text_Type format)
7572{
7573 if (format == EVAS_TEXTBLOCK_TEXT_MARKUP)
7574 return _evas_textblock_cursor_range_text_markup_get(cur1, cur2);
7575 else if (format == EVAS_TEXTBLOCK_TEXT_PLAIN)
7576 return _evas_textblock_cursor_range_text_plain_get(cur1, cur2);
7577 else
7578 return NULL; /* Not yet supported */
7579}
7580
7581EAPI const char *
7582evas_textblock_cursor_paragraph_text_get(const Evas_Textblock_Cursor *cur)
7583{
7584 Evas_Textblock_Cursor cur1, cur2;
7585 if (!cur) return NULL;
7586 if (!cur->node) return NULL;
7587 if (cur->node->utf8)
7588 {
7589 free(cur->node->utf8);
7590 }
7591 cur1.obj = cur2.obj = cur->obj;
7592 cur1.node = cur2.node = cur->node;
7593 evas_textblock_cursor_paragraph_char_first(&cur1);
7594 evas_textblock_cursor_paragraph_char_last(&cur2);
7595
7596 cur->node->utf8 = evas_textblock_cursor_range_text_get(&cur1, &cur2,
7597 EVAS_TEXTBLOCK_TEXT_MARKUP);
7598 return cur->node->utf8;
7599}
7600
7601EAPI int
7602evas_textblock_cursor_paragraph_text_length_get(const Evas_Textblock_Cursor *cur)
7603{
7604 int len;
7605 if (!cur) return -1;
7606 if (!cur->node) return -1;
7607 len = eina_ustrbuf_length_get(cur->node->unicode);
7608
7609 if (EINA_INLIST_GET(cur->node)->next)
7610 return len - 1; /* Remove the paragraph separator */
7611 else
7612 return len;
7613}
7614
7615EAPI const Evas_Object_Textblock_Node_Format *
7616evas_textblock_cursor_format_get(const Evas_Textblock_Cursor *cur)
7617{
7618 if (!cur) return NULL;
7619 if (!cur->node) return NULL;
7620 return _evas_textblock_cursor_node_format_at_pos_get(cur);
7621}
7622
7623EAPI const char *
7624evas_textblock_node_format_text_get(const Evas_Object_Textblock_Node_Format *fmt)
7625{
7626 if (!fmt) return NULL;
7627 return fmt->orig_format;
7628}
7629
7630EAPI void
7631evas_textblock_cursor_at_format_set(Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Format *fmt)
7632{
7633 if (!fmt || !cur) return;
7634 cur->node = fmt->text_node;
7635 cur->pos = _evas_textblock_node_format_pos_get(fmt);
7636}
7637
7638EAPI Eina_Bool
7639evas_textblock_cursor_format_is_visible_get(const Evas_Textblock_Cursor *cur)
7640{
7641 const Eina_Unicode *text;
7642
7643 if (!cur) return EINA_FALSE;
7644 if (!cur->node) return EINA_FALSE;
7645 text = eina_ustrbuf_string_get(cur->node->unicode);
7646 return EVAS_TEXTBLOCK_IS_VISIBLE_FORMAT_CHAR(text[cur->pos]);
7647}
7648
7649EAPI int
7650evas_textblock_cursor_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch, Evas_BiDi_Direction *dir, Evas_Textblock_Cursor_Type ctype)
7651{
7652 int ret = -1;
7653 const Evas_Textblock_Cursor *dir_cur;
7654 Evas_Textblock_Cursor cur2;
7655 Evas_Object_Textblock *o;
7656 o = (Evas_Object_Textblock *)(cur->obj->object_data);
7657 if (!o->formatted.valid) _relayout(cur->obj);
7658
7659 dir_cur = cur;
7660 if (ctype == EVAS_TEXTBLOCK_CURSOR_UNDER)
7661 {
7662 ret = evas_textblock_cursor_pen_geometry_get(cur, cx, cy, cw, ch);
7663 }
7664 else if (ctype == EVAS_TEXTBLOCK_CURSOR_BEFORE)
7665 {
7666 /* In the case of a "before cursor", we should get the coordinates
7667 * of just after the previous char (which in bidi text may not be
7668 * just before the current char). */
7669 Evas_Coord x, y, h, w;
7670 Evas_Object_Textblock_Node_Format *fmt;
7671
7672 /* If it's at the end of the line, we want to get the position, not
7673 * the position of the previous */
7674 if ((cur->pos > 0) && !_evas_textblock_cursor_is_at_the_end(cur))
7675 {
7676 Eina_Bool before_char = EINA_FALSE;
7677 cur2.obj = cur->obj;
7678 evas_textblock_cursor_copy(cur, &cur2);
7679 evas_textblock_cursor_char_prev(&cur2);
7680
7681 fmt = _evas_textblock_cursor_node_format_at_pos_get(&cur2);
7682
7683 if (!fmt || !_IS_LINE_SEPARATOR(fmt->format))
7684 {
7685 dir_cur = &cur2;
7686 before_char = EINA_FALSE;
7687 }
7688 else
7689 {
7690 before_char = EINA_TRUE;
7691 }
7692 ret = evas_textblock_cursor_pen_geometry_get(
7693 dir_cur, &x, &y, &w, &h);
7694#ifdef BIDI_SUPPORT
7695 /* Adjust if the char is an rtl char */
7696 if (ret >= 0)
7697 {
7698 Eina_Bool is_rtl = EINA_FALSE;
7699 if (dir_cur->node->par->is_bidi)
7700 {
7701 Evas_Object_Textblock_Line *ln;
7702 Evas_Object_Textblock_Item *it;
7703 _find_layout_item_match(dir_cur, &ln, &it);
7704 if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) &&
7705 (_ITEM_TEXT(it)->text_props.bidi.dir ==
7706 EVAS_BIDI_DIRECTION_RTL))
7707 is_rtl = EINA_TRUE;
7708 else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) &&
7709 (_ITEM_FORMAT(it)->bidi_dir ==
7710 EVAS_BIDI_DIRECTION_RTL))
7711 is_rtl = EINA_TRUE;
7712 }
7713
7714 if ((!before_char && is_rtl) ||
7715 (before_char && !is_rtl))
7716 {
7717 /* Just don't advance the width */
7718 w = 0;
7719 }
7720 }
7721#endif
7722 }
7723 else if (cur->pos == 0)
7724 {
7725 ret = evas_textblock_cursor_pen_geometry_get(
7726 dir_cur, &x, &y, &w, &h);
7727#ifdef BIDI_SUPPORT
7728 Eina_Bool is_rtl = EINA_FALSE;
7729 if (dir_cur->node && dir_cur->node->par->is_bidi)
7730 {
7731 Evas_Object_Textblock_Line *ln;
7732 Evas_Object_Textblock_Item *it;
7733 _find_layout_item_match(dir_cur, &ln, &it);
7734 if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) &&
7735 (_ITEM_TEXT(it)->text_props.bidi.dir ==
7736 EVAS_BIDI_DIRECTION_RTL))
7737 is_rtl = EINA_TRUE;
7738 else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) &&
7739 (_ITEM_FORMAT(it)->bidi_dir ==
7740 EVAS_BIDI_DIRECTION_RTL))
7741 is_rtl = EINA_TRUE;
7742 }
7743
7744 /* Adjust if the char is an rtl char */
7745 if ((ret >= 0) && (!is_rtl))
7746 {
7747 /* Just don't advance the width */
7748 w = 0;
7749 }
7750#endif
7751 }
7752 else
7753 {
7754 ret = evas_textblock_cursor_pen_geometry_get(
7755 dir_cur, &x, &y, &w, &h);
7756 }
7757 if (ret >= 0)
7758 {
7759 if (cx) *cx = x + w;
7760 if (cy) *cy = y;
7761 if (cw) *cw = 0;
7762 if (ch) *ch = h;
7763 }
7764 }
7765
7766 if (dir && dir_cur && dir_cur->node)
7767 {
7768#ifdef BIDI_SUPPORT
7769 Eina_Bool is_rtl = EINA_FALSE;
7770 if (dir_cur->node->par->is_bidi)
7771 {
7772 Evas_Object_Textblock_Line *ln;
7773 Evas_Object_Textblock_Item *it;
7774 _find_layout_item_match(dir_cur, &ln, &it);
7775 if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) &&
7776 (_ITEM_TEXT(it)->text_props.bidi.dir ==
7777 EVAS_BIDI_DIRECTION_RTL))
7778 is_rtl = EINA_TRUE;
7779 else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) &&
7780 (_ITEM_FORMAT(it)->bidi_dir ==
7781 EVAS_BIDI_DIRECTION_RTL))
7782 is_rtl = EINA_TRUE;
7783 }
7784
7785 if (_evas_textblock_cursor_is_at_the_end(dir_cur) && (dir_cur->pos > 0))
7786 {
7787 *dir = (is_rtl) ?
7788 EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR;
7789 }
7790 else if (dir_cur->pos > 0)
7791 {
7792 *dir = (is_rtl) ?
7793 EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR;
7794 }
7795 else
7796#endif
7797 {
7798 *dir = EVAS_BIDI_DIRECTION_LTR;
7799 }
7800 }
7801 return ret;
7802}
7803
7804/**
7805 * @internal
7806 * Returns the geometry/pen position (depending on query_func) of the char
7807 * at pos.
7808 *
7809 * @param cur the position of the char.
7810 * @param query_func the query function to use.
7811 * @param cx the x of the char (or pen_x in the case of pen position).
7812 * @param cy the y of the char.
7813 * @param cw the w of the char (or advance in the case pen position).
7814 * @param ch the h of the char.
7815 * @return line number of the char on success, -1 on error.
7816 */
7817static int
7818_evas_textblock_cursor_char_pen_geometry_common_get(int (*query_func) (void *data, Evas_Font_Set *font, const Evas_Text_Props *intl_props, int pos, int *cx, int *cy, int *cw, int *ch), const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch)
7819{
7820 Evas_Object_Textblock *o;
7821 Evas_Object_Textblock_Line *ln = NULL;
7822 Evas_Object_Textblock_Item *it = NULL;
7823 Evas_Object_Textblock_Text_Item *ti = NULL;
7824 Evas_Object_Textblock_Format_Item *fi = NULL;
7825 int x = 0, y = 0, w = 0, h = 0;
7826 int pos;
7827 Eina_Bool previous_format;
7828
7829 if (!cur) return -1;
7830 o = (Evas_Object_Textblock *)(cur->obj->object_data);
7831 if (!o->formatted.valid) _relayout(cur->obj);
7832
7833 if (!cur->node)
7834 {
7835 if (!o->text_nodes)
7836 {
7837 if (!o->paragraphs) return -1;
7838 ln = o->paragraphs->lines;
7839 if (!ln) return -1;
7840 if (cx) *cx = ln->x;
7841 if (cy) *cy = ln->par->y + ln->y;
7842 if (cw) *cw = ln->w;
7843 if (ch) *ch = ln->h;
7844 return ln->par->line_no + ln->line_no;
7845 }
7846 else
7847 return -1;
7848 }
7849
7850 previous_format = _find_layout_item_match(cur, &ln, &it);
7851 if (!it)
7852 {
7853 return -1;
7854 }
7855 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
7856 {
7857 ti = _ITEM_TEXT(it);
7858 }
7859 else
7860 {
7861 fi = _ITEM_FORMAT(it);
7862 }
7863
7864 if (ln && ti)
7865 {
7866 pos = cur->pos - ti->parent.text_pos;
7867
7868 if (pos < 0) pos = 0;
7869 if (ti->parent.format->font.font)
7870 {
7871 query_func(cur->ENDT,
7872 ti->parent.format->font.font,
7873 &ti->text_props,
7874 pos,
7875 &x, &y, &w, &h);
7876 }
7877
7878 x += ln->x + _ITEM(ti)->x;
7879
7880 if (x < ln->x)
7881 {
7882 x = ln->x;
7883 }
7884 y = ln->par->y + ln->y;
7885 h = ln->h;
7886 }
7887 else if (ln && fi)
7888 {
7889 if (previous_format)
7890 {
7891 if (_IS_LINE_SEPARATOR(fi->item))
7892 {
7893 x = 0;
7894 y = ln->par->y + ln->y + ln->h;
7895 }
7896 else
7897 {
7898#ifdef BIDI_SUPPORT
7899 if (ln->par->direction == EVAS_BIDI_DIRECTION_RTL)
7900 {
7901 x = ln->x;
7902 }
7903 else
7904#endif
7905 {
7906 x = ln->x + ln->w;
7907 }
7908 y = ln->par->y + ln->y;
7909 }
7910 w = 0;
7911 h = ln->h;
7912 }
7913 else
7914 {
7915 x = ln->x + _ITEM(fi)->x;
7916 y = ln->par->y + ln->y;
7917 w = _ITEM(fi)->w;
7918 h = ln->h;
7919 }
7920 }
7921 else
7922 {
7923 return -1;
7924 }
7925 if (cx) *cx = x;
7926 if (cy) *cy = y;
7927 if (cw) *cw = w;
7928 if (ch) *ch = h;
7929 return ln->par->line_no + ln->line_no;
7930}
7931
7932EAPI int
7933evas_textblock_cursor_char_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch)
7934{
7935 return _evas_textblock_cursor_char_pen_geometry_common_get(
7936 cur->ENFN->font_char_coords_get, cur, cx, cy, cw, ch);
7937}
7938
7939EAPI int
7940evas_textblock_cursor_pen_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch)
7941{
7942 return _evas_textblock_cursor_char_pen_geometry_common_get(
7943 cur->ENFN->font_pen_coords_get, cur, cx, cy, cw, ch);
7944}
7945
7946EAPI int
7947evas_textblock_cursor_line_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch)
7948{
7949 Evas_Object_Textblock *o;
7950 Evas_Object_Textblock_Line *ln = NULL;
7951 Evas_Object_Textblock_Item *it = NULL;
7952 int x, y, w, h;
7953
7954 if (!cur) return -1;
7955 o = (Evas_Object_Textblock *)(cur->obj->object_data);
7956 if (!o->formatted.valid) _relayout(cur->obj);
7957 if (!cur->node)
7958 {
7959 ln = o->paragraphs->lines;
7960 }
7961 else
7962 {
7963 _find_layout_item_match(cur, &ln, &it);
7964 }
7965 if (!ln) return -1;
7966 x = ln->x;
7967 y = ln->par->y + ln->y;
7968 w = ln->w;
7969 h = ln->h;
7970 if (cx) *cx = x;
7971 if (cy) *cy = y;
7972 if (cw) *cw = w;
7973 if (ch) *ch = h;
7974 return ln->par->line_no + ln->line_no;
7975}
7976
7977EAPI Eina_Bool
7978evas_textblock_cursor_visible_range_get(Evas_Textblock_Cursor *start, Evas_Textblock_Cursor *end)
7979{
7980 Evas *e;
7981 Evas_Coord cy, ch;
7982 Evas_Object *obj = start->obj;
7983 TB_HEAD_RETURN(EINA_FALSE);
7984 e = evas_object_evas_get(obj);
7985 cy = 0 - obj->cur.geometry.y;
7986 ch = e->viewport.h;
7987 evas_textblock_cursor_line_coord_set(start, cy);
7988 evas_textblock_cursor_line_coord_set(end, cy + ch);
7989 evas_textblock_cursor_line_char_last(end);
7990
7991 return EINA_TRUE;
7992}
7993
7994EAPI Eina_Bool
7995evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y)
7996{
7997 Evas_Object_Textblock *o;
7998 Evas_Object_Textblock_Paragraph *found_par;
7999 Evas_Object_Textblock_Line *ln;
8000 Evas_Object_Textblock_Item *it = NULL;
8001
8002 if (!cur) return EINA_FALSE;
8003 o = (Evas_Object_Textblock *)(cur->obj->object_data);
8004 if (!o->formatted.valid) _relayout(cur->obj);
8005 x += o->style_pad.l;
8006 y += o->style_pad.t;
8007
8008 found_par = _layout_find_paragraph_by_y(o, y);
8009 if (found_par)
8010 {
8011 _layout_paragraph_render(o, found_par);
8012 EINA_INLIST_FOREACH(found_par->lines, ln)
8013 {
8014 if (ln->par->y + ln->y > y) break;
8015 if ((ln->par->y + ln->y <= y) && ((ln->par->y + ln->y + ln->h) > y))
8016 {
8017 /* If before or after the line, go to start/end according
8018 * to paragraph direction. */
8019 if (x < ln->x)
8020 {
8021 cur->pos = ln->items->text_pos;
8022 cur->node = found_par->text_node;
8023 if (found_par->direction == EVAS_BIDI_DIRECTION_RTL)
8024 {
8025 evas_textblock_cursor_line_char_last(cur);
8026 }
8027 else
8028 {
8029 evas_textblock_cursor_line_char_first(cur);
8030 }
8031 return EINA_TRUE;
8032 }
8033 else if (x >= ln->x + ln->w)
8034 {
8035 cur->pos = ln->items->text_pos;
8036 cur->node = found_par->text_node;
8037 if (found_par->direction == EVAS_BIDI_DIRECTION_RTL)
8038 {
8039 evas_textblock_cursor_line_char_first(cur);
8040 }
8041 else
8042 {
8043 evas_textblock_cursor_line_char_last(cur);
8044 }
8045 return EINA_TRUE;
8046 }
8047
8048 EINA_INLIST_FOREACH(ln->items, it)
8049 {
8050 if (((it->x + ln->x) <= x) && (((it->x + ln->x) + it->adv) > x))
8051 {
8052 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
8053 {
8054 int pos;
8055 int cx, cy, cw, ch;
8056 Evas_Object_Textblock_Text_Item *ti;
8057 ti = _ITEM_TEXT(it);
8058
8059 pos = -1;
8060 if (ti->parent.format->font.font)
8061 pos = cur->ENFN->font_char_at_coords_get(
8062 cur->ENDT,
8063 ti->parent.format->font.font,
8064 &ti->text_props,
8065 x - it->x - ln->x, 0,
8066 &cx, &cy, &cw, &ch);
8067 if (pos < 0)
8068 return EINA_FALSE;
8069 cur->pos = pos + it->text_pos;
8070 cur->node = it->text_node;
8071 return EINA_TRUE;
8072 }
8073 else
8074 {
8075 Evas_Object_Textblock_Format_Item *fi;
8076 fi = _ITEM_FORMAT(it);
8077 cur->pos = fi->parent.text_pos;
8078 cur->node = found_par->text_node;
8079 return EINA_TRUE;
8080 }
8081 }
8082 }
8083 }
8084 }
8085 }
8086 else if (o->paragraphs && (y >= o->paragraphs->y + o->formatted.h))
8087 {
8088 /* If we are after the last paragraph, use the last position in the
8089 * text. */
8090 evas_textblock_cursor_paragraph_last(cur);
8091 return EINA_TRUE;
8092 }
8093 else if (o->paragraphs && (y < o->paragraphs->y))
8094 {
8095 evas_textblock_cursor_paragraph_first(cur);
8096 return EINA_TRUE;
8097 }
8098
8099 return EINA_FALSE;
8100}
8101
8102EAPI int
8103evas_textblock_cursor_line_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord y)
8104{
8105 Evas_Object_Textblock *o;
8106 Evas_Object_Textblock_Paragraph *found_par;
8107 Evas_Object_Textblock_Line *ln;
8108
8109 if (!cur) return -1;
8110 o = (Evas_Object_Textblock *)(cur->obj->object_data);
8111 if (!o->formatted.valid) _relayout(cur->obj);
8112 y += o->style_pad.t;
8113
8114 found_par = _layout_find_paragraph_by_y(o, y);
8115
8116 if (found_par)
8117 {
8118 _layout_paragraph_render(o, found_par);
8119 EINA_INLIST_FOREACH(found_par->lines, ln)
8120 {
8121 if (ln->par->y + ln->y > y) break;
8122 if ((ln->par->y + ln->y <= y) && ((ln->par->y + ln->y + ln->h) > y))
8123 {
8124 evas_textblock_cursor_line_set(cur, ln->par->line_no +
8125 ln->line_no);
8126 return ln->par->line_no + ln->line_no;
8127 }
8128 }
8129 }
8130 else if (o->paragraphs && (y >= o->paragraphs->y + o->formatted.h))
8131 {
8132 int line_no = 0;
8133 /* If we are after the last paragraph, use the last position in the
8134 * text. */
8135 evas_textblock_cursor_paragraph_last(cur);
8136 if (cur->node && cur->node->par)
8137 {
8138 line_no = cur->node->par->line_no;
8139 if (cur->node->par->lines)
8140 {
8141 line_no += ((Evas_Object_Textblock_Line *)
8142 EINA_INLIST_GET(cur->node->par->lines)->last)->line_no;
8143 }
8144 }
8145 return line_no;
8146 }
8147 else if (o->paragraphs && (y < o->paragraphs->y))
8148 {
8149 int line_no = 0;
8150 evas_textblock_cursor_paragraph_first(cur);
8151 if (cur->node && cur->node->par)
8152 {
8153 line_no = cur->node->par->line_no;
8154 }
8155 return line_no;
8156 }
8157 return -1;
8158}
8159
8160/**
8161 * @internal
8162 * Updates x and w according to the text direction, position in text and
8163 * if it's a special case switch
8164 *
8165 * @param ti the text item we are working on
8166 * @param x the current x (we get) and the x we return
8167 * @param w the current w (we get) and the w we return
8168 * @param start if this is the first item or not
8169 * @param switch_items toogles item switching (rtl cases)
8170 */
8171static void
8172_evas_textblock_range_calc_x_w(const Evas_Object_Textblock_Item *it,
8173 Evas_Coord *x, Evas_Coord *w, Eina_Bool start, Eina_Bool switch_items)
8174{
8175 if ((start && !switch_items) || (!start && switch_items))
8176 {
8177#ifdef BIDI_SUPPORT
8178 if (((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) &&
8179 _ITEM_TEXT(it)->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL)
8180 ||
8181 ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) &&
8182 _ITEM_FORMAT(it)->bidi_dir == EVAS_BIDI_DIRECTION_RTL))
8183 {
8184 *w = *x + *w;
8185 *x = 0;
8186 }
8187 else
8188#endif
8189 {
8190 *w = it->adv - *x;
8191 }
8192 }
8193 else
8194 {
8195#ifdef BIDI_SUPPORT
8196 if (((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) &&
8197 _ITEM_TEXT(it)->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL)
8198 ||
8199 ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) &&
8200 _ITEM_FORMAT(it)->bidi_dir == EVAS_BIDI_DIRECTION_RTL))
8201 {
8202 *x = *x + *w;
8203 *w = it->adv - *x;
8204 }
8205 else
8206#endif
8207 {
8208 *w = *x;
8209 *x = 0;
8210 }
8211 }
8212
8213}
8214
8215/**
8216 * @internal
8217 * Returns the geometry of the range in line ln. Cur1 is the start cursor,
8218 * cur2 is the end cursor, NULL means from the start or to the end accordingly.
8219 * Assumes that ln is valid, and that at least one of cur1 and cur2 is not NULL.
8220 *
8221 * @param ln the line to work on.
8222 * @param cur1 the start cursor
8223 * @param cur2 the end cursor
8224 * @return Returns the geometry of the range
8225 */
8226static Eina_List *
8227_evas_textblock_cursor_range_in_line_geometry_get(
8228 const Evas_Object_Textblock_Line *ln, const Evas_Textblock_Cursor *cur1,
8229 const Evas_Textblock_Cursor *cur2)
8230{
8231 Evas_Object_Textblock_Item *it;
8232 Evas_Object_Textblock_Item *it1, *it2;
8233 Eina_List *rects = NULL;
8234 Evas_Textblock_Rectangle *tr;
8235 size_t start, end;
8236 Eina_Bool switch_items;
8237 const Evas_Textblock_Cursor *cur;
8238
8239 cur = (cur1) ? cur1 : cur2;
8240
8241 if (!cur) return NULL;
8242
8243 /* Find the first and last items */
8244 it1 = it2 = NULL;
8245 start = end = 0;
8246 EINA_INLIST_FOREACH(ln->items, it)
8247 {
8248 size_t item_len;
8249 item_len = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ?
8250 _ITEM_TEXT(it)->text_props.text_len
8251 : 1;
8252 if ((!cur1 || (cur1->pos < it->text_pos + item_len)) &&
8253 (!cur2 || (cur2->pos >= it->text_pos)))
8254 {
8255 if (!it1)
8256 {
8257 it1 = it;
8258 start = item_len; /* start stores the first item_len */
8259 }
8260 it2 = it;
8261 end = item_len; /* end stores the last item_len */
8262 }
8263 }
8264
8265 /* If we couldn't find even one item, return */
8266 if (!it1) return NULL;
8267
8268 /* If the first item is logically before or equal the second item
8269 * we have to set start and end differently than in the other case */
8270 if (it1->text_pos <= it2->text_pos)
8271 {
8272 start = (cur1) ? (cur1->pos - it1->text_pos) : 0;
8273 end = (cur2) ? (cur2->pos - it2->text_pos) : end;
8274 switch_items = EINA_FALSE;
8275 }
8276 else
8277 {
8278 start = (cur2) ? (cur2->pos - it1->text_pos) : start;
8279 end = (cur1) ? (cur1->pos - it2->text_pos) : 0;
8280 switch_items = EINA_TRUE;
8281 }
8282
8283 /* IMPORTANT: Don't use cur1/cur2 past this point (because they probably
8284 * don't make sense anymore. That's why there are start and end),
8285 * unless you know what you are doing */
8286
8287 /* Special case when they share the same item and it's a text item */
8288 if ((it1 == it2) && (it1->type == EVAS_TEXTBLOCK_ITEM_TEXT))
8289 {
8290 Evas_Coord x1, w1, x2, w2;
8291 Evas_Coord x, w, y, h;
8292 Evas_Object_Textblock_Text_Item *ti;
8293 int ret = 0;
8294
8295 ti = _ITEM_TEXT(it1);
8296 if (ti->parent.format->font.font)
8297 {
8298 ret = cur->ENFN->font_pen_coords_get(cur->ENDT,
8299 ti->parent.format->font.font,
8300 &ti->text_props,
8301 start,
8302 &x1, &y, &w1, &h);
8303 }
8304 if (!ret)
8305 {
8306 return NULL;
8307 }
8308 ret = cur->ENFN->font_pen_coords_get(cur->ENDT,
8309 ti->parent.format->font.font,
8310 &ti->text_props,
8311 end,
8312 &x2, &y, &w2, &h);
8313 if (!ret)
8314 {
8315 return NULL;
8316 }
8317
8318 /* Make x2 the one on the right */
8319 if (x2 < x1)
8320 {
8321 Evas_Coord tmp;
8322 tmp = x1;
8323 x1 = x2;
8324 x2 = tmp;
8325
8326 tmp = w1;
8327 w1 = w2;
8328 w2 = tmp;
8329 }
8330
8331#ifdef BIDI_SUPPORT
8332 if (ti->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL)
8333 {
8334 x = x1 + w1;
8335 w = x2 + w2 - x;
8336 }
8337 else
8338#endif
8339 {
8340 x = x1;
8341 w = x2 - x1;
8342 }
8343 if (w > 0)
8344 {
8345 tr = calloc(1, sizeof(Evas_Textblock_Rectangle));
8346 rects = eina_list_append(rects, tr);
8347 tr->x = ln->x + it1->x + x;
8348 tr->y = ln->par->y + ln->y;
8349 tr->h = ln->h;
8350 tr->w = w;
8351 }
8352 }
8353 else if (it1 != it2)
8354 {
8355 /* Get the middle items */
8356 Evas_Coord min_x, max_x;
8357 Evas_Coord x, w;
8358 it = _ITEM(EINA_INLIST_GET(it1)->next);
8359 min_x = max_x = it->x;
8360
8361 if (it1->type == EVAS_TEXTBLOCK_ITEM_TEXT)
8362 {
8363 Evas_Coord y, h;
8364 Evas_Object_Textblock_Text_Item *ti;
8365 int ret;
8366 ti = _ITEM_TEXT(it1);
8367
8368 ret = cur->ENFN->font_pen_coords_get(cur->ENDT,
8369 ti->parent.format->font.font,
8370 &ti->text_props,
8371 start,
8372 &x, &y, &w, &h);
8373 if (!ret)
8374 {
8375 /* BUG! Skip the first item */
8376 x = w = 0;
8377 }
8378 else
8379 {
8380 _evas_textblock_range_calc_x_w(it1, &x, &w, EINA_TRUE,
8381 switch_items);
8382 }
8383 }
8384 else
8385 {
8386 x = 0;
8387 w = it1->w;
8388 _evas_textblock_range_calc_x_w(it1, &x, &w, EINA_TRUE,
8389 switch_items);
8390 }
8391 if (w > 0)
8392 {
8393 tr = calloc(1, sizeof(Evas_Textblock_Rectangle));
8394 rects = eina_list_append(rects, tr);
8395 tr->x = ln->x + it1->x + x;
8396 tr->y = ln->par->y + ln->y;
8397 tr->h = ln->h;
8398 tr->w = w;
8399 }
8400
8401 while (it && (it != it2))
8402 {
8403 max_x = it->x + it->adv;
8404 it = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(it)->next;
8405 }
8406 if (min_x != max_x)
8407 {
8408 tr = calloc(1, sizeof(Evas_Textblock_Rectangle));
8409 rects = eina_list_append(rects, tr);
8410 tr->x = ln->x + min_x;
8411 tr->y = ln->par->y + ln->y;
8412 tr->h = ln->h;
8413 tr->w = max_x - min_x;
8414 }
8415 if (it2->type == EVAS_TEXTBLOCK_ITEM_TEXT)
8416 {
8417 Evas_Coord y, h;
8418 Evas_Object_Textblock_Text_Item *ti;
8419 int ret;
8420 ti = _ITEM_TEXT(it2);
8421
8422 ret = cur->ENFN->font_pen_coords_get(cur->ENDT,
8423 ti->parent.format->font.font,
8424 &ti->text_props,
8425 end,
8426 &x, &y, &w, &h);
8427 if (!ret)
8428 {
8429 /* BUG! skip the last item */
8430 x = w = 0;
8431 }
8432 else
8433 {
8434 _evas_textblock_range_calc_x_w(it2, &x, &w, EINA_FALSE,
8435 switch_items);
8436 }
8437 }
8438 else
8439 {
8440 x = 0;
8441 w = it2->w;
8442 _evas_textblock_range_calc_x_w(it2, &x, &w, EINA_FALSE,
8443 switch_items);
8444 }
8445 if (w > 0)
8446 {
8447 tr = calloc(1, sizeof(Evas_Textblock_Rectangle));
8448 rects = eina_list_append(rects, tr);
8449 tr->x = ln->x + it2->x + x;
8450 tr->y = ln->par->y + ln->y;
8451 tr->h = ln->h;
8452 tr->w = w;
8453 }
8454 }
8455 return rects;
8456}
8457
8458EAPI Eina_List *
8459evas_textblock_cursor_range_geometry_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2)
8460{
8461 Evas_Object_Textblock *o;
8462 Evas_Object_Textblock_Line *ln1, *ln2;
8463 Evas_Object_Textblock_Item *it1, *it2;
8464 Eina_List *rects = NULL;
8465 Evas_Textblock_Rectangle *tr;
8466
8467 if (!cur1 || !cur1->node) return NULL;
8468 if (!cur2 || !cur2->node) return NULL;
8469 if (cur1->obj != cur2->obj) return NULL;
8470 o = (Evas_Object_Textblock *)(cur1->obj->object_data);
8471 if (!o->formatted.valid) _relayout(cur1->obj);
8472 if (evas_textblock_cursor_compare(cur1, cur2) > 0)
8473 {
8474 const Evas_Textblock_Cursor *tc;
8475
8476 tc = cur1;
8477 cur1 = cur2;
8478 cur2 = tc;
8479 }
8480
8481 ln1 = ln2 = NULL;
8482 it1 = it2 = NULL;
8483 _find_layout_item_match(cur1, &ln1, &it1);
8484 if (!ln1 || !it1) return NULL;
8485 _find_layout_item_match(cur2, &ln2, &it2);
8486 if (!ln2 || !it2) return NULL;
8487
8488 if (ln1 == ln2)
8489 {
8490 rects = _evas_textblock_cursor_range_in_line_geometry_get(ln1,
8491 cur1, cur2);
8492 }
8493 else
8494 {
8495 Evas_Object_Textblock_Line *plni, *lni;
8496 Eina_List *rects2 = NULL;
8497 /* Handle the first line */
8498 rects = _evas_textblock_cursor_range_in_line_geometry_get(ln1,
8499 cur1, NULL);
8500
8501 /* Handle the lines between the first and the last line */
8502 lni = (Evas_Object_Textblock_Line *) EINA_INLIST_GET(ln1)->next;
8503 if (!lni && (ln1->par != ln2->par))
8504 {
8505 lni = ((Evas_Object_Textblock_Paragraph *)
8506 EINA_INLIST_GET(ln1->par)->next)->lines;
8507 }
8508 while (lni && (lni != ln2))
8509 {
8510 tr = calloc(1, sizeof(Evas_Textblock_Rectangle));
8511 rects = eina_list_append(rects, tr);
8512 tr->x = lni->x;
8513 tr->y = lni->par->y + lni->y;
8514 tr->h = lni->h;
8515 tr->w = lni->w;
8516 plni = lni;
8517 lni = (Evas_Object_Textblock_Line *) EINA_INLIST_GET(lni)->next;
8518 if (!lni && (plni->par != ln2->par))
8519 {
8520 lni = ((Evas_Object_Textblock_Paragraph *)
8521 EINA_INLIST_GET(plni->par)->next)->lines;
8522 }
8523 }
8524 rects2 = _evas_textblock_cursor_range_in_line_geometry_get(ln2,
8525 NULL, cur2);
8526 rects = eina_list_merge(rects, rects2);
8527 }
8528 return rects;
8529}
8530
8531EAPI Eina_Bool
8532evas_textblock_cursor_format_item_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch)
8533{
8534 Evas_Object_Textblock *o;
8535 Evas_Object_Textblock_Line *ln = NULL;
8536 Evas_Object_Textblock_Format_Item *fi;
8537 Evas_Object_Textblock_Item *it = NULL;
8538 Evas_Coord x, y, w, h;
8539
8540 if (!cur || !evas_textblock_cursor_format_is_visible_get(cur)) return EINA_FALSE;
8541 o = (Evas_Object_Textblock *)(cur->obj->object_data);
8542 if (!o->formatted.valid) _relayout(cur->obj);
8543 if (!evas_textblock_cursor_format_is_visible_get(cur)) return EINA_FALSE;
8544 _find_layout_item_line_match(cur->obj, cur->node, cur->pos, &ln, &it);
8545 fi = _ITEM_FORMAT(it);
8546 if ((!ln) || (!fi)) return EINA_FALSE;
8547 x = ln->x + fi->parent.x;
8548 y = ln->par->y + ln->y + ln->baseline + fi->y;
8549 w = fi->parent.w;
8550 h = fi->parent.h;
8551 if (cx) *cx = x;
8552 if (cy) *cy = y;
8553 if (cw) *cw = w;
8554 if (ch) *ch = h;
8555 return EINA_TRUE;
8556}
8557
8558EAPI Eina_Bool
8559evas_textblock_cursor_eol_get(const Evas_Textblock_Cursor *cur)
8560{
8561 Eina_Bool ret = EINA_FALSE;
8562 Evas_Textblock_Cursor cur2;
8563 if (!cur) return EINA_FALSE;
8564
8565 cur2.obj = cur->obj;
8566 evas_textblock_cursor_copy(cur, &cur2);
8567 evas_textblock_cursor_line_char_last(&cur2);
8568 if (cur2.pos == cur->pos)
8569 {
8570 ret = EINA_TRUE;
8571 }
8572 return ret;
8573}
8574
8575/* general controls */
8576EAPI Eina_Bool
8577evas_object_textblock_line_number_geometry_get(const Evas_Object *obj, int line, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch)
8578{
8579 Evas_Object_Textblock_Line *ln;
8580
8581 TB_HEAD_RETURN(0);
8582 ln = _find_layout_line_num(obj, line);
8583 if (!ln) return EINA_FALSE;
8584 if (cx) *cx = ln->x;
8585 if (cy) *cy = ln->par->y + ln->y;
8586 if (cw) *cw = ln->w;
8587 if (ch) *ch = ln->h;
8588 return EINA_TRUE;
8589}
8590
8591EAPI void
8592evas_object_textblock_clear(Evas_Object *obj)
8593{
8594 Eina_List *l;
8595 Evas_Textblock_Cursor *cur;
8596
8597 TB_HEAD();
8598 if (o->paragraphs)
8599 {
8600 _paragraphs_free(obj, o->paragraphs);
8601 o->paragraphs = NULL;
8602 }
8603
8604 _nodes_clear(obj);
8605 o->cursor->node = NULL;
8606 o->cursor->pos = 0;
8607 EINA_LIST_FOREACH(o->cursors, l, cur)
8608 {
8609 cur->node = NULL;
8610 cur->pos = 0;
8611
8612 }
8613 _evas_textblock_changed(o, obj);
8614}
8615
8616EAPI void
8617evas_object_textblock_size_formatted_get(const Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
8618{
8619 TB_HEAD();
8620 if (!o->formatted.valid) _relayout(obj);
8621 if (w) *w = o->formatted.w;
8622 if (h) *h = o->formatted.h;
8623}
8624
8625static void
8626_size_native_calc_line_finalize(const Evas_Object *obj, Eina_List *items,
8627 Evas_Coord *ascent, Evas_Coord *descent, Evas_Coord *w)
8628{
8629 Evas_Object_Textblock_Item *it;
8630 Eina_List *i;
8631
8632 it = eina_list_data_get(items);
8633 /* If there are no text items yet, calc ascent/descent
8634 * according to the current format. */
8635 if (it && (*ascent + *descent == 0))
8636 _layout_format_ascent_descent_adjust(obj, ascent, descent, it->format);
8637
8638 *w = 0;
8639 /* Adjust all the item sizes according to the final line size,
8640 * and update the x positions of all the items of the line. */
8641 EINA_LIST_FOREACH(items, i, it)
8642 {
8643 if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT)
8644 {
8645 Evas_Coord fw, fh, fy;
8646
8647 Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it);
8648 if (!fi->formatme) goto loop_advance;
8649 _layout_calculate_format_item_size(obj, fi, ascent,
8650 descent, &fy, &fw, &fh);
8651 }
8652
8653loop_advance:
8654 *w += it->adv;
8655 }
8656}
8657
8658/* FIXME: doc */
8659static void
8660_size_native_calc_paragraph_size(const Evas_Object *obj,
8661 const Evas_Object_Textblock *o,
8662 const Evas_Object_Textblock_Paragraph *par,
8663 Evas_Coord *_w, Evas_Coord *_h)
8664{
8665 Eina_List *i;
8666 Evas_Object_Textblock_Item *it;
8667 Eina_List *line_items = NULL;
8668 Evas_Coord w = 0, y = 0, wmax = 0, h = 0, ascent = 0, descent = 0;
8669
8670 EINA_LIST_FOREACH(par->logical_items, i, it)
8671 {
8672 line_items = eina_list_append(line_items, it);
8673 if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT)
8674 {
8675 Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it);
8676 if (fi->item && (_IS_LINE_SEPARATOR(fi->item) ||
8677 _IS_PARAGRAPH_SEPARATOR(o, fi->item)))
8678 {
8679 _size_native_calc_line_finalize(obj, line_items, &ascent,
8680 &descent, &w);
8681
8682 if (ascent + descent > h)
8683 h = ascent + descent;
8684
8685 y += h;
8686 if (w > wmax)
8687 wmax = w;
8688 h = 0;
8689 ascent = descent = 0;
8690 line_items = eina_list_free(line_items);
8691 }
8692 else
8693 {
8694 Evas_Coord fw, fh, fy;
8695 /* If there are no text items yet, calc ascent/descent
8696 * according to the current format. */
8697 if (it && (ascent + descent == 0))
8698 _layout_format_ascent_descent_adjust(obj, &ascent,
8699 &descent, it->format);
8700
8701 _layout_calculate_format_item_size(obj, fi, &ascent,
8702 &descent, &fy, &fw, &fh);
8703 }
8704 }
8705 else
8706 {
8707 _layout_format_ascent_descent_adjust(obj, &ascent,
8708 &descent, it->format);
8709 }
8710 }
8711
8712 _size_native_calc_line_finalize(obj, line_items, &ascent, &descent, &w);
8713
8714 line_items = eina_list_free(line_items);
8715
8716 /* Do the last addition */
8717 if (ascent + descent > h)
8718 h = ascent + descent;
8719
8720 if (w > wmax)
8721 wmax = w;
8722
8723 *_h = y + h;
8724 *_w = wmax;
8725}
8726
8727EAPI void
8728evas_object_textblock_size_native_get(const Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
8729{
8730 TB_HEAD();
8731 if (!o->native.valid)
8732 {
8733 Evas_Coord wmax = 0, hmax = 0;
8734 Evas_Object_Textblock_Paragraph *par;
8735 /* We just want the layout objects to update, should probably
8736 * split that. */
8737 if (!o->formatted.valid) _relayout(obj);
8738 EINA_INLIST_FOREACH(o->paragraphs, par)
8739 {
8740 Evas_Coord tw, th;
8741 _size_native_calc_paragraph_size(obj, o, par, &tw, &th);
8742 if (tw > wmax)
8743 wmax = tw;
8744 hmax += th;
8745 }
8746
8747 o->native.w = wmax;
8748 o->native.h = hmax;
8749
8750 o->native.valid = 1;
8751 o->content_changed = 0;
8752 o->format_changed = EINA_FALSE;
8753 }
8754 if (w) *w = o->native.w;
8755 if (h) *h = o->native.h;
8756}
8757
8758EAPI void
8759evas_object_textblock_style_insets_get(const Evas_Object *obj, Evas_Coord *l, Evas_Coord *r, Evas_Coord *t, Evas_Coord *b)
8760{
8761 TB_HEAD();
8762 if (!o->formatted.valid) _relayout(obj);
8763 if (l) *l = o->style_pad.l;
8764 if (r) *r = o->style_pad.r;
8765 if (t) *t = o->style_pad.t;
8766 if (b) *b = o->style_pad.b;
8767}
8768
8769/** @internal
8770 * FIXME: DELETE ME! DELETE ME!
8771 * This is an ugly workaround to get around the fact that
8772 * evas_object_textblock_coords_recalc isn't really called when it's supposed
8773 * to. When that bug is fixed please remove this. */
8774static void
8775_workaround_object_coords_recalc(void *data __UNUSED__, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
8776{
8777 evas_object_textblock_coords_recalc(obj);
8778}
8779
8780/* all nice and private */
8781static void
8782evas_object_textblock_init(Evas_Object *obj)
8783{
8784 Evas_Object_Textblock *o;
8785#ifdef HAVE_LINEBREAK
8786 static Eina_Bool linebreak_init = EINA_FALSE;
8787 if (!linebreak_init)
8788 {
8789 linebreak_init = EINA_TRUE;
8790 init_linebreak();
8791 }
8792#endif
8793
8794 /* alloc image ob, setup methods and default values */
8795 obj->object_data = evas_object_textblock_new();
8796 /* set up default settings for this kind of object */
8797 obj->cur.color.r = 255;
8798 obj->cur.color.g = 255;
8799 obj->cur.color.b = 255;
8800 obj->cur.color.a = 255;
8801 obj->cur.geometry.x = 0.0;
8802 obj->cur.geometry.y = 0.0;
8803 obj->cur.geometry.w = 0.0;
8804 obj->cur.geometry.h = 0.0;
8805 obj->cur.layer = 0;
8806 /* set up object-specific settings */
8807 obj->prev = obj->cur;
8808 /* set up methods (compulsory) */
8809 obj->func = &object_func;
8810 obj->type = o_type;
8811
8812 o = (Evas_Object_Textblock *)(obj->object_data);
8813 o->cursor->obj = obj;
8814 o->legacy_newline = EINA_TRUE;
8815 evas_object_event_callback_priority_add(obj, EVAS_CALLBACK_RESIZE, -1000,
8816 _workaround_object_coords_recalc, NULL);
8817}
8818
8819static void *
8820evas_object_textblock_new(void)
8821{
8822 Evas_Object_Textblock *o;
8823
8824 /* alloc obj private data */
8825 EVAS_MEMPOOL_INIT(_mp_obj, "evas_object_textblock", Evas_Object_Textblock, 64, NULL);
8826 o = EVAS_MEMPOOL_ALLOC(_mp_obj, Evas_Object_Textblock);
8827 if (!o) return NULL;
8828 EVAS_MEMPOOL_PREP(_mp_obj, o, Evas_Object_Textblock);
8829 o->magic = MAGIC_OBJ_TEXTBLOCK;
8830 o->cursor = calloc(1, sizeof(Evas_Textblock_Cursor));
8831 _format_command_init();
8832 return o;
8833}
8834
8835static void
8836evas_object_textblock_free(Evas_Object *obj)
8837{
8838 Evas_Object_Textblock *o;
8839
8840 evas_object_textblock_clear(obj);
8841 evas_object_textblock_style_set(obj, NULL);
8842 o = (Evas_Object_Textblock *)(obj->object_data);
8843 free(o->cursor);
8844 while (o->cursors)
8845 {
8846 Evas_Textblock_Cursor *cur;
8847
8848 cur = (Evas_Textblock_Cursor *)o->cursors->data;
8849 o->cursors = eina_list_remove_list(o->cursors, o->cursors);
8850 free(cur);
8851 }
8852 if (o->repch) eina_stringshare_del(o->repch);
8853 if (o->ellip_ti) _item_free(obj, NULL, _ITEM(o->ellip_ti));
8854 o->magic = 0;
8855 EVAS_MEMPOOL_FREE(_mp_obj, o);
8856 _format_command_shutdown();
8857}
8858
8859
8860static void
8861evas_object_textblock_render(Evas_Object *obj, void *output, void *context, void *surface, int x, int y)
8862{
8863 Evas_Object_Textblock_Paragraph *par, *start = NULL;
8864 Evas_Object_Textblock_Line *ln;
8865 Evas_Object_Textblock *o;
8866 int i, j;
8867 int cx, cy, cw, ch, clip;
8868 const char vals[5][5] =
8869 {
8870 {0, 1, 2, 1, 0},
8871 {1, 3, 4, 3, 1},
8872 {2, 4, 5, 4, 2},
8873 {1, 3, 4, 3, 1},
8874 {0, 1, 2, 1, 0}
8875 };
8876
8877 /* render object to surface with context, and offxet by x,y */
8878 o = (Evas_Object_Textblock *)(obj->object_data);
8879 obj->layer->evas->engine.func->context_multiplier_unset(output,
8880 context);
8881 /* FIXME: This clipping is just until we fix inset handling correctly. */
8882 ENFN->context_clip_clip(output, context,
8883 obj->cur.geometry.x + x,
8884 obj->cur.geometry.y + y,
8885 obj->cur.geometry.w,
8886 obj->cur.geometry.h);
8887 clip = ENFN->context_clip_get(output, context, &cx, &cy, &cw, &ch);
8888 /* If there are no paragraphs and thus there are no lines,
8889 * there's nothing left to do. */
8890 if (!o->paragraphs) return;
8891
8892#define ITEM_WALK() \
8893 EINA_INLIST_FOREACH(start, par) \
8894 { \
8895 if (!par->visible) continue; \
8896 if (clip) \
8897 { \
8898 if ((obj->cur.geometry.y + y + par->y + par->h) < (cy - 20)) \
8899 continue; \
8900 if ((obj->cur.geometry.y + y + par->y) > (cy + ch + 20)) \
8901 break; \
8902 } \
8903 _layout_paragraph_render(o, par); \
8904 EINA_INLIST_FOREACH(par->lines, ln) \
8905 { \
8906 Evas_Object_Textblock_Item *itr; \
8907 \
8908 if (clip) \
8909 { \
8910 if ((obj->cur.geometry.y + y + par->y + ln->y + ln->h) < (cy - 20)) \
8911 continue; \
8912 if ((obj->cur.geometry.y + y + par->y + ln->y) > (cy + ch + 20)) \
8913 break; \
8914 } \
8915 EINA_INLIST_FOREACH(ln->items, itr) \
8916 { \
8917 Evas_Coord yoff; \
8918 yoff = ln->baseline; \
8919 if (itr->format->valign != -1.0) \
8920 { \
8921 yoff += itr->format->valign * (ln->h - itr->h); \
8922 } \
8923 if (clip) \
8924 { \
8925 if ((obj->cur.geometry.x + x + ln->x + itr->x + itr->w) < (cx - 20)) \
8926 continue; \
8927 if ((obj->cur.geometry.x + x + ln->x + itr->x) > (cx + cw + 20)) \
8928 break; \
8929 } \
8930 if ((ln->x + itr->x + itr->w) <= 0) continue; \
8931 if (ln->x + itr->x > obj->cur.geometry.w) break; \
8932 do
8933
8934#define ITEM_WALK_END() \
8935 while (0); \
8936 } \
8937 } \
8938 } \
8939 do {} while(0)
8940#define COLOR_SET(col) \
8941 ENFN->context_color_set(output, context, \
8942 (obj->cur.cache.clip.r * ti->parent.format->color.col.r) / 255, \
8943 (obj->cur.cache.clip.g * ti->parent.format->color.col.g) / 255, \
8944 (obj->cur.cache.clip.b * ti->parent.format->color.col.b) / 255, \
8945 (obj->cur.cache.clip.a * ti->parent.format->color.col.a) / 255);
8946#define COLOR_SET_AMUL(col, amul) \
8947 ENFN->context_color_set(output, context, \
8948 (obj->cur.cache.clip.r * ti->parent.format->color.col.r * (amul)) / 65025, \
8949 (obj->cur.cache.clip.g * ti->parent.format->color.col.g * (amul)) / 65025, \
8950 (obj->cur.cache.clip.b * ti->parent.format->color.col.b * (amul)) / 65025, \
8951 (obj->cur.cache.clip.a * ti->parent.format->color.col.a * (amul)) / 65025);
8952#define DRAW_TEXT(ox, oy) \
8953 if (ti->parent.format->font.font) ENFN->font_draw(output, context, surface, ti->parent.format->font.font, \
8954 obj->cur.geometry.x + ln->x + ti->parent.x + x + (ox), \
8955 obj->cur.geometry.y + ln->par->y + ln->y + yoff + y + (oy), \
8956 ti->parent.w, ti->parent.h, ti->parent.w, ti->parent.h, \
8957 &ti->text_props);
8958
8959 /* backing */
8960#define DRAW_RECT(ox, oy, ow, oh, or, og, ob, oa) \
8961 do \
8962 { \
8963 ENFN->context_color_set(output, \
8964 context, \
8965 (obj->cur.cache.clip.r * or) / 255, \
8966 (obj->cur.cache.clip.g * og) / 255, \
8967 (obj->cur.cache.clip.b * ob) / 255, \
8968 (obj->cur.cache.clip.a * oa) / 255); \
8969 ENFN->rectangle_draw(output, \
8970 context, \
8971 surface, \
8972 obj->cur.geometry.x + ln->x + x + (ox), \
8973 obj->cur.geometry.y + ln->par->y + ln->y + y + (oy), \
8974 (ow), \
8975 (oh)); \
8976 } \
8977 while (0)
8978
8979#define DRAW_FORMAT_DASHED(oname, oy, oh, dw, dp) \
8980 do \
8981 { \
8982 if (itr->format->oname) \
8983 { \
8984 unsigned char _or, _og, _ob, _oa; \
8985 int _ind, _dx = 0, _dn, _dr; \
8986 _or = itr->format->color.oname.r; \
8987 _og = itr->format->color.oname.g; \
8988 _ob = itr->format->color.oname.b; \
8989 _oa = itr->format->color.oname.a; \
8990 if (!EINA_INLIST_GET(itr)->next) \
8991 { \
8992 _dn = itr->w / (dw + dp); \
8993 _dr = itr->w % (dw + dp); \
8994 } \
8995 else \
8996 { \
8997 _dn = itr->adv / (dw + dp); \
8998 _dr = itr->adv % (dw + dp); \
8999 } \
9000 if (_dr > dw) _dr = dw; \
9001 for (_ind = 0 ; _ind < _dn ; _ind++) \
9002 { \
9003 DRAW_RECT(itr->x + _dx, oy, dw, oh, _or, _og, _ob, _oa); \
9004 _dx += dw + dp; \
9005 } \
9006 DRAW_RECT(itr->x + _dx, oy, _dr, oh, _or, _og, _ob, _oa); \
9007 } \
9008 } \
9009 while (0)
9010
9011#define DRAW_FORMAT(oname, oy, oh) \
9012 do \
9013 { \
9014 if (itr->format->oname) \
9015 { \
9016 unsigned char _or, _og, _ob, _oa; \
9017 _or = itr->format->color.oname.r; \
9018 _og = itr->format->color.oname.g; \
9019 _ob = itr->format->color.oname.b; \
9020 _oa = itr->format->color.oname.a; \
9021 if (!EINA_INLIST_GET(itr)->next) \
9022 { \
9023 DRAW_RECT(itr->x, oy, itr->w, oh, _or, _og, _ob, _oa); \
9024 } \
9025 else \
9026 { \
9027 DRAW_RECT(itr->x, oy, itr->adv, oh, _or, _og, _ob, _oa); \
9028 } \
9029 } \
9030 } \
9031 while (0)
9032
9033 {
9034 Evas_Coord look_for_y = 0 - (obj->cur.geometry.y + y);
9035 if (clip)
9036 {
9037 Evas_Coord tmp_lfy = cy - (obj->cur.geometry.y + y);
9038 if (tmp_lfy > look_for_y)
9039 look_for_y = tmp_lfy;
9040 }
9041
9042 if (look_for_y >= 0)
9043 start = _layout_find_paragraph_by_y(o, look_for_y);
9044
9045 if (!start)
9046 start = o->paragraphs;
9047 }
9048
9049 ITEM_WALK()
9050 {
9051 DRAW_FORMAT(backing, 0, ln->h);
9052 }
9053 ITEM_WALK_END();
9054
9055 /* There are size adjustments that depend on the styles drawn here back
9056 * in "_text_item_update_sizes" should not modify one without the other. */
9057
9058 /* prepare everything for text draw */
9059
9060 /* shadows */
9061 ITEM_WALK()
9062 {
9063 int shad_dst, shad_sz, dx, dy, haveshad;
9064 Evas_Object_Textblock_Text_Item *ti;
9065 ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL;
9066 if (!ti) continue;
9067
9068 shad_dst = shad_sz = dx = dy = haveshad = 0;
9069 switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_BASIC)
9070 {
9071 case EVAS_TEXT_STYLE_SHADOW:
9072 case EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW:
9073 shad_dst = 1;
9074 haveshad = 1;
9075 break;
9076 case EVAS_TEXT_STYLE_OUTLINE_SHADOW:
9077 case EVAS_TEXT_STYLE_FAR_SHADOW:
9078 shad_dst = 2;
9079 haveshad = 1;
9080 break;
9081 case EVAS_TEXT_STYLE_FAR_SOFT_SHADOW:
9082 shad_dst = 2;
9083 shad_sz = 2;
9084 haveshad = 1;
9085 break;
9086 case EVAS_TEXT_STYLE_SOFT_SHADOW:
9087 shad_dst = 1;
9088 shad_sz = 2;
9089 haveshad = 1;
9090 break;
9091 default:
9092 break;
9093 }
9094 if (haveshad)
9095 {
9096 if (shad_dst > 0)
9097 {
9098 switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_SHADOW_DIRECTION)
9099 {
9100 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT:
9101 dx = 1;
9102 dy = 1;
9103 break;
9104 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM:
9105 dx = 0;
9106 dy = 1;
9107 break;
9108 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT:
9109 dx = -1;
9110 dy = 1;
9111 break;
9112 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT:
9113 dx = -1;
9114 dy = 0;
9115 break;
9116 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT:
9117 dx = -1;
9118 dy = -1;
9119 break;
9120 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP:
9121 dx = 0;
9122 dy = -1;
9123 break;
9124 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT:
9125 dx = 1;
9126 dy = -1;
9127 break;
9128 case EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT:
9129 dx = 1;
9130 dy = 0;
9131 default:
9132 break;
9133 }
9134 dx *= shad_dst;
9135 dy *= shad_dst;
9136 }
9137 switch (shad_sz)
9138 {
9139 case 0:
9140 COLOR_SET(shadow);
9141 DRAW_TEXT(dx, dy);
9142 break;
9143 case 2:
9144 for (j = 0; j < 5; j++)
9145 {
9146 for (i = 0; i < 5; i++)
9147 {
9148 if (vals[i][j] != 0)
9149 {
9150 COLOR_SET_AMUL(shadow, vals[i][j] * 50);
9151 DRAW_TEXT(i - 2 + dx, j - 2 + dy);
9152 }
9153 }
9154 }
9155 break;
9156 default:
9157 break;
9158 }
9159 }
9160 }
9161 ITEM_WALK_END();
9162
9163 /* glows */
9164 ITEM_WALK()
9165 {
9166 Evas_Object_Textblock_Text_Item *ti;
9167 ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL;
9168 if (!ti) continue;
9169
9170 if (ti->parent.format->style == EVAS_TEXT_STYLE_GLOW)
9171 {
9172 for (j = 0; j < 5; j++)
9173 {
9174 for (i = 0; i < 5; i++)
9175 {
9176 if (vals[i][j] != 0)
9177 {
9178 COLOR_SET_AMUL(glow, vals[i][j] * 50);
9179 DRAW_TEXT(i - 2, j - 2);
9180 }
9181 }
9182 }
9183 COLOR_SET(glow2);
9184 DRAW_TEXT(-1, 0);
9185 DRAW_TEXT(1, 0);
9186 DRAW_TEXT(0, -1);
9187 DRAW_TEXT(0, 1);
9188 }
9189 }
9190 ITEM_WALK_END();
9191
9192 /* outlines */
9193 ITEM_WALK()
9194 {
9195 Evas_Object_Textblock_Text_Item *ti;
9196 ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL;
9197 if (!ti) continue;
9198
9199 if ((ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE) ||
9200 (ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE_SHADOW) ||
9201 (ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW))
9202 {
9203 COLOR_SET(outline);
9204 DRAW_TEXT(-1, 0);
9205 DRAW_TEXT(1, 0);
9206 DRAW_TEXT(0, -1);
9207 DRAW_TEXT(0, 1);
9208 }
9209 else if (ti->parent.format->style == EVAS_TEXT_STYLE_SOFT_OUTLINE)
9210 {
9211 for (j = 0; j < 5; j++)
9212 {
9213 for (i = 0; i < 5; i++)
9214 {
9215 if (((i != 2) || (j != 2)) && (vals[i][j] != 0))
9216 {
9217 COLOR_SET_AMUL(outline, vals[i][j] * 50);
9218 DRAW_TEXT(i - 2, j - 2);
9219 }
9220 }
9221 }
9222 }
9223 }
9224 ITEM_WALK_END();
9225
9226 /* normal text and lines */
9227 ITEM_WALK()
9228 {
9229 Evas_Object_Textblock_Text_Item *ti;
9230 ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL;
9231 /* NORMAL TEXT */
9232 if (ti)
9233 {
9234 COLOR_SET(normal);
9235 DRAW_TEXT(0, 0);
9236 }
9237
9238 /* STRIKETHROUGH */
9239 DRAW_FORMAT(strikethrough, (ln->h / 2), 1);
9240
9241 /* UNDERLINE */
9242 DRAW_FORMAT(underline, ln->baseline + 1, 1);
9243
9244 /* UNDERLINE DASHED */
9245 DRAW_FORMAT_DASHED(underline_dash, ln->baseline + 1, 1,
9246 ti->parent.format->underline_dash_width,
9247 ti->parent.format->underline_dash_gap);
9248
9249 /* UNDERLINE2 */
9250 DRAW_FORMAT(underline2, ln->baseline + 3, 1);
9251 }
9252 ITEM_WALK_END();
9253}
9254
9255static void
9256evas_object_textblock_render_pre(Evas_Object *obj)
9257{
9258 Evas_Object_Textblock *o;
9259 int is_v, was_v;
9260
9261 /* dont pre-render the obj twice! */
9262 if (obj->pre_render_done) return;
9263 obj->pre_render_done = 1;
9264 /* pre-render phase. this does anything an object needs to do just before */
9265 /* rendering. this could mean loading the image data, retrieving it from */
9266 /* elsewhere, decoding video etc. */
9267 /* then when this is done the object needs to figure if it changed and */
9268 /* if so what and where and add the appropriate redraw textblocks */
9269 o = (Evas_Object_Textblock *)(obj->object_data);
9270 if ((o->changed) || (o->content_changed) || (o->format_changed) ||
9271 ((obj->cur.geometry.w != o->last_w) ||
9272 (((o->valign != 0.0) || (o->have_ellipsis)) &&
9273 (obj->cur.geometry.h != o->last_h))))
9274 {
9275 _relayout(obj);
9276 o->redraw = 0;
9277 evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj);
9278 is_v = evas_object_is_visible(obj);
9279 was_v = evas_object_was_visible(obj);
9280 goto done;
9281 }
9282 if (o->redraw)
9283 {
9284 o->redraw = 0;
9285 evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj);
9286 is_v = evas_object_is_visible(obj);
9287 was_v = evas_object_was_visible(obj);
9288 goto done;
9289 }
9290 /* if someone is clipping this obj - go calculate the clipper */
9291 if (obj->cur.clipper)
9292 {
9293 if (obj->cur.cache.clip.dirty)
9294 evas_object_clip_recalc(obj->cur.clipper);
9295 obj->cur.clipper->func->render_pre(obj->cur.clipper);
9296 }
9297 /* now figure what changed and add draw rects */
9298 /* if it just became visible or invisible */
9299 is_v = evas_object_is_visible(obj);
9300 was_v = evas_object_was_visible(obj);
9301 if (is_v != was_v)
9302 {
9303 evas_object_render_pre_visible_change(&obj->layer->evas->clip_changes, obj, is_v, was_v);
9304 goto done;
9305 }
9306 if ((obj->cur.map != obj->prev.map) ||
9307 (obj->cur.usemap != obj->prev.usemap))
9308 {
9309 evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj);
9310 goto done;
9311 }
9312 /* it's not visible - we accounted for it appearing or not so just abort */
9313 if (!is_v) goto done;
9314 /* clipper changed this is in addition to anything else for obj */
9315 evas_object_render_pre_clipper_change(&obj->layer->evas->clip_changes, obj);
9316 /* if we restacked (layer or just within a layer) and don't clip anyone */
9317 if (obj->restack)
9318 {
9319 evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj);
9320 goto done;
9321 }
9322 /* if it changed color */
9323 if ((obj->cur.color.r != obj->prev.color.r) ||
9324 (obj->cur.color.g != obj->prev.color.g) ||
9325 (obj->cur.color.b != obj->prev.color.b) ||
9326 (obj->cur.color.a != obj->prev.color.a))
9327 {
9328 evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj);
9329 goto done;
9330 }
9331 /* if it changed geometry - and obviously not visibility or color */
9332 /* calculate differences since we have a constant color fill */
9333 /* we really only need to update the differences */
9334 if ((obj->cur.geometry.x != obj->prev.geometry.x) ||
9335 (obj->cur.geometry.y != obj->prev.geometry.y) ||
9336 (obj->cur.geometry.w != obj->prev.geometry.w) ||
9337 (obj->cur.geometry.h != obj->prev.geometry.h))
9338 {
9339 evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj);
9340 goto done;
9341 }
9342 done:
9343 evas_object_render_pre_effect_updates(&obj->layer->evas->clip_changes, obj, is_v, was_v);
9344}
9345
9346static void
9347evas_object_textblock_render_post(Evas_Object *obj)
9348{
9349/* Evas_Object_Textblock *o; */
9350
9351 /* this moves the current data to the previous state parts of the object */
9352 /* in whatever way is safest for the object. also if we don't need object */
9353 /* data anymore we can free it if the object deems this is a good idea */
9354/* o = (Evas_Object_Textblock *)(obj->object_data); */
9355 /* remove those pesky changes */
9356 evas_object_clip_changes_clean(obj);
9357 /* move cur to prev safely for object data */
9358 obj->prev = obj->cur;
9359/* o->prev = o->cur; */
9360/* o->changed = 0; */
9361}
9362
9363static unsigned int evas_object_textblock_id_get(Evas_Object *obj)
9364{
9365 Evas_Object_Textblock *o;
9366
9367 o = (Evas_Object_Textblock *)(obj->object_data);
9368 if (!o) return 0;
9369 return MAGIC_OBJ_TEXTBLOCK;
9370}
9371
9372static unsigned int evas_object_textblock_visual_id_get(Evas_Object *obj)
9373{
9374 Evas_Object_Textblock *o;
9375
9376 o = (Evas_Object_Textblock *)(obj->object_data);
9377 if (!o) return 0;
9378 return MAGIC_OBJ_CUSTOM;
9379}
9380
9381static void *evas_object_textblock_engine_data_get(Evas_Object *obj)
9382{
9383 Evas_Object_Textblock *o;
9384
9385 o = (Evas_Object_Textblock *)(obj->object_data);
9386 if (!o) return NULL;
9387 return o->engine_data;
9388}
9389
9390static int
9391evas_object_textblock_is_opaque(Evas_Object *obj __UNUSED__)
9392{
9393 /* this returns 1 if the internal object data implies that the object is */
9394 /* currently fulyl opque over the entire gradient it occupies */
9395 return 0;
9396}
9397
9398static int
9399evas_object_textblock_was_opaque(Evas_Object *obj __UNUSED__)
9400{
9401 /* this returns 1 if the internal object data implies that the object was */
9402 /* currently fulyl opque over the entire gradient it occupies */
9403 return 0;
9404}
9405
9406static void
9407evas_object_textblock_coords_recalc(Evas_Object *obj)
9408{
9409 Evas_Object_Textblock *o;
9410
9411 o = (Evas_Object_Textblock *)(obj->object_data);
9412 if ((obj->cur.geometry.w != o->last_w) ||
9413 (((o->valign != 0.0) || (o->have_ellipsis)) &&
9414 (obj->cur.geometry.h != o->last_h)))
9415 {
9416 o->formatted.valid = 0;
9417 o->changed = 1;
9418 }
9419}
9420
9421static void
9422evas_object_textblock_scale_update(Evas_Object *obj)
9423{
9424 Evas_Object_Textblock *o;
9425
9426 o = (Evas_Object_Textblock *)(obj->object_data);
9427 _evas_textblock_invalidate_all(o);
9428 _evas_textblock_changed(o, obj);
9429}
9430
9431void
9432_evas_object_textblock_rehint(Evas_Object *obj)
9433{
9434 Evas_Object_Textblock *o;
9435 Evas_Object_Textblock_Paragraph *par;
9436 Evas_Object_Textblock_Line *ln;
9437
9438 o = (Evas_Object_Textblock *)(obj->object_data);
9439 EINA_INLIST_FOREACH(o->paragraphs, par)
9440 {
9441 EINA_INLIST_FOREACH(par->lines, ln)
9442 {
9443 Evas_Object_Textblock_Item *it;
9444
9445 EINA_INLIST_FOREACH(ln->items, it)
9446 {
9447 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
9448 {
9449 Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it);
9450 if (ti->parent.format->font.font)
9451 {
9452#ifdef EVAS_FRAME_QUEUING
9453 evas_common_pipe_op_text_flush((RGBA_Font *) ti->parent.format->font.font);
9454#endif
9455 evas_font_load_hinting_set(obj->layer->evas,
9456 ti->parent.format->font.font,
9457 obj->layer->evas->hinting);
9458 }
9459 }
9460 }
9461 }
9462 }
9463 _evas_textblock_invalidate_all(o);
9464 _evas_textblock_changed(o, obj);
9465}
9466
9467/**
9468 * @}
9469 */
9470
9471#ifdef HAVE_TESTS
9472/* return EINA_FALSE on error, used in unit_testing */
9473EAPI Eina_Bool
9474_evas_textblock_check_item_node_link(Evas_Object *obj)
9475{
9476 Evas_Object_Textblock *o;
9477 Evas_Object_Textblock_Paragraph *par;
9478 Evas_Object_Textblock_Line *ln;
9479 Evas_Object_Textblock_Item *it;
9480
9481 o = (Evas_Object_Textblock *)(obj->object_data);
9482 if (!o) return EINA_FALSE;
9483
9484 if (!o->formatted.valid) _relayout(obj);
9485
9486 EINA_INLIST_FOREACH(o->paragraphs, par)
9487 {
9488 EINA_INLIST_FOREACH(par->lines, ln)
9489 {
9490 EINA_INLIST_FOREACH(ln->items, it)
9491 {
9492 if (it->text_node != par->text_node)
9493 return EINA_FALSE;
9494 }
9495 }
9496 }
9497 return EINA_TRUE;
9498}
9499
9500EAPI int
9501_evas_textblock_format_offset_get(const Evas_Object_Textblock_Node_Format *n)
9502{
9503 return n->offset;
9504}
9505#endif
9506
9507#if 0
9508/* Good for debugging */
9509void
9510pfnode(Evas_Object_Textblock_Node_Format *n)
9511{
9512 printf("Format Node: %p\n", n);
9513 printf("next = %p, prev = %p, last = %p\n", EINA_INLIST_GET(n)->next, EINA_INLIST_GET(n)->prev, EINA_INLIST_GET(n)->last);
9514 printf("text_node = %p, offset = %u, visible = %d\n", n->text_node, n->offset, n->visible);
9515 printf("'%s'\n", eina_strbuf_string_get(n->format));
9516}
9517
9518void
9519ptnode(Evas_Object_Textblock_Node_Text *n)
9520{
9521 printf("Text Node: %p\n", n);
9522 printf("next = %p, prev = %p, last = %p\n", EINA_INLIST_GET(n)->next, EINA_INLIST_GET(n)->prev, EINA_INLIST_GET(n)->last);
9523 printf("format_node = %p\n", n->format_node);
9524 printf("'%ls'\n", eina_ustrbuf_string_get(n->unicode));
9525}
9526
9527void
9528pitem(Evas_Object_Textblock_Item *it)
9529{
9530 Evas_Object_Textblock_Text_Item *ti;
9531 Evas_Object_Textblock_Format_Item *fi;
9532 printf("Item: %p\n", it);
9533 printf("Type: %s (%d)\n", (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ?
9534 "TEXT" : "FORMAT", it->type);
9535 printf("Text pos: %d Visual pos: %d\n", it->text_pos,
9536#ifdef BIDI_SUPPORT
9537 it->visual_pos
9538#else
9539 it->text_pos
9540#endif
9541 );
9542 printf("Coords: x = %d w = %d adv = %d\n", (int) it->x, (int) it->w,
9543 (int) it->adv);
9544 if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT)
9545 {
9546 ti = _ITEM_TEXT(it);
9547 printf("Text: '%*ls'\n", ti->text_props.text_len, GET_ITEM_TEXT(ti));
9548 }
9549 else
9550 {
9551 fi = _ITEM_FORMAT(it);
9552 printf("Format: '%s'\n", fi->item);
9553 }
9554}
9555
9556void
9557ppar(Evas_Object_Textblock_Paragraph *par)
9558{
9559 Evas_Object_Textblock_Item *it;
9560 Eina_List *i;
9561 EINA_LIST_FOREACH(par->logical_items, i, it)
9562 {
9563 printf("***********************\n");
9564 pitem(it);
9565 }
9566}
9567
9568#endif
9569