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