diff options
Diffstat (limited to 'libraries/evas/src/lib/canvas/evas_object_textblock.c')
-rw-r--r-- | libraries/evas/src/lib/canvas/evas_object_textblock.c | 9569 |
1 files changed, 9569 insertions, 0 deletions
diff --git a/libraries/evas/src/lib/canvas/evas_object_textblock.c b/libraries/evas/src/lib/canvas/evas_object_textblock.c new file mode 100644 index 0000000..7941a45 --- /dev/null +++ b/libraries/evas/src/lib/canvas/evas_object_textblock.c | |||
@@ -0,0 +1,9569 @@ | |||
1 | /** | ||
2 | * @internal | ||
3 | * @section Evas_Object_Textblock_Internal Internal Textblock Object Tutorial | ||
4 | * | ||
5 | * This explains the internal design of the Evas Textblock Object, it's assumed | ||
6 | * that the reader of this section has already read @ref Evas_Object_Textblock_Tutorial "Textblock's usage docs.". | ||
7 | * | ||
8 | * @subsection textblock_internal_intro Introduction | ||
9 | * There are two main parts to the textblock object, the first being the node | ||
10 | * system, and the second being the layout system. The former is just an | ||
11 | * internal representation of the markup text, while the latter is the internal | ||
12 | * visual representation of the text (i.e positioning, sizing, fonts and etc). | ||
13 | * | ||
14 | * @subsection textblock_nodes The Nodes system | ||
15 | * The nodes mechanism consists of two main data types: | ||
16 | * ::Evas_Object_Textblock_Node_Text and ::Evas_Object_Textblock_Node_Format | ||
17 | * the former is for Text nodes and the latter is for format nodes. | ||
18 | * There's always at least one text node, even if there are only formats. | ||
19 | * | ||
20 | * @subsection textblock_nodes_text Text nodes | ||
21 | * Each text node is essentially a paragraph, it includes an @ref Eina_UStrbuf | ||
22 | * that stores the actual paragraph text, a utf8 string to store the paragraph | ||
23 | * text in utf8 (which is not used internally at all), A pointer to it's | ||
24 | * main @ref textblock_nodes_format_internal "Format Node" and the paragraph's | ||
25 | * @ref evas_bidi_props "BiDi properties". The pointer to the format node may be | ||
26 | * NULL if there's no format node anywhere before the end of the text node, | ||
27 | * not even in previous text nodes. If not NULL, it points to the first format | ||
28 | * node pointing to text inside of the text node, or if there is none, it points | ||
29 | * to the previous's text nodes format node. Each paragraph has a format node | ||
30 | * representing a paragraph separator pointing to it's last position except | ||
31 | * for the last paragraph, which has no such constraint. This constraint | ||
32 | * happens because text nodes are paragraphs and paragraphs are delimited by | ||
33 | * paragraph separators. | ||
34 | * | ||
35 | * @subsection textblock_nodes_format_internal Format Nodes - Internal | ||
36 | * Each format node stores a group of format information, for example the | ||
37 | * markup: \<font=Vera,Kochi font_size=10 align=left\> will all be inserted | ||
38 | * inside the same format node, altohugh it consists of different formatting | ||
39 | * commands. | ||
40 | * Each node has a pointer to it's text node, this pointer is NEVER NULL, even | ||
41 | * if there's only one format, and no text, a text node is created. Each format | ||
42 | * node includes an offset from the last format node of the same text node. For | ||
43 | * example, the markup "0<b>12</b>" will create two format nodes, the first | ||
44 | * having an offset of 1 and the second an offset of 2. Each format node also | ||
45 | * includes a @ref Eina_Strbuf that includes the textual representation of the | ||
46 | * format, and a boolean stating if the format is a visible format or not, see | ||
47 | * @ref textblock_nodes_format_visible | ||
48 | * | ||
49 | * @subsection textblock_nodes_format_visible Visible Format Nodes | ||
50 | * There are two types of format nodes, visible and invisible. They are the same | ||
51 | * in every way, except for the representation in the text node. While invisible | ||
52 | * format nodes have no representation in the text node, the visible ones do. | ||
53 | * The Uniceode object replacement character (0xFFFC) is inserted to every place | ||
54 | * a visible format node points to. This makes it very easy to treat visible | ||
55 | * formats as items in the text, both for BiDi purposes and cursor handling | ||
56 | * purposes. | ||
57 | * Here are a few example visible an invisible formats: | ||
58 | * Visible: newline char, tab, paragraph separator and an embedded item. | ||
59 | * Invisible: setting the color, font or alignment of the text. | ||
60 | * | ||
61 | * @subsection textblock_layout The layout system | ||
62 | * @todo write @ref textblock_layout | ||
63 | */ | ||
64 | #include <stdlib.h> | ||
65 | |||
66 | #include "evas_common.h" | ||
67 | #include "evas_private.h" | ||
68 | |||
69 | #ifdef HAVE_LINEBREAK | ||
70 | #include "linebreak.h" | ||
71 | #endif | ||
72 | |||
73 | /* save typing */ | ||
74 | #define ENFN obj->layer->evas->engine.func | ||
75 | #define ENDT obj->layer->evas->engine.data.output | ||
76 | |||
77 | /* private magic number for textblock objects */ | ||
78 | static const char o_type[] = "textblock"; | ||
79 | |||
80 | /* The char to be inserted instead of visible formats */ | ||
81 | #define EVAS_TEXTBLOCK_REPLACEMENT_CHAR 0xFFFC | ||
82 | #define _PARAGRAPH_SEPARATOR 0x2029 | ||
83 | #define EVAS_TEXTBLOCK_IS_VISIBLE_FORMAT_CHAR(ch) \ | ||
84 | (((ch) == EVAS_TEXTBLOCK_REPLACEMENT_CHAR) || \ | ||
85 | ((ch) == '\n') || \ | ||
86 | ((ch) == '\t') || \ | ||
87 | ((ch) == _PARAGRAPH_SEPARATOR)) | ||
88 | |||
89 | /* private struct for textblock object internal data */ | ||
90 | /** | ||
91 | * @internal | ||
92 | * @typedef Evas_Object_Textblock | ||
93 | * The actual textblock object. | ||
94 | */ | ||
95 | typedef struct _Evas_Object_Textblock Evas_Object_Textblock; | ||
96 | /** | ||
97 | * @internal | ||
98 | * @typedef Evas_Object_Style_Tag | ||
99 | * The structure used for finding style tags. | ||
100 | */ | ||
101 | typedef struct _Evas_Object_Style_Tag Evas_Object_Style_Tag; | ||
102 | /** | ||
103 | * @internal | ||
104 | * @typedef Evas_Object_Textblock_Node_Text | ||
105 | * A text node. | ||
106 | */ | ||
107 | typedef struct _Evas_Object_Textblock_Node_Text Evas_Object_Textblock_Node_Text; | ||
108 | /* | ||
109 | * Defined in Evas.h | ||
110 | typedef struct _Evas_Object_Textblock_Node_Format Evas_Object_Textblock_Node_Format; | ||
111 | */ | ||
112 | |||
113 | /** | ||
114 | * @internal | ||
115 | * @typedef Evas_Object_Textblock_Paragraph | ||
116 | * A layouting paragraph. | ||
117 | */ | ||
118 | typedef struct _Evas_Object_Textblock_Paragraph Evas_Object_Textblock_Paragraph; | ||
119 | /** | ||
120 | * @internal | ||
121 | * @typedef Evas_Object_Textblock_Line | ||
122 | * A layouting line. | ||
123 | */ | ||
124 | typedef struct _Evas_Object_Textblock_Line Evas_Object_Textblock_Line; | ||
125 | /** | ||
126 | * @internal | ||
127 | * @typedef Evas_Object_Textblock_Item | ||
128 | * A layouting item. | ||
129 | */ | ||
130 | typedef struct _Evas_Object_Textblock_Item Evas_Object_Textblock_Item; | ||
131 | /** | ||
132 | * @internal | ||
133 | * @typedef Evas_Object_Textblock_Item | ||
134 | * A layouting text item. | ||
135 | */ | ||
136 | typedef struct _Evas_Object_Textblock_Text_Item Evas_Object_Textblock_Text_Item; | ||
137 | /** | ||
138 | * @internal | ||
139 | * @typedef Evas_Object_Textblock_Format_Item | ||
140 | * A layouting format item. | ||
141 | */ | ||
142 | typedef struct _Evas_Object_Textblock_Format_Item Evas_Object_Textblock_Format_Item; | ||
143 | /** | ||
144 | * @internal | ||
145 | * @typedef Evas_Object_Textblock_Format | ||
146 | * A textblock format. | ||
147 | */ | ||
148 | typedef struct _Evas_Object_Textblock_Format Evas_Object_Textblock_Format; | ||
149 | |||
150 | /** | ||
151 | * @internal | ||
152 | * @def IS_AT_END(ti, ind) | ||
153 | * Return true if ind is at the end of the text item, false otherwise. | ||
154 | */ | ||
155 | #define IS_AT_END(ti, ind) (ind == ti->text_props.text_len) | ||
156 | |||
157 | /** | ||
158 | * @internal | ||
159 | * @def MOVE_PREV_UNTIL(limit, ind) | ||
160 | * This decrements ind as long as ind > limit. | ||
161 | */ | ||
162 | #define MOVE_PREV_UNTIL(limit, ind) \ | ||
163 | do \ | ||
164 | { \ | ||
165 | if ((limit) < (ind)) \ | ||
166 | (ind)--; \ | ||
167 | } \ | ||
168 | while (0) | ||
169 | |||
170 | /** | ||
171 | * @internal | ||
172 | * @def MOVE_NEXT_UNTIL(limit, ind) | ||
173 | * This increments ind as long as ind < limit | ||
174 | */ | ||
175 | #define MOVE_NEXT_UNTIL(limit, ind) \ | ||
176 | do \ | ||
177 | { \ | ||
178 | if ((ind) < (limit)) \ | ||
179 | (ind)++; \ | ||
180 | } \ | ||
181 | while (0) | ||
182 | |||
183 | /** | ||
184 | * @internal | ||
185 | * @def GET_ITEM_TEXT(ti) | ||
186 | * Returns a const reference to the text of the ti (not null terminated). | ||
187 | */ | ||
188 | #define GET_ITEM_TEXT(ti) \ | ||
189 | (((ti)->parent.text_node) ? \ | ||
190 | (eina_ustrbuf_string_get((ti)->parent.text_node->unicode) + \ | ||
191 | (ti)->parent.text_pos) : EINA_UNICODE_EMPTY_STRING) | ||
192 | /** | ||
193 | * @internal | ||
194 | * @def _FORMAT_IS_CLOSER_OF(base, closer, closer_len) | ||
195 | * Returns true if closer is the closer of base. | ||
196 | */ | ||
197 | #define _FORMAT_IS_CLOSER_OF(base, closer, closer_len) \ | ||
198 | (!strncmp(base + 1, closer, closer_len) && \ | ||
199 | (!base[closer_len + 1] || \ | ||
200 | (base[closer_len + 1] == '=') || \ | ||
201 | _is_white(base[closer_len + 1]))) | ||
202 | |||
203 | /*FIXME: document the structs and struct items. */ | ||
204 | struct _Evas_Object_Style_Tag | ||
205 | { | ||
206 | EINA_INLIST; | ||
207 | char *tag; | ||
208 | char *replace; | ||
209 | size_t tag_len; | ||
210 | size_t replace_len; | ||
211 | }; | ||
212 | |||
213 | struct _Evas_Object_Textblock_Node_Text | ||
214 | { | ||
215 | EINA_INLIST; | ||
216 | Eina_UStrbuf *unicode; | ||
217 | char *utf8; | ||
218 | Evas_Object_Textblock_Node_Format *format_node; | ||
219 | Evas_Object_Textblock_Paragraph *par; | ||
220 | Eina_Bool dirty : 1; | ||
221 | Eina_Bool is_new : 1; | ||
222 | }; | ||
223 | |||
224 | struct _Evas_Object_Textblock_Node_Format | ||
225 | { | ||
226 | EINA_INLIST; | ||
227 | const char *format; | ||
228 | const char *orig_format; | ||
229 | Evas_Object_Textblock_Node_Text *text_node; | ||
230 | size_t offset; | ||
231 | unsigned char anchor : 2; | ||
232 | Eina_Bool visible : 1; | ||
233 | Eina_Bool format_change : 1; | ||
234 | Eina_Bool is_new : 1; | ||
235 | }; | ||
236 | |||
237 | #define ANCHOR_NONE 0 | ||
238 | #define ANCHOR_A 1 | ||
239 | #define ANCHOR_ITEM 2 | ||
240 | |||
241 | /** | ||
242 | * @internal | ||
243 | * @def _NODE_TEXT(x) | ||
244 | * A convinience macro for casting to a text node. | ||
245 | */ | ||
246 | #define _NODE_TEXT(x) ((Evas_Object_Textblock_Node_Text *) (x)) | ||
247 | /** | ||
248 | * @internal | ||
249 | * @def _NODE_FORMAT(x) | ||
250 | * A convinience macro for casting to a format node. | ||
251 | */ | ||
252 | #define _NODE_FORMAT(x) ((Evas_Object_Textblock_Node_Format *) (x)) | ||
253 | /** | ||
254 | * @internal | ||
255 | * @def _ITEM(x) | ||
256 | * A convinience macro for casting to a generic item. | ||
257 | */ | ||
258 | #define _ITEM(x) ((Evas_Object_Textblock_Item *) (x)) | ||
259 | /** | ||
260 | * @internal | ||
261 | * @def _ITEM_TEXT(x) | ||
262 | * A convinience macro for casting to a text item. | ||
263 | */ | ||
264 | #define _ITEM_TEXT(x) ((Evas_Object_Textblock_Text_Item *) (x)) | ||
265 | /** | ||
266 | * @internal | ||
267 | * @def _ITEM_FORMAT(x) | ||
268 | * A convinience macro for casting to a format item. | ||
269 | */ | ||
270 | #define _ITEM_FORMAT(x) ((Evas_Object_Textblock_Format_Item *) (x)) | ||
271 | |||
272 | struct _Evas_Object_Textblock_Paragraph | ||
273 | { | ||
274 | EINA_INLIST; | ||
275 | Evas_Object_Textblock_Line *lines; | ||
276 | Evas_Object_Textblock_Node_Text *text_node; | ||
277 | Eina_List *logical_items; | ||
278 | Evas_BiDi_Paragraph_Props *bidi_props; /* Only valid during layout */ | ||
279 | Evas_BiDi_Direction direction; | ||
280 | Evas_Coord y, w, h; | ||
281 | int line_no; | ||
282 | Eina_Bool is_bidi : 1; | ||
283 | Eina_Bool visible : 1; | ||
284 | Eina_Bool rendered : 1; | ||
285 | }; | ||
286 | |||
287 | struct _Evas_Object_Textblock_Line | ||
288 | { | ||
289 | EINA_INLIST; | ||
290 | Evas_Object_Textblock_Item *items; | ||
291 | Evas_Object_Textblock_Paragraph *par; | ||
292 | Evas_Coord x, y, w, h; | ||
293 | int baseline; | ||
294 | int line_no; | ||
295 | }; | ||
296 | |||
297 | typedef enum _Evas_Textblock_Item_Type | ||
298 | { | ||
299 | EVAS_TEXTBLOCK_ITEM_TEXT, | ||
300 | EVAS_TEXTBLOCK_ITEM_FORMAT, | ||
301 | } Evas_Textblock_Item_Type; | ||
302 | |||
303 | struct _Evas_Object_Textblock_Item | ||
304 | { | ||
305 | EINA_INLIST; | ||
306 | Evas_Textblock_Item_Type type; | ||
307 | Evas_Object_Textblock_Node_Text *text_node; | ||
308 | Evas_Object_Textblock_Format *format; | ||
309 | size_t text_pos; | ||
310 | #ifdef BIDI_SUPPORT | ||
311 | size_t visual_pos; | ||
312 | #endif | ||
313 | Evas_Coord adv, x, w, h; | ||
314 | Eina_Bool merge : 1; /* Indicates whether this | ||
315 | item should merge to the | ||
316 | previous item or not */ | ||
317 | Eina_Bool visually_deleted : 1; | ||
318 | /* Indicates whether this | ||
319 | item is used in the visual | ||
320 | layout or not. */ | ||
321 | }; | ||
322 | |||
323 | struct _Evas_Object_Textblock_Text_Item | ||
324 | { | ||
325 | Evas_Object_Textblock_Item parent; | ||
326 | Evas_Text_Props text_props; | ||
327 | Evas_Coord inset; | ||
328 | Evas_Coord x_adjustment; /* Used to indicate by how | ||
329 | much we adjusted sizes */ | ||
330 | }; | ||
331 | |||
332 | struct _Evas_Object_Textblock_Format_Item | ||
333 | { | ||
334 | Evas_Object_Textblock_Item parent; | ||
335 | Evas_BiDi_Direction bidi_dir; | ||
336 | const char *item; | ||
337 | int y; | ||
338 | unsigned char vsize : 2; | ||
339 | unsigned char size : 2; | ||
340 | Eina_Bool formatme : 1; | ||
341 | }; | ||
342 | |||
343 | struct _Evas_Object_Textblock_Format | ||
344 | { | ||
345 | Evas_Object_Textblock_Node_Format *fnode; | ||
346 | double halign; | ||
347 | double valign; | ||
348 | struct { | ||
349 | Evas_Font_Description *fdesc; | ||
350 | const char *source; | ||
351 | Evas_Font_Set *font; | ||
352 | Evas_Font_Size size; | ||
353 | } font; | ||
354 | struct { | ||
355 | struct { | ||
356 | unsigned char r, g, b, a; | ||
357 | } normal, underline, underline2, underline_dash, outline, shadow, glow, glow2, backing, | ||
358 | strikethrough; | ||
359 | } color; | ||
360 | struct { | ||
361 | int l, r; | ||
362 | } margin; | ||
363 | int ref; | ||
364 | int tabstops; | ||
365 | int linesize; | ||
366 | int linegap; | ||
367 | int underline_dash_width; | ||
368 | int underline_dash_gap; | ||
369 | double linerelsize; | ||
370 | double linerelgap; | ||
371 | double linefill; | ||
372 | double ellipsis; | ||
373 | unsigned char style; | ||
374 | Eina_Bool wrap_word : 1; | ||
375 | Eina_Bool wrap_char : 1; | ||
376 | Eina_Bool wrap_mixed : 1; | ||
377 | Eina_Bool underline : 1; | ||
378 | Eina_Bool underline2 : 1; | ||
379 | Eina_Bool underline_dash : 1; | ||
380 | Eina_Bool strikethrough : 1; | ||
381 | Eina_Bool backing : 1; | ||
382 | Eina_Bool password : 1; | ||
383 | Eina_Bool halign_auto : 1; | ||
384 | }; | ||
385 | |||
386 | struct _Evas_Textblock_Style | ||
387 | { | ||
388 | const char *style_text; | ||
389 | char *default_tag; | ||
390 | Evas_Object_Style_Tag *tags; | ||
391 | Eina_List *objects; | ||
392 | Eina_Bool delete_me : 1; | ||
393 | }; | ||
394 | |||
395 | struct _Evas_Textblock_Cursor | ||
396 | { | ||
397 | Evas_Object *obj; | ||
398 | size_t pos; | ||
399 | Evas_Object_Textblock_Node_Text *node; | ||
400 | }; | ||
401 | |||
402 | /* Size of the index array */ | ||
403 | #define TEXTBLOCK_PAR_INDEX_SIZE 10 | ||
404 | struct _Evas_Object_Textblock | ||
405 | { | ||
406 | DATA32 magic; | ||
407 | Evas_Textblock_Style *style; | ||
408 | Evas_Textblock_Cursor *cursor; | ||
409 | Eina_List *cursors; | ||
410 | Evas_Object_Textblock_Node_Text *text_nodes; | ||
411 | Evas_Object_Textblock_Node_Format *format_nodes; | ||
412 | |||
413 | int num_paragraphs; | ||
414 | Evas_Object_Textblock_Paragraph *paragraphs; | ||
415 | Evas_Object_Textblock_Paragraph *par_index[TEXTBLOCK_PAR_INDEX_SIZE]; | ||
416 | |||
417 | Evas_Object_Textblock_Text_Item *ellip_ti; | ||
418 | Eina_List *anchors_a; | ||
419 | Eina_List *anchors_item; | ||
420 | int last_w, last_h; | ||
421 | struct { | ||
422 | int l, r, t, b; | ||
423 | } style_pad; | ||
424 | double valign; | ||
425 | char *markup_text; | ||
426 | void *engine_data; | ||
427 | const char *repch; | ||
428 | const char *bidi_delimiters; | ||
429 | struct { | ||
430 | int w, h; | ||
431 | Eina_Bool valid : 1; | ||
432 | } formatted, native; | ||
433 | Eina_Bool redraw : 1; | ||
434 | Eina_Bool changed : 1; | ||
435 | Eina_Bool content_changed : 1; | ||
436 | Eina_Bool format_changed : 1; | ||
437 | Eina_Bool have_ellipsis : 1; | ||
438 | Eina_Bool legacy_newline : 1; | ||
439 | }; | ||
440 | |||
441 | /* private methods for textblock objects */ | ||
442 | static void evas_object_textblock_init(Evas_Object *obj); | ||
443 | static void *evas_object_textblock_new(void); | ||
444 | static void evas_object_textblock_render(Evas_Object *obj, void *output, void *context, void *surface, int x, int y); | ||
445 | static void evas_object_textblock_free(Evas_Object *obj); | ||
446 | static void evas_object_textblock_render_pre(Evas_Object *obj); | ||
447 | static void evas_object_textblock_render_post(Evas_Object *obj); | ||
448 | |||
449 | static unsigned int evas_object_textblock_id_get(Evas_Object *obj); | ||
450 | static unsigned int evas_object_textblock_visual_id_get(Evas_Object *obj); | ||
451 | static void *evas_object_textblock_engine_data_get(Evas_Object *obj); | ||
452 | |||
453 | static int evas_object_textblock_is_opaque(Evas_Object *obj); | ||
454 | static int evas_object_textblock_was_opaque(Evas_Object *obj); | ||
455 | |||
456 | static void evas_object_textblock_coords_recalc(Evas_Object *obj); | ||
457 | |||
458 | static void evas_object_textblock_scale_update(Evas_Object *obj); | ||
459 | |||
460 | static const Evas_Object_Func object_func = | ||
461 | { | ||
462 | /* methods (compulsory) */ | ||
463 | evas_object_textblock_free, | ||
464 | evas_object_textblock_render, | ||
465 | evas_object_textblock_render_pre, | ||
466 | evas_object_textblock_render_post, | ||
467 | evas_object_textblock_id_get, | ||
468 | evas_object_textblock_visual_id_get, | ||
469 | evas_object_textblock_engine_data_get, | ||
470 | /* these are optional. NULL = nothing */ | ||
471 | NULL, | ||
472 | NULL, | ||
473 | NULL, | ||
474 | NULL, | ||
475 | evas_object_textblock_is_opaque, | ||
476 | evas_object_textblock_was_opaque, | ||
477 | NULL, | ||
478 | NULL, | ||
479 | evas_object_textblock_coords_recalc, | ||
480 | evas_object_textblock_scale_update, | ||
481 | NULL, | ||
482 | NULL, | ||
483 | NULL | ||
484 | }; | ||
485 | |||
486 | /* the actual api call to add a textblock */ | ||
487 | |||
488 | #define TB_HEAD() \ | ||
489 | Evas_Object_Textblock *o; \ | ||
490 | MAGIC_CHECK(obj, Evas_Object, MAGIC_OBJ); \ | ||
491 | return; \ | ||
492 | MAGIC_CHECK_END(); \ | ||
493 | o = (Evas_Object_Textblock *)(obj->object_data); \ | ||
494 | MAGIC_CHECK(o, Evas_Object_Textblock, MAGIC_OBJ_TEXTBLOCK); \ | ||
495 | return; \ | ||
496 | MAGIC_CHECK_END(); | ||
497 | |||
498 | #define TB_HEAD_RETURN(x) \ | ||
499 | Evas_Object_Textblock *o; \ | ||
500 | MAGIC_CHECK(obj, Evas_Object, MAGIC_OBJ); \ | ||
501 | return (x); \ | ||
502 | MAGIC_CHECK_END(); \ | ||
503 | o = (Evas_Object_Textblock *)(obj->object_data); \ | ||
504 | MAGIC_CHECK(o, Evas_Object_Textblock, MAGIC_OBJ_TEXTBLOCK); \ | ||
505 | return (x); \ | ||
506 | MAGIC_CHECK_END(); | ||
507 | |||
508 | |||
509 | |||
510 | static Eina_Bool _evas_textblock_cursor_is_at_the_end(const Evas_Textblock_Cursor *cur); | ||
511 | static void _evas_textblock_node_text_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n); | ||
512 | static void _evas_textblock_node_text_remove_formats_between(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n, int start, int end); | ||
513 | static Evas_Object_Textblock_Node_Format *_evas_textblock_cursor_node_format_before_or_at_pos_get(const Evas_Textblock_Cursor *cur); | ||
514 | static size_t _evas_textblock_node_format_pos_get(const Evas_Object_Textblock_Node_Format *fmt); | ||
515 | static void _evas_textblock_node_format_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n, int visual_adjustment); | ||
516 | static void _evas_textblock_node_format_free(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n); | ||
517 | static void _evas_textblock_node_text_free(Evas_Object_Textblock_Node_Text *n); | ||
518 | static void _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj); | ||
519 | static void _evas_textblock_invalidate_all(Evas_Object_Textblock *o); | ||
520 | static void _evas_textblock_cursors_update_offset(const Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Text *n, size_t start, int offset); | ||
521 | static void _evas_textblock_cursors_set_node(Evas_Object_Textblock *o, const Evas_Object_Textblock_Node_Text *n, Evas_Object_Textblock_Node_Text *new_node); | ||
522 | |||
523 | /* styles */ | ||
524 | /** | ||
525 | * @internal | ||
526 | * Clears the textblock style passed except for the style_text which is replaced. | ||
527 | * @param ts The ts to be cleared. Must not be NULL. | ||
528 | * @param style_text the style's text. | ||
529 | */ | ||
530 | static void | ||
531 | _style_replace(Evas_Textblock_Style *ts, const char *style_text) | ||
532 | { | ||
533 | eina_stringshare_replace(&ts->style_text, style_text); | ||
534 | if (ts->default_tag) free(ts->default_tag); | ||
535 | while (ts->tags) | ||
536 | { | ||
537 | Evas_Object_Style_Tag *tag; | ||
538 | |||
539 | tag = (Evas_Object_Style_Tag *)ts->tags; | ||
540 | ts->tags = (Evas_Object_Style_Tag *)eina_inlist_remove(EINA_INLIST_GET(ts->tags), EINA_INLIST_GET(tag)); | ||
541 | free(tag->tag); | ||
542 | free(tag->replace); | ||
543 | free(tag); | ||
544 | } | ||
545 | ts->default_tag = NULL; | ||
546 | ts->tags = NULL; | ||
547 | } | ||
548 | |||
549 | /** | ||
550 | * @internal | ||
551 | * Clears the textblock style passed. | ||
552 | * @param ts The ts to be cleared. Must not be NULL. | ||
553 | */ | ||
554 | static void | ||
555 | _style_clear(Evas_Textblock_Style *ts) | ||
556 | { | ||
557 | _style_replace(ts, NULL); | ||
558 | } | ||
559 | |||
560 | /** | ||
561 | * @internal | ||
562 | * Searches inside the tags stored in the style for the tag matching s. | ||
563 | * @param ts The ts to be cleared. Must not be NULL. | ||
564 | * @param s The tag to be matched. | ||
565 | * @param tag_len the length of the tag string. | ||
566 | * @param[out] replace_len The length of the replcaement found. - Must not be NULL. | ||
567 | * @return The replacement string found. | ||
568 | */ | ||
569 | static inline const char * | ||
570 | _style_match_tag(Evas_Textblock_Style *ts, const char *s, size_t tag_len, size_t *replace_len) | ||
571 | { | ||
572 | Evas_Object_Style_Tag *tag; | ||
573 | |||
574 | EINA_INLIST_FOREACH(ts->tags, tag) | ||
575 | { | ||
576 | if (tag->tag_len != tag_len) continue; | ||
577 | if (!strncmp(tag->tag, s, tag_len)) | ||
578 | { | ||
579 | *replace_len = tag->replace_len; | ||
580 | return tag->replace; | ||
581 | } | ||
582 | } | ||
583 | *replace_len = 0; | ||
584 | return NULL; | ||
585 | } | ||
586 | |||
587 | /** | ||
588 | * @internal | ||
589 | * Clears all the nodes (text and format) of the textblock object. | ||
590 | * @param obj The evas object, must not be NULL. | ||
591 | */ | ||
592 | static void | ||
593 | _nodes_clear(const Evas_Object *obj) | ||
594 | { | ||
595 | Evas_Object_Textblock *o; | ||
596 | |||
597 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
598 | while (o->text_nodes) | ||
599 | { | ||
600 | Evas_Object_Textblock_Node_Text *n; | ||
601 | |||
602 | n = o->text_nodes; | ||
603 | o->text_nodes = _NODE_TEXT(eina_inlist_remove( | ||
604 | EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n))); | ||
605 | _evas_textblock_node_text_free(n); | ||
606 | } | ||
607 | while (o->format_nodes) | ||
608 | { | ||
609 | Evas_Object_Textblock_Node_Format *n; | ||
610 | |||
611 | n = o->format_nodes; | ||
612 | o->format_nodes = _NODE_FORMAT(eina_inlist_remove(EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n))); | ||
613 | _evas_textblock_node_format_free(o, n); | ||
614 | } | ||
615 | } | ||
616 | |||
617 | /** | ||
618 | * @internal | ||
619 | * Unrefs and frees (if needed) a textblock format. | ||
620 | * @param obj The Evas_Object, Must not be NULL. | ||
621 | * @param fmt the format to be cleaned, must not be NULL. | ||
622 | */ | ||
623 | static void | ||
624 | _format_unref_free(const Evas_Object *obj, Evas_Object_Textblock_Format *fmt) | ||
625 | { | ||
626 | fmt->ref--; | ||
627 | if (fmt->ref > 0) return; | ||
628 | if (fmt->font.fdesc) evas_font_desc_unref(fmt->font.fdesc); | ||
629 | if (fmt->font.source) eina_stringshare_del(fmt->font.source); | ||
630 | evas_font_free(obj->layer->evas, fmt->font.font); | ||
631 | free(fmt); | ||
632 | } | ||
633 | |||
634 | /** | ||
635 | * @internal | ||
636 | * Free a layout item | ||
637 | * @param obj The evas object, must not be NULL. | ||
638 | * @param ln the layout line on which the item is in, must not be NULL. | ||
639 | * @param it the layout item to be freed | ||
640 | */ | ||
641 | static void | ||
642 | _item_free(const Evas_Object *obj, Evas_Object_Textblock_Line *ln, Evas_Object_Textblock_Item *it) | ||
643 | { | ||
644 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
645 | { | ||
646 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
647 | |||
648 | evas_common_text_props_content_unref(&ti->text_props); | ||
649 | } | ||
650 | else | ||
651 | { | ||
652 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
653 | |||
654 | if (fi->item) eina_stringshare_del(fi->item); | ||
655 | } | ||
656 | _format_unref_free(obj, it->format); | ||
657 | if (ln) | ||
658 | { | ||
659 | ln->items = (Evas_Object_Textblock_Item *) eina_inlist_remove( | ||
660 | EINA_INLIST_GET(ln->items), EINA_INLIST_GET(ln->items)); | ||
661 | } | ||
662 | free(it); | ||
663 | } | ||
664 | |||
665 | /** | ||
666 | * @internal | ||
667 | * Free a layout line. | ||
668 | * @param obj The evas object, must not be NULL. | ||
669 | * @param ln the layout line to be freed, must not be NULL. | ||
670 | */ | ||
671 | static void | ||
672 | _line_free(Evas_Object_Textblock_Line *ln) | ||
673 | { | ||
674 | /* Items are freed from the logical list, except for the ellip item */ | ||
675 | if (ln) free(ln); | ||
676 | } | ||
677 | |||
678 | /* table of html escapes (that i can find) this should be ordered with the | ||
679 | * most common first as it's a linear search to match - no hash for this. | ||
680 | * | ||
681 | * these are stored as one large string and one additional array that | ||
682 | * contains the offsets to the tokens for space efficiency. | ||
683 | */ | ||
684 | /** | ||
685 | * @internal | ||
686 | * @var escape_strings[] | ||
687 | * This string consists of NULL terminated pairs of strings, the first of | ||
688 | * every pair is an escape and the second is the value of the escape. | ||
689 | */ | ||
690 | static const char escape_strings[] = | ||
691 | /* most common escaped stuff */ | ||
692 | ""\0" "\x22\0" | ||
693 | "&\0" "\x26\0" | ||
694 | "<\0" "\x3c\0" | ||
695 | ">\0" "\x3e\0" | ||
696 | /* all the rest */ | ||
697 | " \0" "\xc2\xa0\0" | ||
698 | "¡\0" "\xc2\xa1\0" | ||
699 | "¢\0" "\xc2\xa2\0" | ||
700 | "£\0" "\xc2\xa3\0" | ||
701 | "¤\0" "\xc2\xa4\0" | ||
702 | "¥\0" "\xc2\xa5\0" | ||
703 | "¦\0" "\xc2\xa6\0" | ||
704 | "§\0" "\xc2\xa7\0" | ||
705 | "¨\0" "\xc2\xa8\0" | ||
706 | "©\0" "\xc2\xa9\0" | ||
707 | "ª\0" "\xc2\xaa\0" | ||
708 | "«\0" "\xc2\xab\0" | ||
709 | "¬\0" "\xc2\xac\0" | ||
710 | "®\0" "\xc2\xae\0" | ||
711 | "¯\0" "\xc2\xaf\0" | ||
712 | "°\0" "\xc2\xb0\0" | ||
713 | "±\0" "\xc2\xb1\0" | ||
714 | "²\0" "\xc2\xb2\0" | ||
715 | "³\0" "\xc2\xb3\0" | ||
716 | "´\0" "\xc2\xb4\0" | ||
717 | "µ\0" "\xc2\xb5\0" | ||
718 | "¶\0" "\xc2\xb6\0" | ||
719 | "·\0" "\xc2\xb7\0" | ||
720 | "¸\0" "\xc2\xb8\0" | ||
721 | "¹\0" "\xc2\xb9\0" | ||
722 | "º\0" "\xc2\xba\0" | ||
723 | "»\0" "\xc2\xbb\0" | ||
724 | "¼\0" "\xc2\xbc\0" | ||
725 | "½\0" "\xc2\xbd\0" | ||
726 | "¾\0" "\xc2\xbe\0" | ||
727 | "¿\0" "\xc2\xbf\0" | ||
728 | "À\0" "\xc3\x80\0" | ||
729 | "Á\0" "\xc3\x81\0" | ||
730 | "Â\0" "\xc3\x82\0" | ||
731 | "Ã\0" "\xc3\x83\0" | ||
732 | "Ä\0" "\xc3\x84\0" | ||
733 | "Å\0" "\xc3\x85\0" | ||
734 | "&Aelig;\0" "\xc3\x86\0" | ||
735 | "Ç\0" "\xc3\x87\0" | ||
736 | "È\0" "\xc3\x88\0" | ||
737 | "É\0" "\xc3\x89\0" | ||
738 | "Ê\0" "\xc3\x8a\0" | ||
739 | "Ë\0" "\xc3\x8b\0" | ||
740 | "Ì\0" "\xc3\x8c\0" | ||
741 | "Í\0" "\xc3\x8d\0" | ||
742 | "Î\0" "\xc3\x8e\0" | ||
743 | "Ï\0" "\xc3\x8f\0" | ||
744 | "&Eth;\0" "\xc3\x90\0" | ||
745 | "Ñ\0" "\xc3\x91\0" | ||
746 | "Ò\0" "\xc3\x92\0" | ||
747 | "Ó\0" "\xc3\x93\0" | ||
748 | "Ô\0" "\xc3\x94\0" | ||
749 | "Õ\0" "\xc3\x95\0" | ||
750 | "Ö\0" "\xc3\x96\0" | ||
751 | "×\0" "\xc3\x97\0" | ||
752 | "Ø\0" "\xc3\x98\0" | ||
753 | "Ù\0" "\xc3\x99\0" | ||
754 | "Ú\0" "\xc3\x9a\0" | ||
755 | "Û\0" "\xc3\x9b\0" | ||
756 | "Ý\0" "\xc3\x9d\0" | ||
757 | "&Thorn;\0" "\xc3\x9e\0" | ||
758 | "ß\0" "\xc3\x9f\0" | ||
759 | "à\0" "\xc3\xa0\0" | ||
760 | "á\0" "\xc3\xa1\0" | ||
761 | "â\0" "\xc3\xa2\0" | ||
762 | "ã\0" "\xc3\xa3\0" | ||
763 | "ä\0" "\xc3\xa4\0" | ||
764 | "å\0" "\xc3\xa5\0" | ||
765 | "æ\0" "\xc3\xa6\0" | ||
766 | "ç\0" "\xc3\xa7\0" | ||
767 | "è\0" "\xc3\xa8\0" | ||
768 | "é\0" "\xc3\xa9\0" | ||
769 | "ê\0" "\xc3\xaa\0" | ||
770 | "ë\0" "\xc3\xab\0" | ||
771 | "ì\0" "\xc3\xac\0" | ||
772 | "í\0" "\xc3\xad\0" | ||
773 | "î\0" "\xc3\xae\0" | ||
774 | "ï\0" "\xc3\xaf\0" | ||
775 | "ð\0" "\xc3\xb0\0" | ||
776 | "ñ\0" "\xc3\xb1\0" | ||
777 | "ò\0" "\xc3\xb2\0" | ||
778 | "ó\0" "\xc3\xb3\0" | ||
779 | "ô\0" "\xc3\xb4\0" | ||
780 | "õ\0" "\xc3\xb5\0" | ||
781 | "ö\0" "\xc3\xb6\0" | ||
782 | "÷\0" "\xc3\xb7\0" | ||
783 | "ø\0" "\xc3\xb8\0" | ||
784 | "ù\0" "\xc3\xb9\0" | ||
785 | "ú\0" "\xc3\xba\0" | ||
786 | "û\0" "\xc3\xbb\0" | ||
787 | "ü\0" "\xc3\xbc\0" | ||
788 | "ý\0" "\xc3\xbd\0" | ||
789 | "þ\0" "\xc3\xbe\0" | ||
790 | "ÿ\0" "\xc3\xbf\0" | ||
791 | "α\0" "\xce\x91\0" | ||
792 | "β\0" "\xce\x92\0" | ||
793 | "γ\0" "\xce\x93\0" | ||
794 | "δ\0" "\xce\x94\0" | ||
795 | "ε\0" "\xce\x95\0" | ||
796 | "ζ\0" "\xce\x96\0" | ||
797 | "η\0" "\xce\x97\0" | ||
798 | "θ\0" "\xce\x98\0" | ||
799 | "ι\0" "\xce\x99\0" | ||
800 | "κ\0" "\xce\x9a\0" | ||
801 | "λ\0" "\xce\x9b\0" | ||
802 | "μ\0" "\xce\x9c\0" | ||
803 | "ν\0" "\xce\x9d\0" | ||
804 | "ξ\0" "\xce\x9e\0" | ||
805 | "ο\0" "\xce\x9f\0" | ||
806 | "π\0" "\xce\xa0\0" | ||
807 | "ρ\0" "\xce\xa1\0" | ||
808 | "σ\0" "\xce\xa3\0" | ||
809 | "τ\0" "\xce\xa4\0" | ||
810 | "υ\0" "\xce\xa5\0" | ||
811 | "φ\0" "\xce\xa6\0" | ||
812 | "χ\0" "\xce\xa7\0" | ||
813 | "ψ\0" "\xce\xa8\0" | ||
814 | "ω\0" "\xce\xa9\0" | ||
815 | "…\0" "\xe2\x80\xa6\0" | ||
816 | "€\0" "\xe2\x82\xac\0" | ||
817 | "←\0" "\xe2\x86\x90\0" | ||
818 | "↑\0" "\xe2\x86\x91\0" | ||
819 | "→\0" "\xe2\x86\x92\0" | ||
820 | "↓\0" "\xe2\x86\x93\0" | ||
821 | "↔\0" "\xe2\x86\x94\0" | ||
822 | "←\0" "\xe2\x87\x90\0" | ||
823 | "→\0" "\xe2\x87\x92\0" | ||
824 | "∀\0" "\xe2\x88\x80\0" | ||
825 | "∃\0" "\xe2\x88\x83\0" | ||
826 | "∇\0" "\xe2\x88\x87\0" | ||
827 | "∏\0" "\xe2\x88\x8f\0" | ||
828 | "∑\0" "\xe2\x88\x91\0" | ||
829 | "∧\0" "\xe2\x88\xa7\0" | ||
830 | "∨\0" "\xe2\x88\xa8\0" | ||
831 | "∫\0" "\xe2\x88\xab\0" | ||
832 | "≠\0" "\xe2\x89\xa0\0" | ||
833 | "≡\0" "\xe2\x89\xa1\0" | ||
834 | "⊕\0" "\xe2\x8a\x95\0" | ||
835 | "⊥\0" "\xe2\x8a\xa5\0" | ||
836 | "†\0" "\xe2\x80\xa0\0" | ||
837 | "‡\0" "\xe2\x80\xa1\0" | ||
838 | "•\0" "\xe2\x80\xa2\0" | ||
839 | ; | ||
840 | |||
841 | EVAS_MEMPOOL(_mp_obj); | ||
842 | |||
843 | /** | ||
844 | * @internal | ||
845 | * Checks if a char is a whitespace. | ||
846 | * @param c the unicode codepoint. | ||
847 | * @return EINA_TRUE if the unicode codepoint is a whitespace, EINA_FALSE otherwise. | ||
848 | */ | ||
849 | static Eina_Bool | ||
850 | _is_white(Eina_Unicode c) | ||
851 | { | ||
852 | /* | ||
853 | * unicode list of whitespace chars | ||
854 | * | ||
855 | * 0009..000D <control-0009>..<control-000D> | ||
856 | * 0020 SPACE | ||
857 | * 0085 <control-0085> | ||
858 | * 00A0 NO-BREAK SPACE | ||
859 | * 1680 OGHAM SPACE MARK | ||
860 | * 180E MONGOLIAN VOWEL SEPARATOR | ||
861 | * 2000..200A EN QUAD..HAIR SPACE | ||
862 | * 2028 LINE SEPARATOR | ||
863 | * 2029 PARAGRAPH SEPARATOR | ||
864 | * 202F NARROW NO-BREAK SPACE | ||
865 | * 205F MEDIUM MATHEMATICAL SPACE | ||
866 | * 3000 IDEOGRAPHIC SPACE | ||
867 | */ | ||
868 | if ( | ||
869 | (c == 0x20) || | ||
870 | ((c >= 0x9) && (c <= 0xd)) || | ||
871 | (c == 0x85) || | ||
872 | (c == 0xa0) || | ||
873 | (c == 0x1680) || | ||
874 | (c == 0x180e) || | ||
875 | ((c >= 0x2000) && (c <= 0x200a)) || | ||
876 | (c == 0x2028) || | ||
877 | (c == 0x2029) || | ||
878 | (c == 0x202f) || | ||
879 | (c == 0x205f) || | ||
880 | (c == 0x3000) | ||
881 | ) | ||
882 | return EINA_TRUE; | ||
883 | return EINA_FALSE; | ||
884 | } | ||
885 | |||
886 | /** | ||
887 | * @internal | ||
888 | * Prepends the text between s and p to the main cursor of the object. | ||
889 | * | ||
890 | * @param cur the cursor to prepend to. | ||
891 | * @param[in] s start of the string | ||
892 | * @param[in] p end of the string | ||
893 | */ | ||
894 | static void | ||
895 | _prepend_text_run(Evas_Textblock_Cursor *cur, const char *s, const char *p) | ||
896 | { | ||
897 | if ((s) && (p > s)) | ||
898 | { | ||
899 | char *ts; | ||
900 | |||
901 | ts = alloca(p - s + 1); | ||
902 | strncpy(ts, s, p - s); | ||
903 | ts[p - s] = 0; | ||
904 | evas_textblock_cursor_text_prepend(cur, ts); | ||
905 | } | ||
906 | } | ||
907 | |||
908 | |||
909 | /** | ||
910 | * @internal | ||
911 | * Returns the numeric value of HEX chars for example for ch = 'A' | ||
912 | * the function will return 10. | ||
913 | * | ||
914 | * @param ch The HEX char. | ||
915 | * @return numeric value of HEX. | ||
916 | */ | ||
917 | static int | ||
918 | _hex_string_get(char ch) | ||
919 | { | ||
920 | if ((ch >= '0') && (ch <= '9')) return (ch - '0'); | ||
921 | else if ((ch >= 'A') && (ch <= 'F')) return (ch - 'A' + 10); | ||
922 | else if ((ch >= 'a') && (ch <= 'f')) return (ch - 'a' + 10); | ||
923 | return 0; | ||
924 | } | ||
925 | |||
926 | /** | ||
927 | * @internal | ||
928 | * Parses a string of one of the formas: | ||
929 | * 1. "#RRGGBB" | ||
930 | * 2. "#RRGGBBAA" | ||
931 | * 3. "#RGB" | ||
932 | * 4. "#RGBA" | ||
933 | * To the rgba values. | ||
934 | * | ||
935 | * @param[in] str The string to parse - NOT NULL. | ||
936 | * @param[out] r The Red value - NOT NULL. | ||
937 | * @param[out] g The Green value - NOT NULL. | ||
938 | * @param[out] b The Blue value - NOT NULL. | ||
939 | * @param[out] a The Alpha value - NOT NULL. | ||
940 | */ | ||
941 | static void | ||
942 | _format_color_parse(const char *str, unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) | ||
943 | { | ||
944 | int slen; | ||
945 | |||
946 | slen = strlen(str); | ||
947 | *r = *g = *b = *a = 0; | ||
948 | |||
949 | if (slen == 7) /* #RRGGBB */ | ||
950 | { | ||
951 | *r = (_hex_string_get(str[1]) << 4) | (_hex_string_get(str[2])); | ||
952 | *g = (_hex_string_get(str[3]) << 4) | (_hex_string_get(str[4])); | ||
953 | *b = (_hex_string_get(str[5]) << 4) | (_hex_string_get(str[6])); | ||
954 | *a = 0xff; | ||
955 | } | ||
956 | else if (slen == 9) /* #RRGGBBAA */ | ||
957 | { | ||
958 | *r = (_hex_string_get(str[1]) << 4) | (_hex_string_get(str[2])); | ||
959 | *g = (_hex_string_get(str[3]) << 4) | (_hex_string_get(str[4])); | ||
960 | *b = (_hex_string_get(str[5]) << 4) | (_hex_string_get(str[6])); | ||
961 | *a = (_hex_string_get(str[7]) << 4) | (_hex_string_get(str[8])); | ||
962 | } | ||
963 | else if (slen == 4) /* #RGB */ | ||
964 | { | ||
965 | *r = _hex_string_get(str[1]); | ||
966 | *r = (*r << 4) | *r; | ||
967 | *g = _hex_string_get(str[2]); | ||
968 | *g = (*g << 4) | *g; | ||
969 | *b = _hex_string_get(str[3]); | ||
970 | *b = (*b << 4) | *b; | ||
971 | *a = 0xff; | ||
972 | } | ||
973 | else if (slen == 5) /* #RGBA */ | ||
974 | { | ||
975 | *r = _hex_string_get(str[1]); | ||
976 | *r = (*r << 4) | *r; | ||
977 | *g = _hex_string_get(str[2]); | ||
978 | *g = (*g << 4) | *g; | ||
979 | *b = _hex_string_get(str[3]); | ||
980 | *b = (*b << 4) | *b; | ||
981 | *a = _hex_string_get(str[4]); | ||
982 | *a = (*a << 4) | *a; | ||
983 | } | ||
984 | *r = (*r * *a) / 255; | ||
985 | *g = (*g * *a) / 255; | ||
986 | *b = (*b * *a) / 255; | ||
987 | } | ||
988 | |||
989 | /* The refcount for the formats. */ | ||
990 | static int format_refcount = 0; | ||
991 | /* Holders for the stringshares */ | ||
992 | static const char *fontstr = NULL; | ||
993 | static const char *font_fallbacksstr = NULL; | ||
994 | static const char *font_sizestr = NULL; | ||
995 | static const char *font_sourcestr = NULL; | ||
996 | static const char *font_weightstr = NULL; | ||
997 | static const char *font_stylestr = NULL; | ||
998 | static const char *font_widthstr = NULL; | ||
999 | static const char *langstr = NULL; | ||
1000 | static const char *colorstr = NULL; | ||
1001 | static const char *underline_colorstr = NULL; | ||
1002 | static const char *underline2_colorstr = NULL; | ||
1003 | static const char *underline_dash_colorstr = NULL; | ||
1004 | static const char *outline_colorstr = NULL; | ||
1005 | static const char *shadow_colorstr = NULL; | ||
1006 | static const char *glow_colorstr = NULL; | ||
1007 | static const char *glow2_colorstr = NULL; | ||
1008 | static const char *backing_colorstr = NULL; | ||
1009 | static const char *strikethrough_colorstr = NULL; | ||
1010 | static const char *alignstr = NULL; | ||
1011 | static const char *valignstr = NULL; | ||
1012 | static const char *wrapstr = NULL; | ||
1013 | static const char *left_marginstr = NULL; | ||
1014 | static const char *right_marginstr = NULL; | ||
1015 | static const char *underlinestr = NULL; | ||
1016 | static const char *strikethroughstr = NULL; | ||
1017 | static const char *backingstr = NULL; | ||
1018 | static const char *stylestr = NULL; | ||
1019 | static const char *tabstopsstr = NULL; | ||
1020 | static const char *linesizestr = NULL; | ||
1021 | static const char *linerelsizestr = NULL; | ||
1022 | static const char *linegapstr = NULL; | ||
1023 | static const char *linerelgapstr = NULL; | ||
1024 | static const char *itemstr = NULL; | ||
1025 | static const char *linefillstr = NULL; | ||
1026 | static const char *ellipsisstr = NULL; | ||
1027 | static const char *passwordstr = NULL; | ||
1028 | static const char *underline_dash_widthstr = NULL; | ||
1029 | static const char *underline_dash_gapstr = NULL; | ||
1030 | |||
1031 | /** | ||
1032 | * @internal | ||
1033 | * Init the format strings. | ||
1034 | */ | ||
1035 | static void | ||
1036 | _format_command_init(void) | ||
1037 | { | ||
1038 | if (format_refcount == 0) | ||
1039 | { | ||
1040 | fontstr = eina_stringshare_add("font"); | ||
1041 | font_fallbacksstr = eina_stringshare_add("font_fallbacks"); | ||
1042 | font_sizestr = eina_stringshare_add("font_size"); | ||
1043 | font_sourcestr = eina_stringshare_add("font_source"); | ||
1044 | font_weightstr = eina_stringshare_add("font_weight"); | ||
1045 | font_stylestr = eina_stringshare_add("font_style"); | ||
1046 | font_widthstr = eina_stringshare_add("font_width"); | ||
1047 | langstr = eina_stringshare_add("lang"); | ||
1048 | colorstr = eina_stringshare_add("color"); | ||
1049 | underline_colorstr = eina_stringshare_add("underline_color"); | ||
1050 | underline2_colorstr = eina_stringshare_add("underline2_color"); | ||
1051 | underline_dash_colorstr = eina_stringshare_add("underline_dash_color"); | ||
1052 | outline_colorstr = eina_stringshare_add("outline_color"); | ||
1053 | shadow_colorstr = eina_stringshare_add("shadow_color"); | ||
1054 | glow_colorstr = eina_stringshare_add("glow_color"); | ||
1055 | glow2_colorstr = eina_stringshare_add("glow2_color"); | ||
1056 | backing_colorstr = eina_stringshare_add("backing_color"); | ||
1057 | strikethrough_colorstr = eina_stringshare_add("strikethrough_color"); | ||
1058 | alignstr = eina_stringshare_add("align"); | ||
1059 | valignstr = eina_stringshare_add("valign"); | ||
1060 | wrapstr = eina_stringshare_add("wrap"); | ||
1061 | left_marginstr = eina_stringshare_add("left_margin"); | ||
1062 | right_marginstr = eina_stringshare_add("right_margin"); | ||
1063 | underlinestr = eina_stringshare_add("underline"); | ||
1064 | strikethroughstr = eina_stringshare_add("strikethrough"); | ||
1065 | backingstr = eina_stringshare_add("backing"); | ||
1066 | stylestr = eina_stringshare_add("style"); | ||
1067 | tabstopsstr = eina_stringshare_add("tabstops"); | ||
1068 | linesizestr = eina_stringshare_add("linesize"); | ||
1069 | linerelsizestr = eina_stringshare_add("linerelsize"); | ||
1070 | linegapstr = eina_stringshare_add("linegap"); | ||
1071 | linerelgapstr = eina_stringshare_add("linerelgap"); | ||
1072 | itemstr = eina_stringshare_add("item"); | ||
1073 | linefillstr = eina_stringshare_add("linefill"); | ||
1074 | ellipsisstr = eina_stringshare_add("ellipsis"); | ||
1075 | passwordstr = eina_stringshare_add("password"); | ||
1076 | underline_dash_widthstr = eina_stringshare_add("underline_dash_width"); | ||
1077 | underline_dash_gapstr = eina_stringshare_add("underline_dash_gap"); | ||
1078 | } | ||
1079 | format_refcount++; | ||
1080 | } | ||
1081 | |||
1082 | /** | ||
1083 | * @internal | ||
1084 | * Shutdown the format strings. | ||
1085 | */ | ||
1086 | static void | ||
1087 | _format_command_shutdown(void) | ||
1088 | { | ||
1089 | if (--format_refcount > 0) return; | ||
1090 | |||
1091 | eina_stringshare_del(fontstr); | ||
1092 | eina_stringshare_del(font_fallbacksstr); | ||
1093 | eina_stringshare_del(font_sizestr); | ||
1094 | eina_stringshare_del(font_sourcestr); | ||
1095 | eina_stringshare_del(font_weightstr); | ||
1096 | eina_stringshare_del(font_stylestr); | ||
1097 | eina_stringshare_del(font_widthstr); | ||
1098 | eina_stringshare_del(langstr); | ||
1099 | eina_stringshare_del(colorstr); | ||
1100 | eina_stringshare_del(underline_colorstr); | ||
1101 | eina_stringshare_del(underline2_colorstr); | ||
1102 | eina_stringshare_del(underline_dash_colorstr); | ||
1103 | eina_stringshare_del(outline_colorstr); | ||
1104 | eina_stringshare_del(shadow_colorstr); | ||
1105 | eina_stringshare_del(glow_colorstr); | ||
1106 | eina_stringshare_del(glow2_colorstr); | ||
1107 | eina_stringshare_del(backing_colorstr); | ||
1108 | eina_stringshare_del(strikethrough_colorstr); | ||
1109 | eina_stringshare_del(alignstr); | ||
1110 | eina_stringshare_del(valignstr); | ||
1111 | eina_stringshare_del(wrapstr); | ||
1112 | eina_stringshare_del(left_marginstr); | ||
1113 | eina_stringshare_del(right_marginstr); | ||
1114 | eina_stringshare_del(underlinestr); | ||
1115 | eina_stringshare_del(strikethroughstr); | ||
1116 | eina_stringshare_del(backingstr); | ||
1117 | eina_stringshare_del(stylestr); | ||
1118 | eina_stringshare_del(tabstopsstr); | ||
1119 | eina_stringshare_del(linesizestr); | ||
1120 | eina_stringshare_del(linerelsizestr); | ||
1121 | eina_stringshare_del(linegapstr); | ||
1122 | eina_stringshare_del(linerelgapstr); | ||
1123 | eina_stringshare_del(itemstr); | ||
1124 | eina_stringshare_del(linefillstr); | ||
1125 | eina_stringshare_del(ellipsisstr); | ||
1126 | eina_stringshare_del(passwordstr); | ||
1127 | eina_stringshare_del(underline_dash_widthstr); | ||
1128 | eina_stringshare_del(underline_dash_gapstr); | ||
1129 | } | ||
1130 | |||
1131 | /** | ||
1132 | * @internal | ||
1133 | * Copies str to dst while removing the \\ char, i.e unescape the escape sequences. | ||
1134 | * | ||
1135 | * @param[out] dst the destination string - Should not be NULL. | ||
1136 | * @param[in] src the source string - Should not be NULL. | ||
1137 | */ | ||
1138 | static void | ||
1139 | _format_clean_param(char *dst, const char *src) | ||
1140 | { | ||
1141 | const char *ss; | ||
1142 | char *ds; | ||
1143 | |||
1144 | ds = dst; | ||
1145 | for (ss = src; *ss; ss++, ds++) | ||
1146 | { | ||
1147 | if ((*ss == '\\') && *(ss + 1)) ss++; | ||
1148 | *ds = *ss; | ||
1149 | } | ||
1150 | *ds = 0; | ||
1151 | } | ||
1152 | |||
1153 | /** | ||
1154 | * @internal | ||
1155 | * Parses the cmd and parameter and adds the parsed format to fmt. | ||
1156 | * | ||
1157 | * @param obj the evas object - should not be NULL. | ||
1158 | * @param fmt The format to populate - should not be NULL. | ||
1159 | * @param[in] cmd the command to process, should be stringshared. | ||
1160 | * @param[in] param the parameter of the command. | ||
1161 | */ | ||
1162 | static void | ||
1163 | _format_command(Evas_Object *obj, Evas_Object_Textblock_Format *fmt, const char *cmd, const char *param) | ||
1164 | { | ||
1165 | int len; | ||
1166 | char *tmp_param; | ||
1167 | |||
1168 | len = strlen(param); | ||
1169 | tmp_param = alloca(len + 1); | ||
1170 | |||
1171 | _format_clean_param(tmp_param, param); | ||
1172 | |||
1173 | /* If we are changing the font, create the fdesc. */ | ||
1174 | if ((cmd == font_weightstr) || (cmd == font_widthstr) || | ||
1175 | (cmd == font_stylestr) || (cmd == langstr) || | ||
1176 | (cmd == fontstr) || (cmd == font_fallbacksstr)) | ||
1177 | { | ||
1178 | if (!fmt->font.fdesc) | ||
1179 | { | ||
1180 | fmt->font.fdesc = evas_font_desc_new(); | ||
1181 | } | ||
1182 | else if (!fmt->font.fdesc->is_new) | ||
1183 | { | ||
1184 | Evas_Font_Description *old = fmt->font.fdesc; | ||
1185 | fmt->font.fdesc = evas_font_desc_dup(fmt->font.fdesc); | ||
1186 | if (old) evas_font_desc_unref(old); | ||
1187 | } | ||
1188 | } | ||
1189 | |||
1190 | |||
1191 | if (cmd == fontstr) | ||
1192 | { | ||
1193 | evas_font_name_parse(fmt->font.fdesc, tmp_param); | ||
1194 | } | ||
1195 | else if (cmd == font_fallbacksstr) | ||
1196 | { | ||
1197 | eina_stringshare_replace(&(fmt->font.fdesc->fallbacks), tmp_param); | ||
1198 | } | ||
1199 | else if (cmd == font_sizestr) | ||
1200 | { | ||
1201 | int v; | ||
1202 | |||
1203 | v = atoi(tmp_param); | ||
1204 | if (v != fmt->font.size) | ||
1205 | { | ||
1206 | fmt->font.size = v; | ||
1207 | } | ||
1208 | } | ||
1209 | else if (cmd == font_sourcestr) | ||
1210 | { | ||
1211 | if ((!fmt->font.source) || | ||
1212 | ((fmt->font.source) && (strcmp(fmt->font.source, tmp_param)))) | ||
1213 | { | ||
1214 | if (fmt->font.source) eina_stringshare_del(fmt->font.source); | ||
1215 | fmt->font.source = eina_stringshare_add(tmp_param); | ||
1216 | } | ||
1217 | } | ||
1218 | else if (cmd == font_weightstr) | ||
1219 | { | ||
1220 | fmt->font.fdesc->weight = evas_font_style_find(tmp_param, | ||
1221 | tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_WEIGHT); | ||
1222 | } | ||
1223 | else if (cmd == font_stylestr) | ||
1224 | { | ||
1225 | fmt->font.fdesc->slant = evas_font_style_find(tmp_param, | ||
1226 | tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_SLANT); | ||
1227 | } | ||
1228 | else if (cmd == font_widthstr) | ||
1229 | { | ||
1230 | fmt->font.fdesc->width = evas_font_style_find(tmp_param, | ||
1231 | tmp_param + strlen(tmp_param), EVAS_FONT_STYLE_WIDTH); | ||
1232 | } | ||
1233 | else if (cmd == langstr) | ||
1234 | { | ||
1235 | eina_stringshare_replace(&(fmt->font.fdesc->lang), tmp_param); | ||
1236 | } | ||
1237 | else if (cmd == colorstr) | ||
1238 | _format_color_parse(tmp_param, | ||
1239 | &(fmt->color.normal.r), &(fmt->color.normal.g), | ||
1240 | &(fmt->color.normal.b), &(fmt->color.normal.a)); | ||
1241 | else if (cmd == underline_colorstr) | ||
1242 | _format_color_parse(tmp_param, | ||
1243 | &(fmt->color.underline.r), &(fmt->color.underline.g), | ||
1244 | &(fmt->color.underline.b), &(fmt->color.underline.a)); | ||
1245 | else if (cmd == underline2_colorstr) | ||
1246 | _format_color_parse(tmp_param, | ||
1247 | &(fmt->color.underline2.r), &(fmt->color.underline2.g), | ||
1248 | &(fmt->color.underline2.b), &(fmt->color.underline2.a)); | ||
1249 | else if (cmd == underline_dash_colorstr) | ||
1250 | _format_color_parse(tmp_param, | ||
1251 | &(fmt->color.underline_dash.r), &(fmt->color.underline_dash.g), | ||
1252 | &(fmt->color.underline_dash.b), &(fmt->color.underline_dash.a)); | ||
1253 | else if (cmd == outline_colorstr) | ||
1254 | _format_color_parse(tmp_param, | ||
1255 | &(fmt->color.outline.r), &(fmt->color.outline.g), | ||
1256 | &(fmt->color.outline.b), &(fmt->color.outline.a)); | ||
1257 | else if (cmd == shadow_colorstr) | ||
1258 | _format_color_parse(tmp_param, | ||
1259 | &(fmt->color.shadow.r), &(fmt->color.shadow.g), | ||
1260 | &(fmt->color.shadow.b), &(fmt->color.shadow.a)); | ||
1261 | else if (cmd == glow_colorstr) | ||
1262 | _format_color_parse(tmp_param, | ||
1263 | &(fmt->color.glow.r), &(fmt->color.glow.g), | ||
1264 | &(fmt->color.glow.b), &(fmt->color.glow.a)); | ||
1265 | else if (cmd == glow2_colorstr) | ||
1266 | _format_color_parse(tmp_param, | ||
1267 | &(fmt->color.glow2.r), &(fmt->color.glow2.g), | ||
1268 | &(fmt->color.glow2.b), &(fmt->color.glow2.a)); | ||
1269 | else if (cmd == backing_colorstr) | ||
1270 | _format_color_parse(tmp_param, | ||
1271 | &(fmt->color.backing.r), &(fmt->color.backing.g), | ||
1272 | &(fmt->color.backing.b), &(fmt->color.backing.a)); | ||
1273 | else if (cmd == strikethrough_colorstr) | ||
1274 | _format_color_parse(tmp_param, | ||
1275 | &(fmt->color.strikethrough.r), &(fmt->color.strikethrough.g), | ||
1276 | &(fmt->color.strikethrough.b), &(fmt->color.strikethrough.a)); | ||
1277 | else if (cmd == alignstr) | ||
1278 | { | ||
1279 | if (!strcmp(tmp_param, "auto")) | ||
1280 | { | ||
1281 | fmt->halign_auto = EINA_TRUE; | ||
1282 | } | ||
1283 | else | ||
1284 | { | ||
1285 | if (!strcmp(tmp_param, "middle")) fmt->halign = 0.5; | ||
1286 | else if (!strcmp(tmp_param, "center")) fmt->halign = 0.5; | ||
1287 | else if (!strcmp(tmp_param, "left")) fmt->halign = 0.0; | ||
1288 | else if (!strcmp(tmp_param, "right")) fmt->halign = 1.0; | ||
1289 | else | ||
1290 | { | ||
1291 | char *endptr = NULL; | ||
1292 | double val = strtod(tmp_param, &endptr); | ||
1293 | if (endptr) | ||
1294 | { | ||
1295 | while (*endptr && _is_white(*endptr)) | ||
1296 | endptr++; | ||
1297 | if (*endptr == '%') | ||
1298 | val /= 100.0; | ||
1299 | } | ||
1300 | fmt->halign = val; | ||
1301 | if (fmt->halign < 0.0) fmt->halign = 0.0; | ||
1302 | else if (fmt->halign > 1.0) fmt->halign = 1.0; | ||
1303 | } | ||
1304 | fmt->halign_auto = EINA_FALSE; | ||
1305 | } | ||
1306 | } | ||
1307 | else if (cmd == valignstr) | ||
1308 | { | ||
1309 | if (!strcmp(tmp_param, "top")) fmt->valign = 0.0; | ||
1310 | else if (!strcmp(tmp_param, "middle")) fmt->valign = 0.5; | ||
1311 | else if (!strcmp(tmp_param, "center")) fmt->valign = 0.5; | ||
1312 | else if (!strcmp(tmp_param, "bottom")) fmt->valign = 1.0; | ||
1313 | else if (!strcmp(tmp_param, "baseline")) fmt->valign = -1.0; | ||
1314 | else if (!strcmp(tmp_param, "base")) fmt->valign = -1.0; | ||
1315 | else | ||
1316 | { | ||
1317 | char *endptr = NULL; | ||
1318 | double val = strtod(tmp_param, &endptr); | ||
1319 | if (endptr) | ||
1320 | { | ||
1321 | while (*endptr && _is_white(*endptr)) | ||
1322 | endptr++; | ||
1323 | if (*endptr == '%') | ||
1324 | val /= 100.0; | ||
1325 | } | ||
1326 | fmt->valign = val; | ||
1327 | if (fmt->valign < 0.0) fmt->valign = 0.0; | ||
1328 | else if (fmt->valign > 1.0) fmt->valign = 1.0; | ||
1329 | } | ||
1330 | } | ||
1331 | else if (cmd == wrapstr) | ||
1332 | { | ||
1333 | if (!strcmp(tmp_param, "word")) | ||
1334 | { | ||
1335 | fmt->wrap_word = 1; | ||
1336 | fmt->wrap_char = fmt->wrap_mixed = 0; | ||
1337 | } | ||
1338 | else if (!strcmp(tmp_param, "char")) | ||
1339 | { | ||
1340 | fmt->wrap_word = fmt->wrap_mixed = 0; | ||
1341 | fmt->wrap_char = 1; | ||
1342 | } | ||
1343 | else if (!strcmp(tmp_param, "mixed")) | ||
1344 | { | ||
1345 | fmt->wrap_word = fmt->wrap_char = 0; | ||
1346 | fmt->wrap_mixed = 1; | ||
1347 | } | ||
1348 | else | ||
1349 | { | ||
1350 | fmt->wrap_word = fmt->wrap_mixed = fmt->wrap_char = 0; | ||
1351 | } | ||
1352 | } | ||
1353 | else if (cmd == left_marginstr) | ||
1354 | { | ||
1355 | if (!strcmp(tmp_param, "reset")) | ||
1356 | fmt->margin.l = 0; | ||
1357 | else | ||
1358 | { | ||
1359 | if (tmp_param[0] == '+') | ||
1360 | fmt->margin.l += atoi(&(tmp_param[1])); | ||
1361 | else if (tmp_param[0] == '-') | ||
1362 | fmt->margin.l -= atoi(&(tmp_param[1])); | ||
1363 | else | ||
1364 | fmt->margin.l = atoi(tmp_param); | ||
1365 | if (fmt->margin.l < 0) fmt->margin.l = 0; | ||
1366 | } | ||
1367 | } | ||
1368 | else if (cmd == right_marginstr) | ||
1369 | { | ||
1370 | if (!strcmp(tmp_param, "reset")) | ||
1371 | fmt->margin.r = 0; | ||
1372 | else | ||
1373 | { | ||
1374 | if (tmp_param[0] == '+') | ||
1375 | fmt->margin.r += atoi(&(tmp_param[1])); | ||
1376 | else if (tmp_param[0] == '-') | ||
1377 | fmt->margin.r -= atoi(&(tmp_param[1])); | ||
1378 | else | ||
1379 | fmt->margin.r = atoi(tmp_param); | ||
1380 | if (fmt->margin.r < 0) fmt->margin.r = 0; | ||
1381 | } | ||
1382 | } | ||
1383 | else if (cmd == underlinestr) | ||
1384 | { | ||
1385 | if (!strcmp(tmp_param, "off")) | ||
1386 | { | ||
1387 | fmt->underline = 0; | ||
1388 | fmt->underline2 = 0; | ||
1389 | } | ||
1390 | else if ((!strcmp(tmp_param, "on")) || | ||
1391 | (!strcmp(tmp_param, "single"))) | ||
1392 | { | ||
1393 | fmt->underline = 1; | ||
1394 | fmt->underline2 = 0; | ||
1395 | } | ||
1396 | else if (!strcmp(tmp_param, "double")) | ||
1397 | { | ||
1398 | fmt->underline = 1; | ||
1399 | fmt->underline2 = 1; | ||
1400 | } | ||
1401 | else if (!strcmp(tmp_param, "dashed")) | ||
1402 | fmt->underline_dash = 1; | ||
1403 | } | ||
1404 | else if (cmd == strikethroughstr) | ||
1405 | { | ||
1406 | if (!strcmp(tmp_param, "off")) | ||
1407 | fmt->strikethrough = 0; | ||
1408 | else if (!strcmp(tmp_param, "on")) | ||
1409 | fmt->strikethrough = 1; | ||
1410 | } | ||
1411 | else if (cmd == backingstr) | ||
1412 | { | ||
1413 | if (!strcmp(tmp_param, "off")) | ||
1414 | fmt->backing = 0; | ||
1415 | else if (!strcmp(tmp_param, "on")) | ||
1416 | fmt->backing = 1; | ||
1417 | } | ||
1418 | else if (cmd == stylestr) | ||
1419 | { | ||
1420 | char *p1, *p2, *p, *pp; | ||
1421 | |||
1422 | p1 = alloca(len + 1); | ||
1423 | *p1 = 0; | ||
1424 | p2 = alloca(len + 1); | ||
1425 | *p2 = 0; | ||
1426 | /* no comma */ | ||
1427 | if (!strstr(tmp_param, ",")) p1 = tmp_param; | ||
1428 | else | ||
1429 | { | ||
1430 | /* split string "str1,str2" into p1 and p2 (if we have more than | ||
1431 | * 1 str2 eg "str1,str2,str3,str4" then we don't care. p2 just | ||
1432 | * ends up being the last one as right now it's only valid to have | ||
1433 | * 1 comma and 2 strings */ | ||
1434 | pp = p1; | ||
1435 | for (p = tmp_param; *p; p++) | ||
1436 | { | ||
1437 | if (*p == ',') | ||
1438 | { | ||
1439 | *pp = 0; | ||
1440 | pp = p2; | ||
1441 | continue; | ||
1442 | } | ||
1443 | *pp = *p; | ||
1444 | pp++; | ||
1445 | } | ||
1446 | *pp = 0; | ||
1447 | } | ||
1448 | if (!strcmp(p1, "off")) fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
1449 | else if (!strcmp(p1, "none")) fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
1450 | else if (!strcmp(p1, "plain")) fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
1451 | else if (!strcmp(p1, "shadow")) fmt->style = EVAS_TEXT_STYLE_SHADOW; | ||
1452 | else if (!strcmp(p1, "outline")) fmt->style = EVAS_TEXT_STYLE_OUTLINE; | ||
1453 | else if (!strcmp(p1, "soft_outline")) fmt->style = EVAS_TEXT_STYLE_SOFT_OUTLINE; | ||
1454 | else if (!strcmp(p1, "outline_shadow")) fmt->style = EVAS_TEXT_STYLE_OUTLINE_SHADOW; | ||
1455 | else if (!strcmp(p1, "outline_soft_shadow")) fmt->style = EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW; | ||
1456 | else if (!strcmp(p1, "glow")) fmt->style = EVAS_TEXT_STYLE_GLOW; | ||
1457 | else if (!strcmp(p1, "far_shadow")) fmt->style = EVAS_TEXT_STYLE_FAR_SHADOW; | ||
1458 | else if (!strcmp(p1, "soft_shadow")) fmt->style = EVAS_TEXT_STYLE_SOFT_SHADOW; | ||
1459 | else if (!strcmp(p1, "far_soft_shadow")) fmt->style = EVAS_TEXT_STYLE_FAR_SOFT_SHADOW; | ||
1460 | else fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
1461 | |||
1462 | if (*p2) | ||
1463 | { | ||
1464 | if (!strcmp(p2, "bottom_right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT); | ||
1465 | else if (!strcmp(p2, "bottom")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM); | ||
1466 | else if (!strcmp(p2, "bottom_left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT); | ||
1467 | else if (!strcmp(p2, "left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT); | ||
1468 | else if (!strcmp(p2, "top_left")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT); | ||
1469 | else if (!strcmp(p2, "top")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP); | ||
1470 | else if (!strcmp(p2, "top_right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT); | ||
1471 | else if (!strcmp(p2, "right")) EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT); | ||
1472 | else EVAS_TEXT_STYLE_SHADOW_DIRECTION_SET(fmt->style, EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT); | ||
1473 | } | ||
1474 | } | ||
1475 | else if (cmd == tabstopsstr) | ||
1476 | { | ||
1477 | fmt->tabstops = atoi(tmp_param); | ||
1478 | if (fmt->tabstops < 1) fmt->tabstops = 1; | ||
1479 | } | ||
1480 | else if (cmd == linesizestr) | ||
1481 | { | ||
1482 | fmt->linesize = atoi(tmp_param); | ||
1483 | fmt->linerelsize = 0.0; | ||
1484 | } | ||
1485 | else if (cmd == linerelsizestr) | ||
1486 | { | ||
1487 | char *endptr = NULL; | ||
1488 | double val = strtod(tmp_param, &endptr); | ||
1489 | if (endptr) | ||
1490 | { | ||
1491 | while (*endptr && _is_white(*endptr)) | ||
1492 | endptr++; | ||
1493 | if (*endptr == '%') | ||
1494 | { | ||
1495 | fmt->linerelsize = val / 100.0; | ||
1496 | fmt->linesize = 0; | ||
1497 | if (fmt->linerelsize < 0.0) fmt->linerelsize = 0.0; | ||
1498 | } | ||
1499 | } | ||
1500 | } | ||
1501 | else if (cmd == linegapstr) | ||
1502 | { | ||
1503 | fmt->linegap = atoi(tmp_param); | ||
1504 | fmt->linerelgap = 0.0; | ||
1505 | } | ||
1506 | else if (cmd == linerelgapstr) | ||
1507 | { | ||
1508 | char *endptr = NULL; | ||
1509 | double val = strtod(tmp_param, &endptr); | ||
1510 | if (endptr) | ||
1511 | { | ||
1512 | while (*endptr && _is_white(*endptr)) | ||
1513 | endptr++; | ||
1514 | if (*endptr == '%') | ||
1515 | { | ||
1516 | fmt->linerelgap = val / 100.0; | ||
1517 | fmt->linegap = 0; | ||
1518 | if (fmt->linerelgap < 0.0) fmt->linerelgap = 0.0; | ||
1519 | } | ||
1520 | } | ||
1521 | } | ||
1522 | else if (cmd == itemstr) | ||
1523 | { | ||
1524 | // itemstr == replacement object items in textblock - inline imges | ||
1525 | // for example | ||
1526 | } | ||
1527 | else if (cmd == linefillstr) | ||
1528 | { | ||
1529 | char *endptr = NULL; | ||
1530 | double val = strtod(tmp_param, &endptr); | ||
1531 | if (endptr) | ||
1532 | { | ||
1533 | while (*endptr && _is_white(*endptr)) | ||
1534 | endptr++; | ||
1535 | if (*endptr == '%') | ||
1536 | { | ||
1537 | fmt->linefill = val / 100.0; | ||
1538 | if (fmt->linefill < 0.0) fmt->linefill = 0.0; | ||
1539 | } | ||
1540 | } | ||
1541 | } | ||
1542 | else if (cmd == ellipsisstr) | ||
1543 | { | ||
1544 | char *endptr = NULL; | ||
1545 | fmt->ellipsis = strtod(tmp_param, &endptr); | ||
1546 | if ((fmt->ellipsis < 0.0) || (fmt->ellipsis > 1.0)) | ||
1547 | fmt->ellipsis = -1.0; | ||
1548 | else | ||
1549 | { | ||
1550 | Evas_Object_Textblock *o; | ||
1551 | |||
1552 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
1553 | o->have_ellipsis = 1; | ||
1554 | } | ||
1555 | } | ||
1556 | else if (cmd == passwordstr) | ||
1557 | { | ||
1558 | if (!strcmp(tmp_param, "off")) | ||
1559 | fmt->password = 0; | ||
1560 | else if (!strcmp(tmp_param, "on")) | ||
1561 | fmt->password = 1; | ||
1562 | } | ||
1563 | else if (cmd == underline_dash_widthstr) | ||
1564 | { | ||
1565 | fmt->underline_dash_width = atoi(tmp_param); | ||
1566 | if (fmt->underline_dash_width <= 0) fmt->underline_dash_width = 1; | ||
1567 | } | ||
1568 | else if (cmd == underline_dash_gapstr) | ||
1569 | { | ||
1570 | fmt->underline_dash_gap = atoi(tmp_param); | ||
1571 | if (fmt->underline_dash_gap <= 0) fmt->underline_dash_gap = 1; | ||
1572 | } | ||
1573 | } | ||
1574 | |||
1575 | /** | ||
1576 | * @internal | ||
1577 | * Returns #EINA_TRUE if the item is a format parameter, #EINA_FALSE otherwise. | ||
1578 | * | ||
1579 | * @param[in] item the item to check - Not NULL. | ||
1580 | */ | ||
1581 | static Eina_Bool | ||
1582 | _format_is_param(const char *item) | ||
1583 | { | ||
1584 | if (strchr(item, '=')) return EINA_TRUE; | ||
1585 | return EINA_FALSE; | ||
1586 | } | ||
1587 | |||
1588 | /** | ||
1589 | * @internal | ||
1590 | * Parse the format item and populate key and val with the stringshares that | ||
1591 | * corrospond to the formats parsed. | ||
1592 | * It expects item to be of the structure: | ||
1593 | * "key=val" | ||
1594 | * | ||
1595 | * @param[in] item the item to parse - Not NULL. | ||
1596 | * @param[out] key where to store the key at - Not NULL. | ||
1597 | * @param[out] val where to store the value at - Not NULL. | ||
1598 | */ | ||
1599 | static void | ||
1600 | _format_param_parse(const char *item, const char **key, const char **val) | ||
1601 | { | ||
1602 | const char *start, *end, *quote; | ||
1603 | |||
1604 | start = strchr(item, '='); | ||
1605 | *key = eina_stringshare_add_length(item, start - item); | ||
1606 | start++; /* Advance after the '=' */ | ||
1607 | /* If we can find a quote, our new delimiter is a quote, not a space. */ | ||
1608 | if ((quote = strchr(start, '\''))) | ||
1609 | { | ||
1610 | start = quote + 1; | ||
1611 | end = strchr(start, '\''); | ||
1612 | } | ||
1613 | else | ||
1614 | { | ||
1615 | end = strchr(start, ' '); | ||
1616 | } | ||
1617 | |||
1618 | /* Null terminate before the spaces */ | ||
1619 | if (end) | ||
1620 | { | ||
1621 | *val = eina_stringshare_add_length(start, end - start); | ||
1622 | } | ||
1623 | else | ||
1624 | { | ||
1625 | *val = eina_stringshare_add(start); | ||
1626 | } | ||
1627 | } | ||
1628 | |||
1629 | /** | ||
1630 | * @internal | ||
1631 | * This function parses the format passed in *s and advances s to point to the | ||
1632 | * next format item, while returning the current one as the return value. | ||
1633 | * @param s The current and returned position in the format string. | ||
1634 | * @return the current item parsed from the string. | ||
1635 | */ | ||
1636 | static const char * | ||
1637 | _format_parse(const char **s) | ||
1638 | { | ||
1639 | const char *p; | ||
1640 | const char *s1 = NULL, *s2 = NULL; | ||
1641 | Eina_Bool quote = EINA_FALSE;; | ||
1642 | |||
1643 | p = *s; | ||
1644 | if (*p == 0) return NULL; | ||
1645 | for (;;) | ||
1646 | { | ||
1647 | if (!s1) | ||
1648 | { | ||
1649 | if (*p != ' ') s1 = p; | ||
1650 | if (*p == 0) break; | ||
1651 | } | ||
1652 | else if (!s2) | ||
1653 | { | ||
1654 | if (*p == '\'') | ||
1655 | { | ||
1656 | quote = !quote; | ||
1657 | } | ||
1658 | |||
1659 | if ((p > *s) && (p[-1] != '\\') && (!quote)) | ||
1660 | { | ||
1661 | if (*p == ' ') s2 = p; | ||
1662 | } | ||
1663 | if (*p == 0) s2 = p; | ||
1664 | } | ||
1665 | p++; | ||
1666 | if (s1 && s2) | ||
1667 | { | ||
1668 | *s = s2; | ||
1669 | return s1; | ||
1670 | } | ||
1671 | } | ||
1672 | *s = p; | ||
1673 | return NULL; | ||
1674 | } | ||
1675 | |||
1676 | /** | ||
1677 | * @internal | ||
1678 | * Parse the format str and populate fmt with the formats found. | ||
1679 | * | ||
1680 | * @param obj The evas object - Not NULL. | ||
1681 | * @param[out] fmt The format to populate - Not NULL. | ||
1682 | * @param[in] str the string to parse.- Not NULL. | ||
1683 | */ | ||
1684 | static void | ||
1685 | _format_fill(Evas_Object *obj, Evas_Object_Textblock_Format *fmt, const char *str) | ||
1686 | { | ||
1687 | const char *s; | ||
1688 | const char *item; | ||
1689 | |||
1690 | s = str; | ||
1691 | |||
1692 | /* get rid of anything +s or -s off the start of the string */ | ||
1693 | while ((*s == ' ') || (*s == '+') || (*s == '-')) s++; | ||
1694 | |||
1695 | while ((item = _format_parse(&s))) | ||
1696 | { | ||
1697 | if (_format_is_param(item)) | ||
1698 | { | ||
1699 | const char *key = NULL, *val = NULL; | ||
1700 | |||
1701 | _format_param_parse(item, &key, &val); | ||
1702 | _format_command(obj, fmt, key, val); | ||
1703 | eina_stringshare_del(key); | ||
1704 | eina_stringshare_del(val); | ||
1705 | } | ||
1706 | else | ||
1707 | { | ||
1708 | /* immediate - not handled here */ | ||
1709 | } | ||
1710 | } | ||
1711 | } | ||
1712 | |||
1713 | /** | ||
1714 | * @internal | ||
1715 | * Duplicate a format and return the duplicate. | ||
1716 | * | ||
1717 | * @param obj The evas object - Not NULL. | ||
1718 | * @param[in] fmt The format to duplicate - Not NULL. | ||
1719 | * @return the copy of the format. | ||
1720 | */ | ||
1721 | static Evas_Object_Textblock_Format * | ||
1722 | _format_dup(Evas_Object *obj, const Evas_Object_Textblock_Format *fmt) | ||
1723 | { | ||
1724 | Evas_Object_Textblock_Format *fmt2; | ||
1725 | |||
1726 | fmt2 = calloc(1, sizeof(Evas_Object_Textblock_Format)); | ||
1727 | memcpy(fmt2, fmt, sizeof(Evas_Object_Textblock_Format)); | ||
1728 | fmt2->ref = 1; | ||
1729 | fmt2->font.fdesc = evas_font_desc_ref(fmt->font.fdesc); | ||
1730 | |||
1731 | if (fmt->font.source) fmt2->font.source = eina_stringshare_add(fmt->font.source); | ||
1732 | |||
1733 | /* FIXME: just ref the font here... */ | ||
1734 | fmt2->font.font = evas_font_load(obj->layer->evas, fmt2->font.fdesc, | ||
1735 | fmt2->font.source, (int)(((double) fmt2->font.size) * obj->cur.scale)); | ||
1736 | return fmt2; | ||
1737 | } | ||
1738 | |||
1739 | |||
1740 | |||
1741 | |||
1742 | /** | ||
1743 | * @internal | ||
1744 | * @typedef Ctxt | ||
1745 | * | ||
1746 | * A pack of information that needed to be passed around in the layout engine, | ||
1747 | * packed for easier access. | ||
1748 | */ | ||
1749 | typedef struct _Ctxt Ctxt; | ||
1750 | |||
1751 | struct _Ctxt | ||
1752 | { | ||
1753 | Evas_Object *obj; | ||
1754 | Evas_Object_Textblock *o; | ||
1755 | |||
1756 | Evas_Object_Textblock_Paragraph *paragraphs; | ||
1757 | Evas_Object_Textblock_Paragraph *par; | ||
1758 | Evas_Object_Textblock_Line *ln; | ||
1759 | |||
1760 | |||
1761 | Eina_List *format_stack; | ||
1762 | Evas_Object_Textblock_Format *fmt; | ||
1763 | |||
1764 | int x, y; | ||
1765 | int w, h; | ||
1766 | int wmax, hmax; | ||
1767 | int maxascent, maxdescent; | ||
1768 | int marginl, marginr; | ||
1769 | int line_no; | ||
1770 | int underline_extend; | ||
1771 | int have_underline, have_underline2; | ||
1772 | double align, valign; | ||
1773 | Eina_Bool align_auto : 1; | ||
1774 | Eina_Bool width_changed : 1; | ||
1775 | }; | ||
1776 | |||
1777 | static void _layout_text_add_logical_item(Ctxt *c, Evas_Object_Textblock_Text_Item *ti, Eina_List *rel); | ||
1778 | static void _text_item_update_sizes(Ctxt *c, Evas_Object_Textblock_Text_Item *ti); | ||
1779 | static void _layout_do_format(const Evas_Object *obj, Ctxt *c, Evas_Object_Textblock_Format **_fmt, Evas_Object_Textblock_Node_Format *n, int *style_pad_l, int *style_pad_r, int *style_pad_t, int *style_pad_b, Eina_Bool create_item); | ||
1780 | /** | ||
1781 | * @internal | ||
1782 | * Adjust the ascent/descent of the format and context. | ||
1783 | * | ||
1784 | * @param maxascent The ascent to update - Not NUL. | ||
1785 | * @param maxdescent The descent to update - Not NUL. | ||
1786 | * @param fmt The format to adjust - NOT NULL. | ||
1787 | */ | ||
1788 | static void | ||
1789 | _layout_format_ascent_descent_adjust(const Evas_Object *obj, | ||
1790 | Evas_Coord *maxascent, Evas_Coord *maxdescent, | ||
1791 | Evas_Object_Textblock_Format *fmt) | ||
1792 | { | ||
1793 | int ascent, descent; | ||
1794 | |||
1795 | if (fmt->font.font) | ||
1796 | { | ||
1797 | // ascent = c->ENFN->font_max_ascent_get(c->ENDT, fmt->font.font); | ||
1798 | // descent = c->ENFN->font_max_descent_get(c->ENDT, fmt->font.font); | ||
1799 | ascent = ENFN->font_ascent_get(ENDT, fmt->font.font); | ||
1800 | descent = ENFN->font_descent_get(ENDT, fmt->font.font); | ||
1801 | if (fmt->linesize > 0) | ||
1802 | { | ||
1803 | if ((ascent + descent) < fmt->linesize) | ||
1804 | { | ||
1805 | ascent = ((fmt->linesize * ascent) / (ascent + descent)); | ||
1806 | descent = fmt->linesize - ascent; | ||
1807 | } | ||
1808 | } | ||
1809 | else if (fmt->linerelsize > 0.0) | ||
1810 | { | ||
1811 | descent = descent * fmt->linerelsize; | ||
1812 | ascent = ascent * fmt->linerelsize; | ||
1813 | } | ||
1814 | descent += fmt->linegap; | ||
1815 | descent += ((ascent + descent) * fmt->linerelgap); | ||
1816 | if (*maxascent < ascent) *maxascent = ascent; | ||
1817 | if (*maxdescent < descent) *maxdescent = descent; | ||
1818 | if (fmt->linefill > 0.0) | ||
1819 | { | ||
1820 | int dh; | ||
1821 | |||
1822 | dh = obj->cur.geometry.h - (*maxascent + *maxdescent); | ||
1823 | if (dh < 0) dh = 0; | ||
1824 | dh = fmt->linefill * dh; | ||
1825 | *maxdescent += dh / 2; | ||
1826 | *maxascent += dh - (dh / 2); | ||
1827 | // FIXME: set flag that says "if heigh changes - reformat" | ||
1828 | } | ||
1829 | } | ||
1830 | } | ||
1831 | |||
1832 | /** | ||
1833 | * @internal | ||
1834 | * Create a new line using the info from the format and update the format | ||
1835 | * and context. | ||
1836 | * | ||
1837 | * @param c The context to work on - Not NULL. | ||
1838 | * @param fmt The format to use info from - NOT NULL. | ||
1839 | */ | ||
1840 | static void | ||
1841 | _layout_line_new(Ctxt *c, Evas_Object_Textblock_Format *fmt) | ||
1842 | { | ||
1843 | c->ln = calloc(1, sizeof(Evas_Object_Textblock_Line)); | ||
1844 | c->align = fmt->halign; | ||
1845 | c->align_auto = fmt->halign_auto; | ||
1846 | c->marginl = fmt->margin.l; | ||
1847 | c->marginr = fmt->margin.r; | ||
1848 | c->par->lines = (Evas_Object_Textblock_Line *)eina_inlist_append(EINA_INLIST_GET(c->par->lines), EINA_INLIST_GET(c->ln)); | ||
1849 | c->x = 0; | ||
1850 | c->maxascent = c->maxdescent = 0; | ||
1851 | c->ln->line_no = -1; | ||
1852 | c->ln->par = c->par; | ||
1853 | } | ||
1854 | |||
1855 | static inline Evas_Object_Textblock_Paragraph * | ||
1856 | _layout_find_paragraph_by_y(Evas_Object_Textblock *o, Evas_Coord y) | ||
1857 | { | ||
1858 | Evas_Object_Textblock_Paragraph *start, *par; | ||
1859 | int i; | ||
1860 | |||
1861 | start = o->paragraphs; | ||
1862 | |||
1863 | for (i = 0 ; i < TEXTBLOCK_PAR_INDEX_SIZE ; i++) | ||
1864 | { | ||
1865 | if (!o->par_index[i] || (o->par_index[i]->y > y)) | ||
1866 | { | ||
1867 | break; | ||
1868 | } | ||
1869 | start = o->par_index[i]; | ||
1870 | } | ||
1871 | |||
1872 | EINA_INLIST_FOREACH(start, par) | ||
1873 | { | ||
1874 | if ((par->y <= y) && (y < par->y + par->h)) | ||
1875 | return par; | ||
1876 | } | ||
1877 | |||
1878 | return NULL; | ||
1879 | } | ||
1880 | |||
1881 | static inline Evas_Object_Textblock_Paragraph * | ||
1882 | _layout_find_paragraph_by_line_no(Evas_Object_Textblock *o, int line_no) | ||
1883 | { | ||
1884 | Evas_Object_Textblock_Paragraph *start, *par; | ||
1885 | int i; | ||
1886 | |||
1887 | start = o->paragraphs; | ||
1888 | |||
1889 | for (i = 0 ; i < TEXTBLOCK_PAR_INDEX_SIZE ; i++) | ||
1890 | { | ||
1891 | if (!o->par_index[i] || (o->par_index[i]->line_no > line_no)) | ||
1892 | { | ||
1893 | break; | ||
1894 | } | ||
1895 | start = o->par_index[i]; | ||
1896 | } | ||
1897 | |||
1898 | EINA_INLIST_FOREACH(start, par) | ||
1899 | { | ||
1900 | Evas_Object_Textblock_Paragraph *npar = | ||
1901 | (Evas_Object_Textblock_Paragraph *) EINA_INLIST_GET(par)->next; | ||
1902 | if ((par->line_no <= line_no) && | ||
1903 | (!npar || (line_no < npar->line_no))) | ||
1904 | return par; | ||
1905 | } | ||
1906 | |||
1907 | return NULL; | ||
1908 | } | ||
1909 | /* End of rbtree index functios */ | ||
1910 | |||
1911 | /** | ||
1912 | * @internal | ||
1913 | * Create a new layout paragraph. | ||
1914 | * If c->par is not NULL, the paragraph is appended/prepended according | ||
1915 | * to the append parameter. If it is NULL, the paragraph is appended at | ||
1916 | * the end of the list. | ||
1917 | * | ||
1918 | * @param c The context to work on - Not NULL. | ||
1919 | * @param n the associated text node | ||
1920 | * @param append true to append, false to prpend. | ||
1921 | */ | ||
1922 | static void | ||
1923 | _layout_paragraph_new(Ctxt *c, Evas_Object_Textblock_Node_Text *n, | ||
1924 | Eina_Bool append) | ||
1925 | { | ||
1926 | Evas_Object_Textblock_Paragraph *rel_par = c->par; | ||
1927 | c->par = calloc(1, sizeof(Evas_Object_Textblock_Paragraph)); | ||
1928 | if (append || !rel_par) | ||
1929 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
1930 | eina_inlist_append_relative(EINA_INLIST_GET(c->paragraphs), | ||
1931 | EINA_INLIST_GET(c->par), | ||
1932 | EINA_INLIST_GET(rel_par)); | ||
1933 | else | ||
1934 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
1935 | eina_inlist_prepend_relative(EINA_INLIST_GET(c->paragraphs), | ||
1936 | EINA_INLIST_GET(c->par), | ||
1937 | EINA_INLIST_GET(rel_par)); | ||
1938 | |||
1939 | c->ln = NULL; | ||
1940 | c->par->text_node = n; | ||
1941 | if (n) | ||
1942 | n->par = c->par; | ||
1943 | c->par->line_no = -1; | ||
1944 | c->par->visible = 1; | ||
1945 | c->o->num_paragraphs++; | ||
1946 | } | ||
1947 | |||
1948 | #ifdef BIDI_SUPPORT | ||
1949 | /** | ||
1950 | * @internal | ||
1951 | * Update bidi paragraph props. | ||
1952 | * | ||
1953 | * @param par The paragraph to update | ||
1954 | */ | ||
1955 | static inline void | ||
1956 | _layout_update_bidi_props(const Evas_Object_Textblock *o, | ||
1957 | Evas_Object_Textblock_Paragraph *par) | ||
1958 | { | ||
1959 | if (par->text_node) | ||
1960 | { | ||
1961 | const Eina_Unicode *text; | ||
1962 | int *segment_idxs = NULL; | ||
1963 | text = eina_ustrbuf_string_get(par->text_node->unicode); | ||
1964 | |||
1965 | if (o->bidi_delimiters) | ||
1966 | segment_idxs = evas_bidi_segment_idxs_get(text, o->bidi_delimiters); | ||
1967 | |||
1968 | evas_bidi_paragraph_props_unref(par->bidi_props); | ||
1969 | par->bidi_props = evas_bidi_paragraph_props_get(text, | ||
1970 | eina_ustrbuf_length_get(par->text_node->unicode), | ||
1971 | segment_idxs); | ||
1972 | par->direction = EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(par->bidi_props) ? | ||
1973 | EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR; | ||
1974 | par->is_bidi = !!par->bidi_props; | ||
1975 | if (segment_idxs) free(segment_idxs); | ||
1976 | } | ||
1977 | } | ||
1978 | #endif | ||
1979 | |||
1980 | |||
1981 | /** | ||
1982 | * @internal | ||
1983 | * Free the visual lines in the paragraph (logical items are kept) | ||
1984 | */ | ||
1985 | static void | ||
1986 | _paragraph_clear(const Evas_Object *obj __UNUSED__, | ||
1987 | Evas_Object_Textblock_Paragraph *par) | ||
1988 | { | ||
1989 | while (par->lines) | ||
1990 | { | ||
1991 | Evas_Object_Textblock_Line *ln; | ||
1992 | |||
1993 | ln = (Evas_Object_Textblock_Line *) par->lines; | ||
1994 | par->lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(par->lines), EINA_INLIST_GET(par->lines)); | ||
1995 | _line_free(ln); | ||
1996 | } | ||
1997 | } | ||
1998 | |||
1999 | /** | ||
2000 | * @internal | ||
2001 | * Free the layout paragraph and all of it's lines and logical items. | ||
2002 | */ | ||
2003 | static void | ||
2004 | _paragraph_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par) | ||
2005 | { | ||
2006 | Evas_Object_Textblock *o; | ||
2007 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
2008 | _paragraph_clear(obj, par); | ||
2009 | |||
2010 | { | ||
2011 | Eina_List *i, *i_prev; | ||
2012 | Evas_Object_Textblock_Item *it; | ||
2013 | EINA_LIST_FOREACH_SAFE(par->logical_items, i, i_prev, it) | ||
2014 | { | ||
2015 | _item_free(obj, NULL, it); | ||
2016 | } | ||
2017 | eina_list_free(par->logical_items); | ||
2018 | } | ||
2019 | #ifdef BIDI_SUPPORT | ||
2020 | if (par->bidi_props) | ||
2021 | evas_bidi_paragraph_props_unref(par->bidi_props); | ||
2022 | #endif | ||
2023 | /* If we are the active par of the text node, set to NULL */ | ||
2024 | if (par->text_node && (par->text_node->par == par)) | ||
2025 | par->text_node->par = NULL; | ||
2026 | |||
2027 | o->num_paragraphs--; | ||
2028 | |||
2029 | free(par); | ||
2030 | } | ||
2031 | |||
2032 | /** | ||
2033 | * @internal | ||
2034 | * Clear all the paragraphs from the inlist pars. | ||
2035 | * | ||
2036 | * @param obj the evas object - Not NULL. | ||
2037 | * @param pars the paragraphs to clean - Not NULL. | ||
2038 | */ | ||
2039 | static void | ||
2040 | _paragraphs_clear(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars) | ||
2041 | { | ||
2042 | Evas_Object_Textblock_Paragraph *par; | ||
2043 | |||
2044 | EINA_INLIST_FOREACH(EINA_INLIST_GET(pars), par) | ||
2045 | { | ||
2046 | _paragraph_clear(obj, par); | ||
2047 | } | ||
2048 | } | ||
2049 | |||
2050 | /** | ||
2051 | * @internal | ||
2052 | * Free the paragraphs from the inlist pars, the difference between this and | ||
2053 | * _paragraphs_clear is that the latter keeps the logical items and the par | ||
2054 | * items, while the former frees them as well. | ||
2055 | * | ||
2056 | * @param obj the evas object - Not NULL. | ||
2057 | * @param pars the paragraphs to clean - Not NULL. | ||
2058 | */ | ||
2059 | static void | ||
2060 | _paragraphs_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *pars) | ||
2061 | { | ||
2062 | Evas_Object_Textblock *o; | ||
2063 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
2064 | |||
2065 | o->num_paragraphs = 0; | ||
2066 | |||
2067 | while (pars) | ||
2068 | { | ||
2069 | Evas_Object_Textblock_Paragraph *par; | ||
2070 | |||
2071 | par = (Evas_Object_Textblock_Paragraph *) pars; | ||
2072 | pars = (Evas_Object_Textblock_Paragraph *)eina_inlist_remove(EINA_INLIST_GET(pars), EINA_INLIST_GET(par)); | ||
2073 | _paragraph_free(obj, par); | ||
2074 | } | ||
2075 | } | ||
2076 | |||
2077 | /** | ||
2078 | * @internal | ||
2079 | * Push fmt to the format stack, if fmt is NULL, will fush a default item. | ||
2080 | * | ||
2081 | * @param c the context to work on - Not NULL. | ||
2082 | * @param fmt the format to push. | ||
2083 | * @see _layout_format_pop() | ||
2084 | */ | ||
2085 | static Evas_Object_Textblock_Format * | ||
2086 | _layout_format_push(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
2087 | Evas_Object_Textblock_Node_Format *fnode) | ||
2088 | { | ||
2089 | if (fmt) | ||
2090 | { | ||
2091 | fmt = _format_dup(c->obj, fmt); | ||
2092 | c->format_stack = eina_list_prepend(c->format_stack, fmt); | ||
2093 | fmt->fnode = fnode; | ||
2094 | } | ||
2095 | else | ||
2096 | { | ||
2097 | fmt = calloc(1, sizeof(Evas_Object_Textblock_Format)); | ||
2098 | c->format_stack = eina_list_prepend(c->format_stack, fmt); | ||
2099 | fmt->ref = 1; | ||
2100 | fmt->halign = 0.0; | ||
2101 | fmt->halign_auto = EINA_TRUE; | ||
2102 | fmt->valign = -1.0; | ||
2103 | fmt->style = EVAS_TEXT_STYLE_PLAIN; | ||
2104 | fmt->tabstops = 32; | ||
2105 | fmt->linesize = 0; | ||
2106 | fmt->linerelsize = 0.0; | ||
2107 | fmt->linegap = 0; | ||
2108 | fmt->underline_dash_width = 6; | ||
2109 | fmt->underline_dash_gap = 2; | ||
2110 | fmt->linerelgap = 0.0; | ||
2111 | fmt->password = 1; | ||
2112 | } | ||
2113 | return fmt; | ||
2114 | } | ||
2115 | |||
2116 | /** | ||
2117 | * @internal | ||
2118 | * Pop fmt to the format stack, if there's something in the stack free fmt | ||
2119 | * and set it to point to the next item instead, else return fmt. | ||
2120 | * | ||
2121 | * @param c the context to work on - Not NULL. | ||
2122 | * @param format - the text of the format to free (assured to start with '-'). | ||
2123 | * @return the next format in the stack, or format if there's none. | ||
2124 | * @see _layout_format_push() | ||
2125 | */ | ||
2126 | static Evas_Object_Textblock_Format * | ||
2127 | _layout_format_pop(Ctxt *c, const char *format) | ||
2128 | { | ||
2129 | Evas_Object_Textblock_Format *fmt = eina_list_data_get(c->format_stack); | ||
2130 | |||
2131 | if ((c->format_stack) && (c->format_stack->next)) | ||
2132 | { | ||
2133 | Eina_List *redo_nodes = NULL; | ||
2134 | format++; /* Skip the '-' */ | ||
2135 | |||
2136 | /* Generic pop, should just pop. */ | ||
2137 | if (((format[0] == ' ') && !format[1]) || | ||
2138 | !format[0]) | ||
2139 | { | ||
2140 | _format_unref_free(c->obj, fmt); | ||
2141 | c->format_stack = | ||
2142 | eina_list_remove_list(c->format_stack, c->format_stack); | ||
2143 | } | ||
2144 | else | ||
2145 | { | ||
2146 | size_t len = strlen(format); | ||
2147 | Eina_List *i, *i_next; | ||
2148 | /* Remove only the matching format. */ | ||
2149 | EINA_LIST_FOREACH_SAFE(c->format_stack, i, i_next, fmt) | ||
2150 | { | ||
2151 | /* Stop when we reach the base item */ | ||
2152 | if (!i_next) | ||
2153 | break; | ||
2154 | |||
2155 | c->format_stack = | ||
2156 | eina_list_remove_list(c->format_stack, c->format_stack); | ||
2157 | |||
2158 | /* Make sure the ending tag matches the starting tag. | ||
2159 | * I.e whole of the ending tag matches the start of the | ||
2160 | * starting tag, and the starting tag's next char is either | ||
2161 | * NULL or white. Skip the starting '+'. */ | ||
2162 | if (_FORMAT_IS_CLOSER_OF( | ||
2163 | fmt->fnode->orig_format, format, len)) | ||
2164 | { | ||
2165 | _format_unref_free(c->obj, fmt); | ||
2166 | break; | ||
2167 | } | ||
2168 | else | ||
2169 | { | ||
2170 | redo_nodes = eina_list_prepend(redo_nodes, fmt->fnode); | ||
2171 | _format_unref_free(c->obj, fmt); | ||
2172 | } | ||
2173 | } | ||
2174 | } | ||
2175 | |||
2176 | /* Redo all the nodes needed to be redone */ | ||
2177 | { | ||
2178 | Evas_Object_Textblock_Node_Format *fnode; | ||
2179 | Eina_List *i, *i_next; | ||
2180 | |||
2181 | EINA_LIST_FOREACH_SAFE(redo_nodes, i, i_next, fnode) | ||
2182 | { | ||
2183 | /* FIXME: Actually do something with the new acquired padding, | ||
2184 | * the can be different and affect our padding! */ | ||
2185 | Evas_Coord style_pad_l, style_pad_r, style_pad_t, style_pad_b; | ||
2186 | style_pad_l = style_pad_r = style_pad_t = style_pad_b = 0; | ||
2187 | redo_nodes = eina_list_remove_list(redo_nodes, i); | ||
2188 | fmt = eina_list_data_get(c->format_stack); | ||
2189 | _layout_do_format(c->obj, c, &fmt, fnode, | ||
2190 | &style_pad_l, &style_pad_r, | ||
2191 | &style_pad_t, &style_pad_b, EINA_FALSE); | ||
2192 | } | ||
2193 | } | ||
2194 | |||
2195 | fmt = eina_list_data_get(c->format_stack); | ||
2196 | } | ||
2197 | return fmt; | ||
2198 | } | ||
2199 | |||
2200 | /** | ||
2201 | * @internal | ||
2202 | * Parse item and fill fmt with the item. | ||
2203 | * | ||
2204 | * @param c the context to work on - Not NULL. | ||
2205 | * @param fmt the format to fill - not null. | ||
2206 | */ | ||
2207 | static void | ||
2208 | _layout_format_value_handle(Ctxt *c, Evas_Object_Textblock_Format *fmt, const char *item) | ||
2209 | { | ||
2210 | const char *key = NULL, *val = NULL; | ||
2211 | |||
2212 | _format_param_parse(item, &key, &val); | ||
2213 | if ((key) && (val)) _format_command(c->obj, fmt, key, val); | ||
2214 | if (key) eina_stringshare_del(key); | ||
2215 | if (val) eina_stringshare_del(val); | ||
2216 | c->align = fmt->halign; | ||
2217 | c->align_auto = fmt->halign_auto; | ||
2218 | c->marginl = fmt->margin.l; | ||
2219 | c->marginr = fmt->margin.r; | ||
2220 | } | ||
2221 | |||
2222 | #define VSIZE_FULL 0 | ||
2223 | #define VSIZE_ASCENT 1 | ||
2224 | |||
2225 | #define SIZE 0 | ||
2226 | #define SIZE_ABS 1 | ||
2227 | #define SIZE_REL 2 | ||
2228 | |||
2229 | /** | ||
2230 | * @internal | ||
2231 | * Get the current line's alignment from the context. | ||
2232 | * | ||
2233 | * @param c the context to work on - Not NULL. | ||
2234 | */ | ||
2235 | static inline double | ||
2236 | _layout_line_align_get(Ctxt *c) | ||
2237 | { | ||
2238 | #ifdef BIDI_SUPPORT | ||
2239 | if (c->align_auto && c->ln) | ||
2240 | { | ||
2241 | if (c->ln->items && c->ln->items->text_node && | ||
2242 | (c->ln->par->direction == EVAS_BIDI_DIRECTION_RTL)) | ||
2243 | { | ||
2244 | /* Align right*/ | ||
2245 | return 1.0; | ||
2246 | } | ||
2247 | else | ||
2248 | { | ||
2249 | /* Align left */ | ||
2250 | return 0.0; | ||
2251 | } | ||
2252 | } | ||
2253 | #endif | ||
2254 | return c->align; | ||
2255 | } | ||
2256 | |||
2257 | #ifdef BIDI_SUPPORT | ||
2258 | /** | ||
2259 | * @internal | ||
2260 | * Reorder the items in visual order | ||
2261 | * | ||
2262 | * @param line the line to reorder | ||
2263 | */ | ||
2264 | static void | ||
2265 | _layout_line_reorder(Evas_Object_Textblock_Line *line) | ||
2266 | { | ||
2267 | /*FIXME: do it a bit more efficient - not very efficient ATM. */ | ||
2268 | Evas_Object_Textblock_Item *it; | ||
2269 | EvasBiDiStrIndex *v_to_l = NULL; | ||
2270 | Evas_Coord x; | ||
2271 | size_t start, end; | ||
2272 | size_t len; | ||
2273 | |||
2274 | if (line->items && line->items->text_node && | ||
2275 | line->par->bidi_props) | ||
2276 | { | ||
2277 | Evas_BiDi_Paragraph_Props *props; | ||
2278 | props = line->par->bidi_props; | ||
2279 | start = end = line->items->text_pos; | ||
2280 | |||
2281 | /* Find the first and last positions in the line */ | ||
2282 | |||
2283 | EINA_INLIST_FOREACH(line->items, it) | ||
2284 | { | ||
2285 | if (it->text_pos < start) | ||
2286 | { | ||
2287 | start = it->text_pos; | ||
2288 | } | ||
2289 | else | ||
2290 | { | ||
2291 | int tlen; | ||
2292 | tlen = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? | ||
2293 | _ITEM_TEXT(it)->text_props.text_len : 1; | ||
2294 | if (it->text_pos + tlen > end) | ||
2295 | { | ||
2296 | end = it->text_pos + tlen; | ||
2297 | } | ||
2298 | } | ||
2299 | } | ||
2300 | |||
2301 | len = end - start; | ||
2302 | evas_bidi_props_reorder_line(NULL, start, len, props, &v_to_l); | ||
2303 | |||
2304 | /* Update visual pos */ | ||
2305 | { | ||
2306 | Evas_Object_Textblock_Item *i; | ||
2307 | i = line->items; | ||
2308 | while (i) | ||
2309 | { | ||
2310 | i->visual_pos = evas_bidi_position_logical_to_visual( | ||
2311 | v_to_l, len, i->text_pos - start); | ||
2312 | i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(i)->next; | ||
2313 | } | ||
2314 | } | ||
2315 | |||
2316 | /*FIXME: not very efficient, sort the items arrays. Anyhow, should only | ||
2317 | * reorder if it's a bidi paragraph */ | ||
2318 | { | ||
2319 | Evas_Object_Textblock_Item *i, *j, *min; | ||
2320 | i = line->items; | ||
2321 | while (i) | ||
2322 | { | ||
2323 | min = i; | ||
2324 | EINA_INLIST_FOREACH(i, j) | ||
2325 | { | ||
2326 | if (j->visual_pos < min->visual_pos) | ||
2327 | { | ||
2328 | min = j; | ||
2329 | } | ||
2330 | } | ||
2331 | if (min != i) | ||
2332 | { | ||
2333 | line->items = (Evas_Object_Textblock_Item *) eina_inlist_remove(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min)); | ||
2334 | line->items = (Evas_Object_Textblock_Item *) eina_inlist_prepend_relative(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min), EINA_INLIST_GET(i)); | ||
2335 | } | ||
2336 | |||
2337 | i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(min)->next; | ||
2338 | } | ||
2339 | } | ||
2340 | } | ||
2341 | |||
2342 | if (v_to_l) free(v_to_l); | ||
2343 | x = 0; | ||
2344 | EINA_INLIST_FOREACH(line->items, it) | ||
2345 | { | ||
2346 | it->x = x; | ||
2347 | x += it->adv; | ||
2348 | } | ||
2349 | } | ||
2350 | #endif | ||
2351 | |||
2352 | /* FIXME: doc */ | ||
2353 | static void | ||
2354 | _layout_calculate_format_item_size(const Evas_Object *obj, | ||
2355 | const Evas_Object_Textblock_Format_Item *fi, | ||
2356 | Evas_Coord *maxascent, Evas_Coord *maxdescent, | ||
2357 | Evas_Coord *_y, Evas_Coord *_w, Evas_Coord *_h) | ||
2358 | { | ||
2359 | /* Adjust sizes according to current line height/scale */ | ||
2360 | Evas_Coord w, h; | ||
2361 | const char *p, *s; | ||
2362 | |||
2363 | s = fi->item; | ||
2364 | w = fi->parent.w; | ||
2365 | h = fi->parent.h; | ||
2366 | switch (fi->size) | ||
2367 | { | ||
2368 | case SIZE: | ||
2369 | p = strstr(s, " size="); | ||
2370 | if (p) | ||
2371 | { | ||
2372 | p += 6; | ||
2373 | if (sscanf(p, "%ix%i", &w, &h) == 2) | ||
2374 | { | ||
2375 | w = w * obj->cur.scale; | ||
2376 | h = h * obj->cur.scale; | ||
2377 | } | ||
2378 | } | ||
2379 | break; | ||
2380 | case SIZE_REL: | ||
2381 | p = strstr((char *) s, " relsize="); | ||
2382 | p += 9; | ||
2383 | if (sscanf(p, "%ix%i", &w, &h) == 2) | ||
2384 | { | ||
2385 | int sz = 1; | ||
2386 | if (fi->vsize == VSIZE_FULL) | ||
2387 | { | ||
2388 | sz = *maxdescent + *maxascent; | ||
2389 | } | ||
2390 | else if (fi->vsize == VSIZE_ASCENT) | ||
2391 | { | ||
2392 | sz = *maxascent; | ||
2393 | } | ||
2394 | w = (w * sz) / h; | ||
2395 | h = sz; | ||
2396 | } | ||
2397 | break; | ||
2398 | case SIZE_ABS: | ||
2399 | /* Nothing to do */ | ||
2400 | default: | ||
2401 | break; | ||
2402 | } | ||
2403 | |||
2404 | switch (fi->size) | ||
2405 | { | ||
2406 | case SIZE: | ||
2407 | case SIZE_ABS: | ||
2408 | switch (fi->vsize) | ||
2409 | { | ||
2410 | case VSIZE_FULL: | ||
2411 | if (h > (*maxdescent + *maxascent)) | ||
2412 | { | ||
2413 | *maxascent += h - (*maxdescent + *maxascent); | ||
2414 | *_y = -*maxascent; | ||
2415 | } | ||
2416 | else | ||
2417 | *_y = -(h - *maxdescent); | ||
2418 | break; | ||
2419 | case VSIZE_ASCENT: | ||
2420 | if (h > *maxascent) | ||
2421 | { | ||
2422 | *maxascent = h; | ||
2423 | *_y = -h; | ||
2424 | } | ||
2425 | else | ||
2426 | *_y = -h; | ||
2427 | break; | ||
2428 | default: | ||
2429 | break; | ||
2430 | } | ||
2431 | break; | ||
2432 | case SIZE_REL: | ||
2433 | switch (fi->vsize) | ||
2434 | { | ||
2435 | case VSIZE_FULL: | ||
2436 | case VSIZE_ASCENT: | ||
2437 | *_y = -*maxascent; | ||
2438 | break; | ||
2439 | default: | ||
2440 | break; | ||
2441 | } | ||
2442 | break; | ||
2443 | default: | ||
2444 | break; | ||
2445 | } | ||
2446 | |||
2447 | *_w = w; | ||
2448 | *_h = h; | ||
2449 | } | ||
2450 | |||
2451 | /** | ||
2452 | * @internal | ||
2453 | * Order the items in the line, update it's properties and update it's | ||
2454 | * corresponding paragraph. | ||
2455 | * | ||
2456 | * @param c the context to work on - Not NULL. | ||
2457 | * @param fmt the format to use. | ||
2458 | * @param add_line true if we should create a line, false otherwise. | ||
2459 | */ | ||
2460 | static void | ||
2461 | _layout_line_finalize(Ctxt *c, Evas_Object_Textblock_Format *fmt) | ||
2462 | { | ||
2463 | Evas_Object_Textblock_Item *it; | ||
2464 | Evas_Coord x = 0; | ||
2465 | |||
2466 | /* If there are no text items yet, calc ascent/descent | ||
2467 | * according to the current format. */ | ||
2468 | if (c->maxascent + c->maxdescent == 0) | ||
2469 | _layout_format_ascent_descent_adjust(c->obj, &c->maxascent, | ||
2470 | &c->maxdescent, fmt); | ||
2471 | |||
2472 | /* Adjust all the item sizes according to the final line size, | ||
2473 | * and update the x positions of all the items of the line. */ | ||
2474 | EINA_INLIST_FOREACH(c->ln->items, it) | ||
2475 | { | ||
2476 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
2477 | { | ||
2478 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
2479 | if (!fi->formatme) goto loop_advance; | ||
2480 | _layout_calculate_format_item_size(c->obj, fi, &c->maxascent, | ||
2481 | &c->maxdescent, &fi->y, &fi->parent.w, &fi->parent.h); | ||
2482 | fi->parent.adv = fi->parent.w; | ||
2483 | } | ||
2484 | |||
2485 | loop_advance: | ||
2486 | it->x = x; | ||
2487 | x += it->adv; | ||
2488 | |||
2489 | if ((it->x + it->adv) > c->ln->w) c->ln->w = it->x + it->adv; | ||
2490 | } | ||
2491 | |||
2492 | c->ln->y = (c->y - c->par->y) + c->o->style_pad.t; | ||
2493 | c->ln->h = c->maxascent + c->maxdescent; | ||
2494 | c->ln->baseline = c->maxascent; | ||
2495 | if (c->have_underline2) | ||
2496 | { | ||
2497 | if (c->maxdescent < 4) c->underline_extend = 4 - c->maxdescent; | ||
2498 | } | ||
2499 | else if (c->have_underline) | ||
2500 | { | ||
2501 | if (c->maxdescent < 2) c->underline_extend = 2 - c->maxdescent; | ||
2502 | } | ||
2503 | c->ln->line_no = c->line_no - c->ln->par->line_no; | ||
2504 | c->line_no++; | ||
2505 | c->y += c->maxascent + c->maxdescent; | ||
2506 | if (c->w >= 0) | ||
2507 | { | ||
2508 | c->ln->x = c->marginl + c->o->style_pad.l + | ||
2509 | ((c->w - c->ln->w - | ||
2510 | c->o->style_pad.l - c->o->style_pad.r - | ||
2511 | c->marginl - c->marginr) * _layout_line_align_get(c)); | ||
2512 | } | ||
2513 | else | ||
2514 | { | ||
2515 | c->ln->x = c->marginl + c->o->style_pad.l; | ||
2516 | } | ||
2517 | |||
2518 | c->par->h = c->ln->y + c->ln->h; | ||
2519 | if (c->ln->w > c->par->w) | ||
2520 | c->par->w = c->ln->w; | ||
2521 | |||
2522 | { | ||
2523 | Evas_Coord new_wmax = c->ln->w + | ||
2524 | c->marginl + c->marginr - (c->o->style_pad.l + c->o->style_pad.r); | ||
2525 | if (new_wmax > c->wmax) | ||
2526 | c->wmax = new_wmax; | ||
2527 | } | ||
2528 | } | ||
2529 | |||
2530 | /** | ||
2531 | * @internal | ||
2532 | * Create a new line and append it to the lines in the context. | ||
2533 | * | ||
2534 | * @param c the context to work on - Not NULL. | ||
2535 | * @param fmt the format to use. | ||
2536 | * @param add_line true if we should create a line, false otherwise. | ||
2537 | */ | ||
2538 | static void | ||
2539 | _layout_line_advance(Ctxt *c, Evas_Object_Textblock_Format *fmt) | ||
2540 | { | ||
2541 | _layout_line_finalize(c, fmt); | ||
2542 | _layout_line_new(c, fmt); | ||
2543 | } | ||
2544 | |||
2545 | /** | ||
2546 | * @internal | ||
2547 | * Create a new text layout item from the string and the format. | ||
2548 | * | ||
2549 | * @param c the context to work on - Not NULL. | ||
2550 | * @param fmt the format to use. | ||
2551 | * @param str the string to use. | ||
2552 | * @param len the length of the string. | ||
2553 | */ | ||
2554 | static Evas_Object_Textblock_Text_Item * | ||
2555 | _layout_text_item_new(Ctxt *c __UNUSED__, Evas_Object_Textblock_Format *fmt) | ||
2556 | { | ||
2557 | Evas_Object_Textblock_Text_Item *ti; | ||
2558 | |||
2559 | ti = calloc(1, sizeof(Evas_Object_Textblock_Text_Item)); | ||
2560 | ti->parent.format = fmt; | ||
2561 | ti->parent.format->ref++; | ||
2562 | ti->parent.type = EVAS_TEXTBLOCK_ITEM_TEXT; | ||
2563 | return ti; | ||
2564 | } | ||
2565 | |||
2566 | /** | ||
2567 | * @internal | ||
2568 | * Return the cutoff of the text in the text item. | ||
2569 | * | ||
2570 | * @param c the context to work on - Not NULL. | ||
2571 | * @param fmt the format to use. - Not NULL. | ||
2572 | * @param it the item to check - Not null. | ||
2573 | * @return -1 if there is no cutoff (either because there is really none, | ||
2574 | * or because of an error), cutoff index on success. | ||
2575 | */ | ||
2576 | static int | ||
2577 | _layout_text_cutoff_get(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
2578 | const Evas_Object_Textblock_Text_Item *ti) | ||
2579 | { | ||
2580 | if (fmt->font.font) | ||
2581 | { | ||
2582 | Evas_Coord x; | ||
2583 | x = c->w - c->o->style_pad.l - c->o->style_pad.r - c->marginl - | ||
2584 | c->marginr - c->x - ti->x_adjustment; | ||
2585 | if (x < 0) | ||
2586 | x = 0; | ||
2587 | return c->ENFN->font_last_up_to_pos(c->ENDT, fmt->font.font, | ||
2588 | &ti->text_props, x, 0); | ||
2589 | } | ||
2590 | return -1; | ||
2591 | } | ||
2592 | |||
2593 | /** | ||
2594 | * @internal | ||
2595 | * Split before cut, and strip if str[cut - 1] is a whitespace. | ||
2596 | * | ||
2597 | * @param c the context to work on - Not NULL. | ||
2598 | * @param ti the item to cut - not null. | ||
2599 | * @param lti the logical list item of the item. | ||
2600 | * @param cut the cut index. | ||
2601 | * @return the second (newly created) item. | ||
2602 | */ | ||
2603 | static Evas_Object_Textblock_Text_Item * | ||
2604 | _layout_item_text_split_strip_white(Ctxt *c, | ||
2605 | Evas_Object_Textblock_Text_Item *ti, Eina_List *lti, size_t cut) | ||
2606 | { | ||
2607 | const Eina_Unicode *ts; | ||
2608 | Evas_Object_Textblock_Text_Item *new_ti = NULL, *white_ti = NULL; | ||
2609 | |||
2610 | ts = GET_ITEM_TEXT(ti); | ||
2611 | |||
2612 | if (!IS_AT_END(ti, cut) && (ti->text_props.text_len > 0)) | ||
2613 | { | ||
2614 | new_ti = _layout_text_item_new(c, ti->parent.format); | ||
2615 | new_ti->parent.text_node = ti->parent.text_node; | ||
2616 | new_ti->parent.text_pos = ti->parent.text_pos + cut; | ||
2617 | new_ti->parent.merge = EINA_TRUE; | ||
2618 | |||
2619 | evas_common_text_props_split(&ti->text_props, | ||
2620 | &new_ti->text_props, cut); | ||
2621 | _layout_text_add_logical_item(c, new_ti, lti); | ||
2622 | } | ||
2623 | |||
2624 | /* Strip the previous white if needed */ | ||
2625 | if ((cut >= 1) && _is_white(ts[cut - 1]) && (ti->text_props.text_len > 0)) | ||
2626 | { | ||
2627 | if (cut - 1 > 0) | ||
2628 | { | ||
2629 | size_t white_cut = cut - 1; | ||
2630 | white_ti = _layout_text_item_new(c, ti->parent.format); | ||
2631 | white_ti->parent.text_node = ti->parent.text_node; | ||
2632 | white_ti->parent.text_pos = ti->parent.text_pos + white_cut; | ||
2633 | white_ti->parent.merge = EINA_TRUE; | ||
2634 | white_ti->parent.visually_deleted = EINA_TRUE; | ||
2635 | |||
2636 | evas_common_text_props_split(&ti->text_props, | ||
2637 | &white_ti->text_props, white_cut); | ||
2638 | _layout_text_add_logical_item(c, white_ti, lti); | ||
2639 | } | ||
2640 | else | ||
2641 | { | ||
2642 | /* Mark this one as the visually deleted. */ | ||
2643 | ti->parent.visually_deleted = EINA_TRUE; | ||
2644 | } | ||
2645 | } | ||
2646 | |||
2647 | if (new_ti || white_ti) | ||
2648 | { | ||
2649 | _text_item_update_sizes(c, ti); | ||
2650 | } | ||
2651 | return new_ti; | ||
2652 | } | ||
2653 | |||
2654 | /** | ||
2655 | * @internal | ||
2656 | * Merge item2 into item1 and free item2. | ||
2657 | * | ||
2658 | * @param c the context to work on - Not NULL. | ||
2659 | * @param item1 the item to copy to | ||
2660 | * @param item2 the item to copy from | ||
2661 | */ | ||
2662 | static void | ||
2663 | _layout_item_merge_and_free(Ctxt *c, | ||
2664 | Evas_Object_Textblock_Text_Item *item1, | ||
2665 | Evas_Object_Textblock_Text_Item *item2) | ||
2666 | { | ||
2667 | evas_common_text_props_merge(&item1->text_props, | ||
2668 | &item2->text_props); | ||
2669 | |||
2670 | _text_item_update_sizes(c, item1); | ||
2671 | |||
2672 | item1->parent.merge = EINA_FALSE; | ||
2673 | item1->parent.visually_deleted = EINA_FALSE; | ||
2674 | |||
2675 | _item_free(c->obj, NULL, _ITEM(item2)); | ||
2676 | } | ||
2677 | |||
2678 | /** | ||
2679 | * @internal | ||
2680 | * Calculates an item's size. | ||
2681 | * | ||
2682 | * @param c the context | ||
2683 | * @param it the item itself. | ||
2684 | */ | ||
2685 | static void | ||
2686 | _text_item_update_sizes(Ctxt *c, Evas_Object_Textblock_Text_Item *ti) | ||
2687 | { | ||
2688 | int tw, th, inset, advw; | ||
2689 | const Evas_Object_Textblock_Format *fmt = ti->parent.format; | ||
2690 | int shad_sz = 0, shad_dst = 0, out_sz = 0; | ||
2691 | int dx = 0, minx = 0, maxx = 0, shx1, shx2; | ||
2692 | |||
2693 | tw = th = 0; | ||
2694 | if (fmt->font.font) | ||
2695 | c->ENFN->font_string_size_get(c->ENDT, fmt->font.font, | ||
2696 | &ti->text_props, &tw, &th); | ||
2697 | inset = 0; | ||
2698 | if (fmt->font.font) | ||
2699 | inset = c->ENFN->font_inset_get(c->ENDT, fmt->font.font, | ||
2700 | &ti->text_props); | ||
2701 | advw = 0; | ||
2702 | if (fmt->font.font) | ||
2703 | advw = c->ENFN->font_h_advance_get(c->ENDT, fmt->font.font, | ||
2704 | &ti->text_props); | ||
2705 | |||
2706 | |||
2707 | /* These adjustments are calculated and thus heavily linked to those in | ||
2708 | * textblock_render!!! Don't change one without the other. */ | ||
2709 | |||
2710 | switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_BASIC) | ||
2711 | { | ||
2712 | case EVAS_TEXT_STYLE_SHADOW: | ||
2713 | shad_dst = 1; | ||
2714 | break; | ||
2715 | case EVAS_TEXT_STYLE_OUTLINE_SHADOW: | ||
2716 | case EVAS_TEXT_STYLE_FAR_SHADOW: | ||
2717 | shad_dst = 2; | ||
2718 | out_sz = 1; | ||
2719 | break; | ||
2720 | case EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW: | ||
2721 | shad_dst = 1; | ||
2722 | shad_sz = 2; | ||
2723 | out_sz = 1; | ||
2724 | break; | ||
2725 | case EVAS_TEXT_STYLE_FAR_SOFT_SHADOW: | ||
2726 | shad_dst = 2; | ||
2727 | shad_sz = 2; | ||
2728 | break; | ||
2729 | case EVAS_TEXT_STYLE_SOFT_SHADOW: | ||
2730 | shad_dst = 1; | ||
2731 | shad_sz = 2; | ||
2732 | break; | ||
2733 | case EVAS_TEXT_STYLE_GLOW: | ||
2734 | case EVAS_TEXT_STYLE_SOFT_OUTLINE: | ||
2735 | out_sz = 2; | ||
2736 | break; | ||
2737 | case EVAS_TEXT_STYLE_OUTLINE: | ||
2738 | out_sz = 1; | ||
2739 | break; | ||
2740 | default: | ||
2741 | break; | ||
2742 | } | ||
2743 | switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_SHADOW_DIRECTION) | ||
2744 | { | ||
2745 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT: | ||
2746 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT: | ||
2747 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT: | ||
2748 | dx = -1; | ||
2749 | break; | ||
2750 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT: | ||
2751 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT: | ||
2752 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT: | ||
2753 | dx = 1; | ||
2754 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP: | ||
2755 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM: | ||
2756 | default: | ||
2757 | dx = 0; | ||
2758 | break; | ||
2759 | } | ||
2760 | minx = -out_sz; | ||
2761 | maxx = out_sz; | ||
2762 | shx1 = dx * shad_dst; | ||
2763 | shx1 -= shad_sz; | ||
2764 | shx2 = dx * shad_dst; | ||
2765 | shx2 += shad_sz; | ||
2766 | if (shx1 < minx) minx = shx1; | ||
2767 | if (shx2 > maxx) maxx = shx2; | ||
2768 | inset += -minx; | ||
2769 | ti->x_adjustment = maxx - minx; | ||
2770 | |||
2771 | ti->inset = inset; | ||
2772 | ti->parent.w = tw + ti->x_adjustment; | ||
2773 | ti->parent.h = th; | ||
2774 | ti->parent.adv = advw; | ||
2775 | ti->parent.x = 0; | ||
2776 | } | ||
2777 | |||
2778 | /** | ||
2779 | * @internal | ||
2780 | * Adds the item to the list, updates the item's properties (e.g, x,w,h) | ||
2781 | * | ||
2782 | * @param c the context | ||
2783 | * @param it the item itself. | ||
2784 | * @param rel item ti will be appened after, NULL = last. | ||
2785 | */ | ||
2786 | static void | ||
2787 | _layout_text_add_logical_item(Ctxt *c, Evas_Object_Textblock_Text_Item *ti, | ||
2788 | Eina_List *rel) | ||
2789 | { | ||
2790 | _text_item_update_sizes(c, ti); | ||
2791 | |||
2792 | c->par->logical_items = eina_list_append_relative_list( | ||
2793 | c->par->logical_items, ti, rel); | ||
2794 | } | ||
2795 | |||
2796 | /** | ||
2797 | * @internal | ||
2798 | * Appends the text from node n starting at start ending at off to the layout. | ||
2799 | * It uses the fmt for the formatting. | ||
2800 | * | ||
2801 | * @param c the current context- NOT NULL. | ||
2802 | * @param fmt the format to use. | ||
2803 | * @param n the text node. - Not null. | ||
2804 | * @param start the start position. - in range. | ||
2805 | * @param off the offset - start + offset in range. if offset is -1, it'll add everything to the end of the string if offset = 0 it'll return with doing nothing. | ||
2806 | * @param repch a replacement char to print instead of the original string, for example, * when working with passwords. | ||
2807 | */ | ||
2808 | static void | ||
2809 | _layout_text_append(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Node_Text *n, int start, int off, const char *repch) | ||
2810 | { | ||
2811 | const Eina_Unicode *str = EINA_UNICODE_EMPTY_STRING; | ||
2812 | const Eina_Unicode *tbase; | ||
2813 | Evas_Object_Textblock_Text_Item *ti; | ||
2814 | size_t cur_len = 0; | ||
2815 | Eina_Unicode urepch = 0; | ||
2816 | |||
2817 | /* prepare a working copy of the string, either filled by the repch or | ||
2818 | * filled with the true values */ | ||
2819 | if (n) | ||
2820 | { | ||
2821 | int len; | ||
2822 | int orig_off = off; | ||
2823 | |||
2824 | /* Figure out if we want to bail, work with an empty string, | ||
2825 | * or continue with a slice of the passed string */ | ||
2826 | len = eina_ustrbuf_length_get(n->unicode); | ||
2827 | if (off == 0) return; | ||
2828 | else if (off < 0) off = len - start; | ||
2829 | |||
2830 | if (start < 0) | ||
2831 | { | ||
2832 | start = 0; | ||
2833 | } | ||
2834 | else if ((start == 0) && (off == 0) && (orig_off == -1)) | ||
2835 | { | ||
2836 | /* Special case that means that we need to add an empty | ||
2837 | * item */ | ||
2838 | str = EINA_UNICODE_EMPTY_STRING; | ||
2839 | goto skip; | ||
2840 | } | ||
2841 | else if ((start >= len) || (start + off > len)) | ||
2842 | { | ||
2843 | return; | ||
2844 | } | ||
2845 | |||
2846 | /* If we work with a replacement char, create a string which is the same | ||
2847 | * but with replacement chars instead of regular chars. */ | ||
2848 | if ((fmt->password) && (repch) && (eina_ustrbuf_length_get(n->unicode))) | ||
2849 | { | ||
2850 | int i, ind; | ||
2851 | Eina_Unicode *ptr; | ||
2852 | |||
2853 | tbase = str = ptr = alloca((off + 1) * sizeof(Eina_Unicode)); | ||
2854 | ind = 0; | ||
2855 | urepch = eina_unicode_utf8_get_next(repch, &ind); | ||
2856 | for (i = 0 ; i < off; ptr++, i++) | ||
2857 | *ptr = urepch; | ||
2858 | *ptr = 0; | ||
2859 | } | ||
2860 | /* Use the string, just cut the relevant parts */ | ||
2861 | else | ||
2862 | { | ||
2863 | str = eina_ustrbuf_string_get(n->unicode) + start; | ||
2864 | } | ||
2865 | |||
2866 | cur_len = off; | ||
2867 | } | ||
2868 | |||
2869 | skip: | ||
2870 | tbase = str; | ||
2871 | |||
2872 | /* If there's no parent text node, only create an empty item */ | ||
2873 | if (!n) | ||
2874 | { | ||
2875 | ti = _layout_text_item_new(c, fmt); | ||
2876 | ti->parent.text_node = NULL; | ||
2877 | ti->parent.text_pos = 0; | ||
2878 | _layout_text_add_logical_item(c, ti, NULL); | ||
2879 | |||
2880 | return; | ||
2881 | } | ||
2882 | |||
2883 | while (cur_len > 0) | ||
2884 | { | ||
2885 | Evas_Font_Instance *script_fi = NULL; | ||
2886 | int script_len, tmp_cut; | ||
2887 | Evas_Script_Type script; | ||
2888 | |||
2889 | script_len = cur_len; | ||
2890 | |||
2891 | tmp_cut = evas_common_language_script_end_of_run_get(str, | ||
2892 | c->par->bidi_props, start + str - tbase, script_len); | ||
2893 | if (tmp_cut > 0) | ||
2894 | { | ||
2895 | script_len = tmp_cut; | ||
2896 | } | ||
2897 | cur_len -= script_len; | ||
2898 | |||
2899 | script = evas_common_language_script_type_get(str, script_len); | ||
2900 | |||
2901 | |||
2902 | while (script_len > 0) | ||
2903 | { | ||
2904 | Evas_Font_Instance *cur_fi = NULL; | ||
2905 | int run_len = script_len; | ||
2906 | ti = _layout_text_item_new(c, fmt); | ||
2907 | ti->parent.text_node = n; | ||
2908 | ti->parent.text_pos = start + str - tbase; | ||
2909 | |||
2910 | if (ti->parent.format->font.font) | ||
2911 | { | ||
2912 | run_len = c->ENFN->font_run_end_get(c->ENDT, | ||
2913 | ti->parent.format->font.font, &script_fi, &cur_fi, | ||
2914 | script, str, script_len); | ||
2915 | } | ||
2916 | |||
2917 | evas_common_text_props_bidi_set(&ti->text_props, | ||
2918 | c->par->bidi_props, ti->parent.text_pos); | ||
2919 | evas_common_text_props_script_set(&ti->text_props, script); | ||
2920 | |||
2921 | if (cur_fi) | ||
2922 | { | ||
2923 | c->ENFN->font_text_props_info_create(c->ENDT, | ||
2924 | cur_fi, str, &ti->text_props, c->par->bidi_props, | ||
2925 | ti->parent.text_pos, run_len); | ||
2926 | } | ||
2927 | str += run_len; | ||
2928 | script_len -= run_len; | ||
2929 | |||
2930 | _layout_text_add_logical_item(c, ti, NULL); | ||
2931 | } | ||
2932 | } | ||
2933 | } | ||
2934 | |||
2935 | /** | ||
2936 | * @internal | ||
2937 | * Add a format item from the format node n and the item item. | ||
2938 | * | ||
2939 | * @param c the current context- NOT NULL. | ||
2940 | * @param n the source format node - not null. | ||
2941 | * @param item the format text. | ||
2942 | * | ||
2943 | * @return the new format item. | ||
2944 | */ | ||
2945 | static Evas_Object_Textblock_Format_Item * | ||
2946 | _layout_format_item_add(Ctxt *c, Evas_Object_Textblock_Node_Format *n, const char *item, Evas_Object_Textblock_Format *fmt) | ||
2947 | { | ||
2948 | Evas_Object_Textblock_Format_Item *fi; | ||
2949 | |||
2950 | fi = calloc(1, sizeof(Evas_Object_Textblock_Format_Item)); | ||
2951 | fi->item = eina_stringshare_add(item); | ||
2952 | fi->parent.type = EVAS_TEXTBLOCK_ITEM_FORMAT; | ||
2953 | fi->parent.format = fmt; | ||
2954 | fi->parent.format->ref++; | ||
2955 | c->par->logical_items = eina_list_append(c->par->logical_items, fi); | ||
2956 | if (n) | ||
2957 | { | ||
2958 | fi->parent.text_node = n->text_node; | ||
2959 | /* FIXME: make it more efficient */ | ||
2960 | fi->parent.text_pos = _evas_textblock_node_format_pos_get(n); | ||
2961 | #ifdef BIDI_SUPPORT | ||
2962 | fi->bidi_dir = (evas_bidi_is_rtl_char( | ||
2963 | c->par->bidi_props, | ||
2964 | 0, | ||
2965 | fi->parent.text_pos)) ? | ||
2966 | EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR; | ||
2967 | #else | ||
2968 | fi->bidi_dir = EVAS_BIDI_DIRECTION_LTR; | ||
2969 | #endif | ||
2970 | } | ||
2971 | return fi; | ||
2972 | } | ||
2973 | |||
2974 | /** | ||
2975 | * @internal | ||
2976 | * Should be call after we finish filling a format. | ||
2977 | * FIXME: doc. | ||
2978 | */ | ||
2979 | static void | ||
2980 | _format_finalize(Evas_Object *obj, Evas_Object_Textblock_Format *fmt) | ||
2981 | { | ||
2982 | void *of; | ||
2983 | |||
2984 | of = fmt->font.font; | ||
2985 | |||
2986 | fmt->font.font = evas_font_load(obj->layer->evas, fmt->font.fdesc, | ||
2987 | fmt->font.source, (int)(((double) fmt->font.size) * obj->cur.scale)); | ||
2988 | if (of) evas_font_free(obj->layer->evas, of); | ||
2989 | } | ||
2990 | |||
2991 | /** | ||
2992 | * @internal | ||
2993 | * Returns true if the item is a tab | ||
2994 | * @def _IS_TAB(item) | ||
2995 | */ | ||
2996 | #define _IS_TAB(item) \ | ||
2997 | (!strcmp(item, "\t") || !strcmp(item, "\\t")) | ||
2998 | /** | ||
2999 | * @internal | ||
3000 | * Returns true if the item is a line spearator, false otherwise | ||
3001 | * @def _IS_LINE_SEPARATOR(item) | ||
3002 | */ | ||
3003 | #define _IS_LINE_SEPARATOR(item) \ | ||
3004 | (!strcmp(item, "\n") || !strcmp(item, "\\n")) | ||
3005 | /** | ||
3006 | * @internal | ||
3007 | * Returns true if the item is a paragraph separator, false otherwise | ||
3008 | * @def _IS_PARAGRAPH_SEPARATOR(item) | ||
3009 | */ | ||
3010 | #define _IS_PARAGRAPH_SEPARATOR(o, item) \ | ||
3011 | (!strcmp(item, "ps") || \ | ||
3012 | (o->legacy_newline && _IS_LINE_SEPARATOR(item))) /* Paragraph separator */ | ||
3013 | |||
3014 | /** | ||
3015 | * @internal | ||
3016 | * Handles a format by processing a format node. It returns the relevant format | ||
3017 | * through _fmt and updates the padding through style_pad_*. If needed, | ||
3018 | * it creates a format item. | ||
3019 | * | ||
3020 | * @param obj the evas object - NOT NULL. | ||
3021 | * @param c the current context- NOT NULL. | ||
3022 | * @param _fmt the format that holds the result. | ||
3023 | * @param n the source format node - not null. | ||
3024 | * @param style_pad_l the pad to update. | ||
3025 | * @param style_pad_r the pad to update. | ||
3026 | * @param style_pad_t the pad to update. | ||
3027 | * @param style_pad_b the pad to update. | ||
3028 | * @param create_item Create a new format item if true, only process otherwise. | ||
3029 | */ | ||
3030 | static void | ||
3031 | _layout_do_format(const Evas_Object *obj __UNUSED__, Ctxt *c, | ||
3032 | Evas_Object_Textblock_Format **_fmt, Evas_Object_Textblock_Node_Format *n, | ||
3033 | int *style_pad_l, int *style_pad_r, int *style_pad_t, int *style_pad_b, | ||
3034 | Eina_Bool create_item) | ||
3035 | { | ||
3036 | Evas_Object_Textblock_Format *fmt = *_fmt; | ||
3037 | /* FIXME: comment the algo */ | ||
3038 | |||
3039 | const char *s; | ||
3040 | const char *item; | ||
3041 | int handled = 0; | ||
3042 | |||
3043 | s = n->format; | ||
3044 | if (!strncmp(s, "+ item ", 7)) | ||
3045 | { | ||
3046 | // one of: | ||
3047 | // item size=20x10 href=name | ||
3048 | // item relsize=20x10 href=name | ||
3049 | // item abssize=20x10 href=name | ||
3050 | // | ||
3051 | // optional arguments: | ||
3052 | // vsize=full | ||
3053 | // vsize=ascent | ||
3054 | // | ||
3055 | // size == item size (modifies line size) - can be multiplied by | ||
3056 | // scale factor | ||
3057 | // relsize == relative size (height is current font height, width | ||
3058 | // modified accordingly keeping aspect) | ||
3059 | // abssize == absolute size (modifies line size) - never mulitplied by | ||
3060 | // scale factor | ||
3061 | // href == name of item - to be found and matched later and used for | ||
3062 | // positioning | ||
3063 | Evas_Object_Textblock_Format_Item *fi; | ||
3064 | int w = 1, h = 1; | ||
3065 | int vsize = 0, size = 0; | ||
3066 | char *p; | ||
3067 | |||
3068 | // don't care | ||
3069 | //href = strstr(s, " href="); | ||
3070 | p = strstr(s, " vsize="); | ||
3071 | if (p) | ||
3072 | { | ||
3073 | p += 7; | ||
3074 | if (!strncmp(p, "full", 4)) vsize = VSIZE_FULL; | ||
3075 | else if (!strncmp(p, "ascent", 6)) vsize = VSIZE_ASCENT; | ||
3076 | } | ||
3077 | p = strstr(s, " size="); | ||
3078 | if (p) | ||
3079 | { | ||
3080 | p += 6; | ||
3081 | if (sscanf(p, "%ix%i", &w, &h) == 2) | ||
3082 | { | ||
3083 | /* this is handled somewhere else because it depends | ||
3084 | * on the current scaling factor of the object which | ||
3085 | * may change and break because the results of this | ||
3086 | * function are cached */ | ||
3087 | size = SIZE; | ||
3088 | } | ||
3089 | } | ||
3090 | else | ||
3091 | { | ||
3092 | p = strstr(s, " absize="); | ||
3093 | if (p) | ||
3094 | { | ||
3095 | p += 8; | ||
3096 | if (sscanf(p, "%ix%i", &w, &h) == 2) | ||
3097 | { | ||
3098 | size = SIZE_ABS; | ||
3099 | } | ||
3100 | } | ||
3101 | else | ||
3102 | { | ||
3103 | p = strstr(s, " relsize="); | ||
3104 | if (p) | ||
3105 | { | ||
3106 | /* this is handled somewhere else because it depends | ||
3107 | * on the line it resides in, which is not defined | ||
3108 | * at this point and will change anyway, which will | ||
3109 | * break because the results of this function are | ||
3110 | * cached */ | ||
3111 | size = SIZE_REL; | ||
3112 | } | ||
3113 | } | ||
3114 | } | ||
3115 | |||
3116 | if (create_item) | ||
3117 | { | ||
3118 | fi = _layout_format_item_add(c, n, s, fmt); | ||
3119 | fi->vsize = vsize; | ||
3120 | fi->size = size; | ||
3121 | fi->formatme = 1; | ||
3122 | /* For formats items it's usually | ||
3123 | the same, we don't handle the | ||
3124 | special cases yet. */ | ||
3125 | fi->parent.w = fi->parent.adv = w; | ||
3126 | fi->parent.h = h; | ||
3127 | } | ||
3128 | /* Not sure if it's the best handling, but will do it for now. */ | ||
3129 | fmt = _layout_format_push(c, fmt, n); | ||
3130 | handled = 1; | ||
3131 | } | ||
3132 | |||
3133 | if (!handled) | ||
3134 | { | ||
3135 | Eina_Bool push_fmt = EINA_FALSE; | ||
3136 | if (s[0] == '+') | ||
3137 | { | ||
3138 | fmt = _layout_format_push(c, fmt, n); | ||
3139 | s++; | ||
3140 | push_fmt = EINA_TRUE; | ||
3141 | } | ||
3142 | else if (s[0] == '-') | ||
3143 | { | ||
3144 | fmt = _layout_format_pop(c, n->orig_format); | ||
3145 | s++; | ||
3146 | } | ||
3147 | while ((item = _format_parse(&s))) | ||
3148 | { | ||
3149 | if (_format_is_param(item)) | ||
3150 | { | ||
3151 | /* Only handle it if it's a push format, otherwise, | ||
3152 | * don't let overwrite the format stack.. */ | ||
3153 | if (push_fmt) | ||
3154 | { | ||
3155 | _layout_format_value_handle(c, fmt, item); | ||
3156 | } | ||
3157 | } | ||
3158 | else if (create_item) | ||
3159 | { | ||
3160 | if ((_IS_PARAGRAPH_SEPARATOR(c->o, item)) || | ||
3161 | (_IS_LINE_SEPARATOR(item))) | ||
3162 | { | ||
3163 | Evas_Object_Textblock_Format_Item *fi; | ||
3164 | |||
3165 | fi = _layout_format_item_add(c, n, item, fmt); | ||
3166 | |||
3167 | fi->parent.w = fi->parent.adv = 0; | ||
3168 | } | ||
3169 | else if ((!strcmp(item, "\t")) || (!strcmp(item, "\\t"))) | ||
3170 | { | ||
3171 | Evas_Object_Textblock_Format_Item *fi; | ||
3172 | |||
3173 | fi = _layout_format_item_add(c, n, item, fmt); | ||
3174 | fi->parent.w = fi->parent.adv = fmt->tabstops; | ||
3175 | fi->formatme = 1; | ||
3176 | } | ||
3177 | } | ||
3178 | } | ||
3179 | _format_finalize(c->obj, fmt); | ||
3180 | } | ||
3181 | |||
3182 | { | ||
3183 | Evas_Coord pad_l, pad_r, pad_t, pad_b; | ||
3184 | pad_l = pad_r = pad_t = pad_b = 0; | ||
3185 | evas_text_style_pad_get(fmt->style, &pad_l, &pad_r, &pad_t, &pad_b); | ||
3186 | if (pad_l > *style_pad_l) *style_pad_l = pad_l; | ||
3187 | if (pad_r > *style_pad_r) *style_pad_r = pad_r; | ||
3188 | if (pad_t > *style_pad_t) *style_pad_t = pad_t; | ||
3189 | if (pad_b > *style_pad_b) *style_pad_b = pad_b; | ||
3190 | } | ||
3191 | |||
3192 | if (fmt->underline2) | ||
3193 | c->have_underline2 = 1; | ||
3194 | else if (fmt->underline || fmt->underline_dash) | ||
3195 | c->have_underline = 1; | ||
3196 | *_fmt = fmt; | ||
3197 | } | ||
3198 | |||
3199 | static void | ||
3200 | _layout_update_par(Ctxt *c) | ||
3201 | { | ||
3202 | Evas_Object_Textblock_Paragraph *last_par; | ||
3203 | last_par = (Evas_Object_Textblock_Paragraph *) | ||
3204 | EINA_INLIST_GET(c->par)->prev; | ||
3205 | if (last_par) | ||
3206 | { | ||
3207 | c->par->y = last_par->y + last_par->h; | ||
3208 | } | ||
3209 | else | ||
3210 | { | ||
3211 | c->par->y = 0; | ||
3212 | } | ||
3213 | } | ||
3214 | |||
3215 | /* -1 means no wrap */ | ||
3216 | static int | ||
3217 | _layout_get_charwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
3218 | const Evas_Object_Textblock_Text_Item *ti, size_t line_start, | ||
3219 | const char *breaks) | ||
3220 | { | ||
3221 | int wrap; | ||
3222 | size_t uwrap; | ||
3223 | size_t len = eina_ustrbuf_length_get(ti->parent.text_node->unicode); | ||
3224 | /* Currently not being used, because it doesn't contain relevant | ||
3225 | * information */ | ||
3226 | (void) breaks; | ||
3227 | |||
3228 | { | ||
3229 | wrap = _layout_text_cutoff_get(c, fmt, ti); | ||
3230 | if (wrap < 0) | ||
3231 | return -1; | ||
3232 | uwrap = (size_t) wrap + ti->parent.text_pos; | ||
3233 | } | ||
3234 | |||
3235 | |||
3236 | if (uwrap == line_start) | ||
3237 | { | ||
3238 | uwrap = ti->parent.text_pos + | ||
3239 | (size_t) evas_common_text_props_cluster_next(&ti->text_props, wrap); | ||
3240 | } | ||
3241 | if ((uwrap <= line_start) || (uwrap > len)) | ||
3242 | return -1; | ||
3243 | |||
3244 | return uwrap; | ||
3245 | } | ||
3246 | |||
3247 | /* -1 means no wrap */ | ||
3248 | #ifdef HAVE_LINEBREAK | ||
3249 | |||
3250 | /* Allow break means: if we can break after the current char */ | ||
3251 | #define ALLOW_BREAK(i) \ | ||
3252 | (breaks[i] <= LINEBREAK_ALLOWBREAK) | ||
3253 | |||
3254 | #else | ||
3255 | |||
3256 | #define ALLOW_BREAK(i) \ | ||
3257 | (_is_white(str[i])) | ||
3258 | |||
3259 | #endif | ||
3260 | static int | ||
3261 | _layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
3262 | const Evas_Object_Textblock_Text_Item *ti, Eina_Bool mixed_wrap, | ||
3263 | size_t line_start, const char *breaks) | ||
3264 | { | ||
3265 | Eina_Bool wrap_after = EINA_FALSE; | ||
3266 | size_t wrap; | ||
3267 | size_t orig_wrap; | ||
3268 | const Eina_Unicode *str = eina_ustrbuf_string_get( | ||
3269 | ti->parent.text_node->unicode); | ||
3270 | int item_start = ti->parent.text_pos; | ||
3271 | size_t len = eina_ustrbuf_length_get(ti->parent.text_node->unicode); | ||
3272 | #ifndef HAVE_LINEBREAK | ||
3273 | /* Not used without liblinebreak ATM. */ | ||
3274 | (void) breaks; | ||
3275 | #endif | ||
3276 | |||
3277 | { | ||
3278 | int swrap = -1; | ||
3279 | swrap = _layout_text_cutoff_get(c, fmt, ti); | ||
3280 | /* Avoiding too small textblocks to even contain one char. | ||
3281 | * FIXME: This can cause breaking inside ligatures. */ | ||
3282 | |||
3283 | if (swrap < 0) | ||
3284 | return -1; | ||
3285 | |||
3286 | orig_wrap = wrap = swrap + item_start; | ||
3287 | } | ||
3288 | |||
3289 | if (wrap > line_start) | ||
3290 | { | ||
3291 | /* The wrapping point found is the first char of the next string | ||
3292 | the rest works on the last char of the previous string. | ||
3293 | If it's a whitespace, then it's ok, and no need to go back | ||
3294 | because we'll remove it anyway. */ | ||
3295 | if (!_is_white(str[wrap])) | ||
3296 | MOVE_PREV_UNTIL(line_start, wrap); | ||
3297 | /* If there's a breakable point inside the text, scan backwards until | ||
3298 | * we find it */ | ||
3299 | while (wrap > line_start) | ||
3300 | { | ||
3301 | if (ALLOW_BREAK(wrap)) | ||
3302 | break; | ||
3303 | wrap--; | ||
3304 | } | ||
3305 | |||
3306 | if ((wrap > line_start) || | ||
3307 | ((wrap == line_start) && (ALLOW_BREAK(wrap)) && (wrap < len))) | ||
3308 | { | ||
3309 | /* We found a suitable wrapping point, break here. */ | ||
3310 | MOVE_NEXT_UNTIL(len, wrap); | ||
3311 | return wrap; | ||
3312 | } | ||
3313 | else | ||
3314 | { | ||
3315 | if (mixed_wrap) | ||
3316 | { | ||
3317 | return ((orig_wrap >= line_start) && (orig_wrap < len)) ? | ||
3318 | ((int) orig_wrap) : -1; | ||
3319 | } | ||
3320 | else | ||
3321 | { | ||
3322 | /* Scan forward to find the next wrapping point */ | ||
3323 | wrap = orig_wrap; | ||
3324 | wrap_after = EINA_TRUE; | ||
3325 | } | ||
3326 | } | ||
3327 | } | ||
3328 | |||
3329 | /* If we need to find the position after the cutting point */ | ||
3330 | if ((wrap == line_start) || (wrap_after)) | ||
3331 | { | ||
3332 | if (mixed_wrap) | ||
3333 | { | ||
3334 | return _layout_get_charwrap(c, fmt, ti, | ||
3335 | line_start, breaks); | ||
3336 | } | ||
3337 | else | ||
3338 | { | ||
3339 | while (wrap < len) | ||
3340 | { | ||
3341 | if (ALLOW_BREAK(wrap)) | ||
3342 | break; | ||
3343 | wrap++; | ||
3344 | } | ||
3345 | |||
3346 | |||
3347 | if ((wrap < len) && (wrap > line_start)) | ||
3348 | { | ||
3349 | MOVE_NEXT_UNTIL(len, wrap); | ||
3350 | return wrap; | ||
3351 | } | ||
3352 | else | ||
3353 | { | ||
3354 | return -1; | ||
3355 | } | ||
3356 | } | ||
3357 | } | ||
3358 | |||
3359 | return -1; | ||
3360 | } | ||
3361 | |||
3362 | /* -1 means no wrap */ | ||
3363 | static int | ||
3364 | _layout_get_wordwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
3365 | const Evas_Object_Textblock_Text_Item *ti, size_t line_start, | ||
3366 | const char *breaks) | ||
3367 | { | ||
3368 | return _layout_get_word_mixwrap_common(c, fmt, ti, EINA_FALSE, line_start, | ||
3369 | breaks); | ||
3370 | } | ||
3371 | |||
3372 | /* -1 means no wrap */ | ||
3373 | static int | ||
3374 | _layout_get_mixedwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, | ||
3375 | const Evas_Object_Textblock_Text_Item *ti, size_t line_start, | ||
3376 | const char *breaks) | ||
3377 | { | ||
3378 | return _layout_get_word_mixwrap_common(c, fmt, ti, EINA_TRUE, line_start, | ||
3379 | breaks); | ||
3380 | } | ||
3381 | |||
3382 | /* Should be moved inside _layout_ellipsis_item_new once we fix the hack in | ||
3383 | * textblock render */ | ||
3384 | static const Eina_Unicode _ellip_str[2] = { 0x2026, '\0' }; | ||
3385 | |||
3386 | static Evas_Object_Textblock_Text_Item * | ||
3387 | _layout_ellipsis_item_new(Ctxt *c, const Evas_Object_Textblock_Item *cur_it) | ||
3388 | { | ||
3389 | Evas_Object_Textblock_Text_Item *ellip_ti; | ||
3390 | Evas_Script_Type script; | ||
3391 | Evas_Font_Instance *script_fi = NULL, *cur_fi; | ||
3392 | size_t len = 1; /* The length of _ellip_str */ | ||
3393 | |||
3394 | /* We can free it here, cause there's only one ellipsis item per tb. */ | ||
3395 | if (c->o->ellip_ti) _item_free(c->obj, NULL, _ITEM(c->o->ellip_ti)); | ||
3396 | c->o->ellip_ti = ellip_ti = _layout_text_item_new(c, | ||
3397 | eina_list_data_get(eina_list_last(c->format_stack))); | ||
3398 | ellip_ti->parent.text_node = cur_it->text_node; | ||
3399 | ellip_ti->parent.text_pos = cur_it->text_pos; | ||
3400 | script = evas_common_language_script_type_get(_ellip_str, len); | ||
3401 | |||
3402 | evas_common_text_props_bidi_set(&ellip_ti->text_props, | ||
3403 | c->par->bidi_props, ellip_ti->parent.text_pos); | ||
3404 | evas_common_text_props_script_set (&ellip_ti->text_props, script); | ||
3405 | |||
3406 | if (ellip_ti->parent.format->font.font) | ||
3407 | { | ||
3408 | /* It's only 1 char anyway, we don't need the run end. */ | ||
3409 | (void) c->ENFN->font_run_end_get(c->ENDT, | ||
3410 | ellip_ti->parent.format->font.font, &script_fi, &cur_fi, | ||
3411 | script, _ellip_str, len); | ||
3412 | |||
3413 | c->ENFN->font_text_props_info_create(c->ENDT, | ||
3414 | cur_fi, _ellip_str, &ellip_ti->text_props, | ||
3415 | c->par->bidi_props, ellip_ti->parent.text_pos, len); | ||
3416 | } | ||
3417 | |||
3418 | _text_item_update_sizes(c, ellip_ti); | ||
3419 | |||
3420 | if (cur_it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
3421 | { | ||
3422 | ellip_ti->parent.text_pos += _ITEM_TEXT(cur_it)->text_props.text_len | ||
3423 | - 1; | ||
3424 | } | ||
3425 | else | ||
3426 | { | ||
3427 | ellip_ti->parent.text_pos++; | ||
3428 | } | ||
3429 | |||
3430 | return ellip_ti; | ||
3431 | } | ||
3432 | |||
3433 | /** | ||
3434 | * @internel | ||
3435 | * Handle ellipsis | ||
3436 | */ | ||
3437 | static inline void | ||
3438 | _layout_handle_ellipsis(Ctxt *c, Evas_Object_Textblock_Item *it, Eina_List *i) | ||
3439 | { | ||
3440 | Evas_Object_Textblock_Text_Item *ellip_ti; | ||
3441 | Evas_Object_Textblock_Item *last_it; | ||
3442 | Evas_Coord save_cx; | ||
3443 | int wrap; | ||
3444 | ellip_ti = _layout_ellipsis_item_new(c, it); | ||
3445 | last_it = it; | ||
3446 | |||
3447 | save_cx = c->x; | ||
3448 | c->w -= ellip_ti->parent.w; | ||
3449 | |||
3450 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
3451 | { | ||
3452 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
3453 | |||
3454 | wrap = _layout_text_cutoff_get(c, last_it->format, ti); | ||
3455 | if ((wrap > 0) && !IS_AT_END(ti, (size_t) wrap)) | ||
3456 | { | ||
3457 | _layout_item_text_split_strip_white(c, ti, i, wrap); | ||
3458 | } | ||
3459 | else if ((wrap == 0) && (c->ln->items)) | ||
3460 | { | ||
3461 | last_it = _ITEM(EINA_INLIST_GET(c->ln->items)->last); | ||
3462 | } | ||
3463 | } | ||
3464 | else if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3465 | { | ||
3466 | /* We don't want to add this format item. */ | ||
3467 | last_it = NULL; | ||
3468 | } | ||
3469 | |||
3470 | c->x = save_cx; | ||
3471 | c->w += ellip_ti->parent.w; | ||
3472 | /* If we should add this item, do it */ | ||
3473 | if (last_it == it) | ||
3474 | { | ||
3475 | c->ln->items = (Evas_Object_Textblock_Item *) | ||
3476 | eina_inlist_append(EINA_INLIST_GET(c->ln->items), | ||
3477 | EINA_INLIST_GET(it)); | ||
3478 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3479 | { | ||
3480 | Evas_Object_Textblock_Format_Item *fi; | ||
3481 | fi = _ITEM_FORMAT(it); | ||
3482 | fi->y = c->y; | ||
3483 | } | ||
3484 | } | ||
3485 | c->ln->items = (Evas_Object_Textblock_Item *) | ||
3486 | eina_inlist_append(EINA_INLIST_GET(c->ln->items), | ||
3487 | EINA_INLIST_GET(_ITEM(ellip_ti))); | ||
3488 | _layout_line_finalize(c, ellip_ti->parent.format); | ||
3489 | } | ||
3490 | |||
3491 | #ifdef BIDI_SUPPORT | ||
3492 | static void | ||
3493 | _layout_paragraph_reorder_lines(Evas_Object_Textblock_Paragraph *par) | ||
3494 | { | ||
3495 | Evas_Object_Textblock_Line *ln; | ||
3496 | |||
3497 | EINA_INLIST_FOREACH(EINA_INLIST_GET(par->lines), ln) | ||
3498 | { | ||
3499 | _layout_line_reorder(ln); | ||
3500 | } | ||
3501 | } | ||
3502 | #endif | ||
3503 | |||
3504 | static void | ||
3505 | _layout_paragraph_render(Evas_Object_Textblock *o, | ||
3506 | Evas_Object_Textblock_Paragraph *par) | ||
3507 | { | ||
3508 | if (par->rendered) | ||
3509 | return; | ||
3510 | par->rendered = EINA_TRUE; | ||
3511 | |||
3512 | #ifdef BIDI_SUPPORT | ||
3513 | if (par->is_bidi) | ||
3514 | { | ||
3515 | _layout_update_bidi_props(o, par); | ||
3516 | _layout_paragraph_reorder_lines(par); | ||
3517 | /* Clear the bidi props because we don't need them anymore. */ | ||
3518 | if (par->bidi_props) | ||
3519 | { | ||
3520 | evas_bidi_paragraph_props_unref(par->bidi_props); | ||
3521 | par->bidi_props = NULL; | ||
3522 | } | ||
3523 | } | ||
3524 | #endif | ||
3525 | } | ||
3526 | |||
3527 | /* 0 means go ahead, 1 means break without an error, 2 means | ||
3528 | * break with an error, should probably clean this a bit (enum/macro) | ||
3529 | * FIXME ^ */ | ||
3530 | static int | ||
3531 | _layout_par(Ctxt *c) | ||
3532 | { | ||
3533 | Evas_Object_Textblock_Item *it; | ||
3534 | Eina_List *i; | ||
3535 | int ret = 0; | ||
3536 | int wrap = -1; | ||
3537 | char *line_breaks = NULL; | ||
3538 | |||
3539 | if (!c->par->logical_items) | ||
3540 | return 2; | ||
3541 | |||
3542 | /* We want to show it. */ | ||
3543 | c->par->visible = 1; | ||
3544 | |||
3545 | /* Check if we need to skip this paragraph because it's already layouted | ||
3546 | * correctly, and mark handled nodes as dirty. */ | ||
3547 | c->par->line_no = c->line_no; | ||
3548 | |||
3549 | if (c->par->text_node) | ||
3550 | { | ||
3551 | /* Skip this paragraph if width is the same, there is no ellipsis | ||
3552 | * and we aren't just calculating. */ | ||
3553 | if (!c->par->text_node->is_new && !c->par->text_node->dirty && | ||
3554 | !c->width_changed && c->par->lines && | ||
3555 | !c->o->have_ellipsis) | ||
3556 | { | ||
3557 | Evas_Object_Textblock_Line *ln; | ||
3558 | /* Update c->line_no */ | ||
3559 | ln = (Evas_Object_Textblock_Line *) | ||
3560 | EINA_INLIST_GET(c->par->lines)->last; | ||
3561 | if (ln) | ||
3562 | c->line_no = c->par->line_no + ln->line_no + 1; | ||
3563 | return 0; | ||
3564 | } | ||
3565 | c->par->text_node->dirty = EINA_FALSE; | ||
3566 | c->par->text_node->is_new = EINA_FALSE; | ||
3567 | c->par->rendered = EINA_FALSE; | ||
3568 | |||
3569 | /* Merge back and clear the paragraph */ | ||
3570 | { | ||
3571 | Eina_List *itr, *itr_next; | ||
3572 | Evas_Object_Textblock_Item *ititr, *prev_it = NULL; | ||
3573 | _paragraph_clear(c->obj, c->par); | ||
3574 | EINA_LIST_FOREACH_SAFE(c->par->logical_items, itr, itr_next, ititr) | ||
3575 | { | ||
3576 | if (ititr->merge && prev_it && | ||
3577 | (prev_it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
3578 | (ititr->type == EVAS_TEXTBLOCK_ITEM_TEXT)) | ||
3579 | { | ||
3580 | _layout_item_merge_and_free(c, _ITEM_TEXT(prev_it), | ||
3581 | _ITEM_TEXT(ititr)); | ||
3582 | c->par->logical_items = | ||
3583 | eina_list_remove_list(c->par->logical_items, itr); | ||
3584 | } | ||
3585 | else | ||
3586 | { | ||
3587 | prev_it = ititr; | ||
3588 | } | ||
3589 | } | ||
3590 | } | ||
3591 | } | ||
3592 | |||
3593 | c->y = c->par->y; | ||
3594 | |||
3595 | it = _ITEM(eina_list_data_get(c->par->logical_items)); | ||
3596 | _layout_line_new(c, it->format); | ||
3597 | /* We walk on our own because we want to be able to add items from | ||
3598 | * inside the list and then walk them on the next iteration. */ | ||
3599 | for (i = c->par->logical_items ; i ; ) | ||
3600 | { | ||
3601 | int adv_line = 0; | ||
3602 | int redo_item = 0; | ||
3603 | it = _ITEM(eina_list_data_get(i)); | ||
3604 | /* Skip visually deleted items */ | ||
3605 | if (it->visually_deleted) | ||
3606 | { | ||
3607 | i = eina_list_next(i); | ||
3608 | continue; | ||
3609 | } | ||
3610 | |||
3611 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
3612 | { | ||
3613 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
3614 | _layout_format_ascent_descent_adjust(c->obj, &c->maxascent, | ||
3615 | &c->maxdescent, ti->parent.format); | ||
3616 | } | ||
3617 | else | ||
3618 | { | ||
3619 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
3620 | if (fi->formatme) | ||
3621 | { | ||
3622 | /* If there are no text items yet, calc ascent/descent | ||
3623 | * according to the current format. */ | ||
3624 | if (c->maxascent + c->maxdescent == 0) | ||
3625 | _layout_format_ascent_descent_adjust(c->obj, &c->maxascent, | ||
3626 | &c->maxdescent, it->format); | ||
3627 | |||
3628 | _layout_calculate_format_item_size(c->obj, fi, &c->maxascent, | ||
3629 | &c->maxdescent, &fi->y, &fi->parent.w, &fi->parent.h); | ||
3630 | fi->parent.adv = fi->parent.w; | ||
3631 | } | ||
3632 | } | ||
3633 | |||
3634 | |||
3635 | /* Check if we need to wrap, i.e the text is bigger than the width, | ||
3636 | or we already found a wrap point. */ | ||
3637 | if ((c->w >= 0) && | ||
3638 | (((c->x + it->adv) > | ||
3639 | (c->w - c->o->style_pad.l - c->o->style_pad.r - | ||
3640 | c->marginl - c->marginr)) || (wrap > 0))) | ||
3641 | { | ||
3642 | /* Handle ellipsis here. If we don't have more width left | ||
3643 | * and no height left, or no more width left and no wrapping. */ | ||
3644 | if ((it->format->ellipsis == 1.0) && (c->h >= 0) && | ||
3645 | ((2 * it->h + c->y > | ||
3646 | c->h - c->o->style_pad.t - c->o->style_pad.b) || | ||
3647 | (!it->format->wrap_word && !it->format->wrap_char && | ||
3648 | !it->format->wrap_mixed))) | ||
3649 | { | ||
3650 | _layout_handle_ellipsis(c, it, i); | ||
3651 | ret = 1; | ||
3652 | goto end; | ||
3653 | } | ||
3654 | /* If we want to wrap and it's worth checking for wrapping | ||
3655 | * (i.e there's actually text). */ | ||
3656 | else if ((it->format->wrap_word || it->format->wrap_char || | ||
3657 | it->format->wrap_mixed) && it->text_node) | ||
3658 | { | ||
3659 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3660 | { | ||
3661 | /* Don't wrap if it's the only item */ | ||
3662 | if (c->ln->items) | ||
3663 | { | ||
3664 | /*FIXME: I should handle format correctly, | ||
3665 | i.e verify we are allowed to break here */ | ||
3666 | _layout_line_advance(c, it->format); | ||
3667 | wrap = -1; | ||
3668 | } | ||
3669 | } | ||
3670 | else | ||
3671 | { | ||
3672 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
3673 | size_t line_start; | ||
3674 | |||
3675 | #ifdef HAVE_LINEBREAK | ||
3676 | /* If we haven't calculated the linebreaks yet, | ||
3677 | * do */ | ||
3678 | if (!line_breaks) | ||
3679 | { | ||
3680 | /* Only relevant in those cases */ | ||
3681 | if (it->format->wrap_word || it->format->wrap_mixed) | ||
3682 | { | ||
3683 | const char *lang; | ||
3684 | lang = (it->format->font.fdesc) ? | ||
3685 | it->format->font.fdesc->lang : ""; | ||
3686 | size_t len = | ||
3687 | eina_ustrbuf_length_get( | ||
3688 | it->text_node->unicode); | ||
3689 | line_breaks = malloc(len); | ||
3690 | set_linebreaks_utf32((const utf32_t *) | ||
3691 | eina_ustrbuf_string_get( | ||
3692 | it->text_node->unicode), | ||
3693 | len, lang, line_breaks); | ||
3694 | } | ||
3695 | } | ||
3696 | #endif | ||
3697 | if (c->ln->items) | ||
3698 | line_start = c->ln->items->text_pos; | ||
3699 | else | ||
3700 | line_start = ti->parent.text_pos; | ||
3701 | |||
3702 | adv_line = 1; | ||
3703 | /* If we don't already have a wrap point from before */ | ||
3704 | if (wrap < 0) | ||
3705 | { | ||
3706 | if (it->format->wrap_word) | ||
3707 | wrap = _layout_get_wordwrap(c, it->format, ti, | ||
3708 | line_start, line_breaks); | ||
3709 | else if (it->format->wrap_char) | ||
3710 | wrap = _layout_get_charwrap(c, it->format, ti, | ||
3711 | line_start, line_breaks); | ||
3712 | else if (it->format->wrap_mixed) | ||
3713 | wrap = _layout_get_mixedwrap(c, it->format, ti, | ||
3714 | line_start, line_breaks); | ||
3715 | else | ||
3716 | wrap = -1; | ||
3717 | } | ||
3718 | |||
3719 | /* If it's before the item, rollback and apply. | ||
3720 | if it's in the item, cut. | ||
3721 | If it's after the item, delay the cut */ | ||
3722 | if (wrap > 0) | ||
3723 | { | ||
3724 | size_t uwrap = (size_t) wrap; | ||
3725 | if (uwrap < ti->parent.text_pos) | ||
3726 | { | ||
3727 | /* Rollback latest additions, and cut that | ||
3728 | item */ | ||
3729 | i = eina_list_prev(i); | ||
3730 | it = eina_list_data_get(i); | ||
3731 | while (uwrap < it->text_pos) | ||
3732 | { | ||
3733 | c->ln->items = _ITEM( | ||
3734 | eina_inlist_remove( | ||
3735 | EINA_INLIST_GET(c->ln->items), | ||
3736 | EINA_INLIST_GET(it))); | ||
3737 | i = eina_list_prev(i); | ||
3738 | it = eina_list_data_get(i); | ||
3739 | } | ||
3740 | c->x = it->x; | ||
3741 | c->ln->items = _ITEM( | ||
3742 | eina_inlist_remove( | ||
3743 | EINA_INLIST_GET(c->ln->items), | ||
3744 | EINA_INLIST_GET(it))); | ||
3745 | continue; | ||
3746 | } | ||
3747 | /* If it points to the end, it means the previous | ||
3748 | * char is a whitespace we should remove, so this | ||
3749 | * is a wanted cutting point. */ | ||
3750 | else if (uwrap > ti->parent.text_pos + | ||
3751 | ti->text_props.text_len) | ||
3752 | wrap = -1; /* Delay the cut in a smart way | ||
3753 | i.e use the item_pos as the line_start, because | ||
3754 | there's already no cut before*/ | ||
3755 | else | ||
3756 | wrap -= ti->parent.text_pos; /* Cut here */ | ||
3757 | } | ||
3758 | |||
3759 | if (wrap > 0) | ||
3760 | { | ||
3761 | _layout_item_text_split_strip_white(c, ti, i, wrap); | ||
3762 | } | ||
3763 | else if (wrap == 0) | ||
3764 | { | ||
3765 | /* Should wrap before the item */ | ||
3766 | adv_line = 0; | ||
3767 | redo_item = 1; | ||
3768 | _layout_line_advance(c, it->format); | ||
3769 | } | ||
3770 | /* Reset wrap */ | ||
3771 | wrap = -1; | ||
3772 | } | ||
3773 | } | ||
3774 | } | ||
3775 | |||
3776 | if (!redo_item && !it->visually_deleted) | ||
3777 | { | ||
3778 | c->ln->items = (Evas_Object_Textblock_Item *) | ||
3779 | eina_inlist_append(EINA_INLIST_GET(c->ln->items), | ||
3780 | EINA_INLIST_GET(it)); | ||
3781 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
3782 | { | ||
3783 | Evas_Object_Textblock_Format_Item *fi; | ||
3784 | fi = _ITEM_FORMAT(it); | ||
3785 | fi->y = c->y; | ||
3786 | /* If it's a newline, and we are not in newline compat | ||
3787 | * mode, or we are in newline compat mode, and this is | ||
3788 | * not used as a paragraph separator, advance */ | ||
3789 | if (fi->item && _IS_LINE_SEPARATOR(fi->item) && | ||
3790 | (!c->o->legacy_newline || | ||
3791 | eina_list_next(i))) | ||
3792 | { | ||
3793 | adv_line = 1; | ||
3794 | } | ||
3795 | } | ||
3796 | c->x += it->adv; | ||
3797 | i = eina_list_next(i); | ||
3798 | } | ||
3799 | if (adv_line) | ||
3800 | { | ||
3801 | /* Each line is according to the first item in it, and here | ||
3802 | * i is already the next item (or the current if we redo it) */ | ||
3803 | if (i) | ||
3804 | { | ||
3805 | it = _ITEM(eina_list_data_get(i)); | ||
3806 | } | ||
3807 | _layout_line_advance(c, it->format); | ||
3808 | } | ||
3809 | } | ||
3810 | if (c->ln->items) | ||
3811 | { | ||
3812 | /* Here 'it' is the last format used */ | ||
3813 | _layout_line_finalize(c, it->format); | ||
3814 | } | ||
3815 | |||
3816 | end: | ||
3817 | #ifdef HAVE_LINEBREAK | ||
3818 | if (line_breaks) | ||
3819 | free(line_breaks); | ||
3820 | #endif | ||
3821 | |||
3822 | return ret; | ||
3823 | } | ||
3824 | |||
3825 | /** | ||
3826 | * @internal | ||
3827 | * Invalidate text nodes according to format changes | ||
3828 | * This goes through all the new format changes and marks the text nodes | ||
3829 | * that should be invalidated because of format changes. | ||
3830 | * | ||
3831 | * @param c the working context. | ||
3832 | */ | ||
3833 | static inline void | ||
3834 | _format_changes_invalidate_text_nodes(Ctxt *c) | ||
3835 | { | ||
3836 | Evas_Object_Textblock_Node_Format *fnode = c->o->format_nodes; | ||
3837 | Evas_Object_Textblock_Node_Text *start_n = NULL; | ||
3838 | Eina_List *fstack = NULL; | ||
3839 | int balance = 0; | ||
3840 | while (fnode) | ||
3841 | { | ||
3842 | if (fnode->is_new) | ||
3843 | { | ||
3844 | const char *fstr = fnode->orig_format; | ||
3845 | /* balance < 0 means we gave up and everything should be | ||
3846 | * invalidated */ | ||
3847 | if (*fstr == '+') | ||
3848 | { | ||
3849 | balance++; | ||
3850 | if (!fstack) | ||
3851 | start_n = fnode->text_node; | ||
3852 | fstack = eina_list_prepend(fstack, fnode); | ||
3853 | } | ||
3854 | else if (*fstr == '-') | ||
3855 | { | ||
3856 | size_t fstr_len; | ||
3857 | /* Skip the '-' */ | ||
3858 | fstr++; | ||
3859 | fstr_len = strlen(fstr); | ||
3860 | /* Generic popper, just pop */ | ||
3861 | if (((fstr[0] == ' ') && !fstr[1]) || !fstr[0]) | ||
3862 | { | ||
3863 | fstack = eina_list_remove_list(fstack, fstack); | ||
3864 | balance--; | ||
3865 | } | ||
3866 | /* Find the matching format and pop it, if the matching format | ||
3867 | * is out format, i.e the last one, pop and break. */ | ||
3868 | else | ||
3869 | { | ||
3870 | Eina_List *i; | ||
3871 | Evas_Object_Textblock_Node_Format *fnode2; | ||
3872 | EINA_LIST_FOREACH(fstack, i, fnode2) | ||
3873 | { | ||
3874 | if (_FORMAT_IS_CLOSER_OF( | ||
3875 | fnode2->orig_format, fstr, fstr_len)) | ||
3876 | { | ||
3877 | fstack = eina_list_remove_list(fstack, i); | ||
3878 | break; | ||
3879 | } | ||
3880 | } | ||
3881 | balance--; | ||
3882 | } | ||
3883 | |||
3884 | if (!fstack) | ||
3885 | { | ||
3886 | Evas_Object_Textblock_Node_Text *f_tnode = | ||
3887 | fnode->text_node; | ||
3888 | while (start_n) | ||
3889 | { | ||
3890 | start_n->dirty = EINA_TRUE; | ||
3891 | if (start_n == f_tnode) | ||
3892 | break; | ||
3893 | start_n = | ||
3894 | _NODE_TEXT(EINA_INLIST_GET(start_n)->next); | ||
3895 | } | ||
3896 | start_n = NULL; | ||
3897 | } | ||
3898 | } | ||
3899 | else if (!fnode->visible) | ||
3900 | balance = -1; | ||
3901 | |||
3902 | if (balance < 0) | ||
3903 | { | ||
3904 | /* if we don't already have a starting point, use the | ||
3905 | * current paragraph. */ | ||
3906 | if (!start_n) | ||
3907 | start_n = fnode->text_node; | ||
3908 | break; | ||
3909 | } | ||
3910 | } | ||
3911 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
3912 | } | ||
3913 | |||
3914 | if (balance != 0) | ||
3915 | { | ||
3916 | while (start_n) | ||
3917 | { | ||
3918 | start_n->dirty = EINA_TRUE; | ||
3919 | start_n = _NODE_TEXT(EINA_INLIST_GET(start_n)->next); | ||
3920 | } | ||
3921 | } | ||
3922 | } | ||
3923 | |||
3924 | |||
3925 | /** FIXME: Document */ | ||
3926 | static void | ||
3927 | _layout_pre(Ctxt *c, int *style_pad_l, int *style_pad_r, int *style_pad_t, | ||
3928 | int *style_pad_b) | ||
3929 | { | ||
3930 | Evas_Object *obj = c->obj; | ||
3931 | Evas_Object_Textblock *o = c->o; | ||
3932 | /* Mark text nodes as dirty if format have changed. */ | ||
3933 | if (c->o->format_changed) | ||
3934 | { | ||
3935 | _format_changes_invalidate_text_nodes(c); | ||
3936 | } | ||
3937 | |||
3938 | if (o->content_changed) | ||
3939 | { | ||
3940 | Evas_Object_Textblock_Node_Text *n; | ||
3941 | c->o->have_ellipsis = 0; | ||
3942 | c->par = c->paragraphs = o->paragraphs; | ||
3943 | /* Go through all the text nodes to create the logical layout */ | ||
3944 | EINA_INLIST_FOREACH(c->o->text_nodes, n) | ||
3945 | { | ||
3946 | Evas_Object_Textblock_Node_Format *fnode; | ||
3947 | size_t start; | ||
3948 | int off; | ||
3949 | |||
3950 | /* If it's not a new paragraph, either update it or skip it. | ||
3951 | * Remove all the paragraphs that were deleted */ | ||
3952 | if (!n->is_new) | ||
3953 | { | ||
3954 | /* Remove all the deleted paragraphs at this point */ | ||
3955 | while (c->par->text_node != n) | ||
3956 | { | ||
3957 | Evas_Object_Textblock_Paragraph *tmp_par = | ||
3958 | (Evas_Object_Textblock_Paragraph *) | ||
3959 | EINA_INLIST_GET(c->par)->next; | ||
3960 | |||
3961 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
3962 | eina_inlist_remove(EINA_INLIST_GET(c->paragraphs), | ||
3963 | EINA_INLIST_GET(c->par)); | ||
3964 | _paragraph_free(obj, c->par); | ||
3965 | |||
3966 | c->par = tmp_par; | ||
3967 | } | ||
3968 | |||
3969 | /* If it's dirty, remove and recreate, if it's clean, | ||
3970 | * skip to the next. */ | ||
3971 | if (n->dirty) | ||
3972 | { | ||
3973 | Evas_Object_Textblock_Paragraph *prev_par = c->par; | ||
3974 | |||
3975 | _layout_paragraph_new(c, n, EINA_TRUE); | ||
3976 | |||
3977 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
3978 | eina_inlist_remove(EINA_INLIST_GET(c->paragraphs), | ||
3979 | EINA_INLIST_GET(prev_par)); | ||
3980 | _paragraph_free(obj, prev_par); | ||
3981 | } | ||
3982 | else | ||
3983 | { | ||
3984 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
3985 | EINA_INLIST_GET(c->par)->next; | ||
3986 | |||
3987 | /* Update the format stack according to the node's | ||
3988 | * formats */ | ||
3989 | fnode = n->format_node; | ||
3990 | while (fnode && (fnode->text_node == n)) | ||
3991 | { | ||
3992 | /* Only do this if this actually changes format */ | ||
3993 | if (fnode->format_change) | ||
3994 | _layout_do_format(obj, c, &c->fmt, fnode, | ||
3995 | style_pad_l, style_pad_r, | ||
3996 | style_pad_t, style_pad_b, EINA_FALSE); | ||
3997 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
3998 | } | ||
3999 | continue; | ||
4000 | } | ||
4001 | } | ||
4002 | else | ||
4003 | { | ||
4004 | /* If it's a new paragraph, just add it. */ | ||
4005 | _layout_paragraph_new(c, n, EINA_FALSE); | ||
4006 | } | ||
4007 | |||
4008 | #ifdef BIDI_SUPPORT | ||
4009 | _layout_update_bidi_props(c->o, c->par); | ||
4010 | #endif | ||
4011 | |||
4012 | /* For each text node to thorugh all of it's format nodes | ||
4013 | * append text from the start to the offset of the next format | ||
4014 | * using the last format got. if needed it also creates format | ||
4015 | * items this is the core algorithm of the layout mechanism. | ||
4016 | * Skip the unicode replacement chars when there are because | ||
4017 | * we don't want to print them. */ | ||
4018 | fnode = n->format_node; | ||
4019 | start = off = 0; | ||
4020 | while (fnode && (fnode->text_node == n)) | ||
4021 | { | ||
4022 | off += fnode->offset; | ||
4023 | /* No need to skip on the first run, or a non-visible one */ | ||
4024 | _layout_text_append(c, c->fmt, n, start, off, o->repch); | ||
4025 | _layout_do_format(obj, c, &c->fmt, fnode, style_pad_l, | ||
4026 | style_pad_r, style_pad_t, style_pad_b, EINA_TRUE); | ||
4027 | if ((c->have_underline2) || (c->have_underline)) | ||
4028 | { | ||
4029 | if (*style_pad_b < c->underline_extend) | ||
4030 | *style_pad_b = c->underline_extend; | ||
4031 | c->have_underline = 0; | ||
4032 | c->have_underline2 = 0; | ||
4033 | c->underline_extend = 0; | ||
4034 | } | ||
4035 | start += off; | ||
4036 | if (fnode->visible) | ||
4037 | { | ||
4038 | off = -1; | ||
4039 | start++; | ||
4040 | } | ||
4041 | else | ||
4042 | { | ||
4043 | off = 0; | ||
4044 | } | ||
4045 | fnode->is_new = EINA_FALSE; | ||
4046 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
4047 | } | ||
4048 | _layout_text_append(c, c->fmt, n, start, -1, o->repch); | ||
4049 | #ifdef BIDI_SUPPORT | ||
4050 | /* Clear the bidi props because we don't need them anymore. */ | ||
4051 | if (c->par->bidi_props) | ||
4052 | { | ||
4053 | evas_bidi_paragraph_props_unref(c->par->bidi_props); | ||
4054 | c->par->bidi_props = NULL; | ||
4055 | } | ||
4056 | #endif | ||
4057 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4058 | EINA_INLIST_GET(c->par)->next; | ||
4059 | } | ||
4060 | |||
4061 | /* Delete the rest of the layout paragraphs */ | ||
4062 | while (c->par) | ||
4063 | { | ||
4064 | Evas_Object_Textblock_Paragraph *tmp_par = | ||
4065 | (Evas_Object_Textblock_Paragraph *) | ||
4066 | EINA_INLIST_GET(c->par)->next; | ||
4067 | |||
4068 | c->paragraphs = (Evas_Object_Textblock_Paragraph *) | ||
4069 | eina_inlist_remove(EINA_INLIST_GET(c->paragraphs), | ||
4070 | EINA_INLIST_GET(c->par)); | ||
4071 | _paragraph_free(obj, c->par); | ||
4072 | |||
4073 | c->par = tmp_par; | ||
4074 | } | ||
4075 | o->paragraphs = c->paragraphs; | ||
4076 | c->par = NULL; | ||
4077 | } | ||
4078 | |||
4079 | } | ||
4080 | |||
4081 | /** | ||
4082 | * @internal | ||
4083 | * Create the layout from the nodes. | ||
4084 | * | ||
4085 | * @param obj the evas object - NOT NULL. | ||
4086 | * @param calc_only true if should only calc sizes false if should also create the layout.. It assumes native size is being calculated, doesn't support formatted size atm. | ||
4087 | * @param w the object's w, -1 means no wrapping (i.e infinite size) | ||
4088 | * @param h the object's h, -1 means inifinte size. | ||
4089 | * @param w_ret the object's calculated w. | ||
4090 | * @param h_ret the object's calculated h. | ||
4091 | */ | ||
4092 | static void | ||
4093 | _layout(const Evas_Object *obj, int w, int h, int *w_ret, int *h_ret) | ||
4094 | { | ||
4095 | Evas_Object_Textblock *o; | ||
4096 | Ctxt ctxt, *c; | ||
4097 | int style_pad_l = 0, style_pad_r = 0, style_pad_t = 0, style_pad_b = 0; | ||
4098 | |||
4099 | /* setup context */ | ||
4100 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4101 | c = &ctxt; | ||
4102 | c->obj = (Evas_Object *)obj; | ||
4103 | c->o = o; | ||
4104 | c->paragraphs = c->par = NULL; | ||
4105 | c->format_stack = NULL; | ||
4106 | c->fmt = NULL; | ||
4107 | c->x = c->y = 0; | ||
4108 | c->w = w; | ||
4109 | c->h = h; | ||
4110 | c->wmax = c->hmax = 0; | ||
4111 | c->maxascent = c->maxdescent = 0; | ||
4112 | c->marginl = c->marginr = 0; | ||
4113 | c->have_underline = 0; | ||
4114 | c->have_underline2 = 0; | ||
4115 | c->underline_extend = 0; | ||
4116 | c->line_no = 0; | ||
4117 | c->align = 0.0; | ||
4118 | c->align_auto = EINA_TRUE; | ||
4119 | c->ln = NULL; | ||
4120 | c->width_changed = (obj->cur.geometry.w != o->last_w); | ||
4121 | |||
4122 | /* Start of logical layout creation */ | ||
4123 | /* setup default base style */ | ||
4124 | if ((c->o->style) && (c->o->style->default_tag)) | ||
4125 | { | ||
4126 | c->fmt = _layout_format_push(c, NULL, NULL); | ||
4127 | _format_fill(c->obj, c->fmt, c->o->style->default_tag); | ||
4128 | _format_finalize(c->obj, c->fmt); | ||
4129 | } | ||
4130 | if (!c->fmt) | ||
4131 | { | ||
4132 | if (w_ret) *w_ret = 0; | ||
4133 | if (h_ret) *h_ret = 0; | ||
4134 | return; | ||
4135 | } | ||
4136 | |||
4137 | _layout_pre(c, &style_pad_l, &style_pad_r, &style_pad_t, &style_pad_b); | ||
4138 | c->paragraphs = o->paragraphs; | ||
4139 | |||
4140 | /* If there are no paragraphs, create the minimum needed, | ||
4141 | * if the last paragraph has no lines/text, create that as well */ | ||
4142 | if (!c->paragraphs) | ||
4143 | { | ||
4144 | _layout_paragraph_new(c, NULL, EINA_TRUE); | ||
4145 | o->paragraphs = c->paragraphs; | ||
4146 | } | ||
4147 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4148 | EINA_INLIST_GET(c->paragraphs)->last; | ||
4149 | if (!c->par->logical_items) | ||
4150 | { | ||
4151 | Evas_Object_Textblock_Text_Item *ti; | ||
4152 | ti = _layout_text_item_new(c, c->fmt); | ||
4153 | ti->parent.text_node = c->par->text_node; | ||
4154 | ti->parent.text_pos = 0; | ||
4155 | _layout_text_add_logical_item(c, ti, NULL); | ||
4156 | } | ||
4157 | |||
4158 | /* End of logical layout creation */ | ||
4159 | |||
4160 | /* Start of visual layout creation */ | ||
4161 | { | ||
4162 | Evas_Object_Textblock_Paragraph *last_vis_par = NULL; | ||
4163 | int par_index_step = o->num_paragraphs / TEXTBLOCK_PAR_INDEX_SIZE; | ||
4164 | int par_count = 1; /* Force it to take the first one */ | ||
4165 | int par_index_pos = 0; | ||
4166 | |||
4167 | if (par_index_step == 0) par_index_step = 1; | ||
4168 | |||
4169 | /* Clear all of the index */ | ||
4170 | memset(o->par_index, 0, sizeof(o->par_index)); | ||
4171 | |||
4172 | EINA_INLIST_FOREACH(c->paragraphs, c->par) | ||
4173 | { | ||
4174 | _layout_update_par(c); | ||
4175 | |||
4176 | /* Break if we should stop here. */ | ||
4177 | if (_layout_par(c)) | ||
4178 | { | ||
4179 | last_vis_par = c->par; | ||
4180 | break; | ||
4181 | } | ||
4182 | |||
4183 | if ((par_index_pos < TEXTBLOCK_PAR_INDEX_SIZE) && (--par_count == 0)) | ||
4184 | { | ||
4185 | par_count = par_index_step; | ||
4186 | |||
4187 | o->par_index[par_index_pos++] = c->par; | ||
4188 | } | ||
4189 | } | ||
4190 | |||
4191 | /* Mark all the rest of the paragraphs as invisible */ | ||
4192 | if (c->par) | ||
4193 | { | ||
4194 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4195 | EINA_INLIST_GET(c->par)->next; | ||
4196 | while (c->par) | ||
4197 | { | ||
4198 | c->par->visible = 0; | ||
4199 | c->par = (Evas_Object_Textblock_Paragraph *) | ||
4200 | EINA_INLIST_GET(c->par)->next; | ||
4201 | } | ||
4202 | } | ||
4203 | |||
4204 | /* Get the last visible paragraph in the layout */ | ||
4205 | if (!last_vis_par && c->paragraphs) | ||
4206 | last_vis_par = (Evas_Object_Textblock_Paragraph *) | ||
4207 | EINA_INLIST_GET(c->paragraphs)->last; | ||
4208 | |||
4209 | if (last_vis_par) | ||
4210 | c->hmax = last_vis_par->y + last_vis_par->h; | ||
4211 | } | ||
4212 | |||
4213 | /* Clean the rest of the format stack */ | ||
4214 | while (c->format_stack) | ||
4215 | { | ||
4216 | c->fmt = c->format_stack->data; | ||
4217 | c->format_stack = eina_list_remove_list(c->format_stack, c->format_stack); | ||
4218 | _format_unref_free(c->obj, c->fmt); | ||
4219 | } | ||
4220 | |||
4221 | if (w_ret) *w_ret = c->wmax; | ||
4222 | if (h_ret) *h_ret = c->hmax; | ||
4223 | |||
4224 | /* Vertically align the textblock */ | ||
4225 | if ((o->valign > 0.0) && (c->h > c->hmax)) | ||
4226 | { | ||
4227 | Evas_Coord adjustment = (c->h - c->hmax) * o->valign; | ||
4228 | Evas_Object_Textblock_Paragraph *par; | ||
4229 | EINA_INLIST_FOREACH(c->paragraphs, par) | ||
4230 | { | ||
4231 | par->y += adjustment; | ||
4232 | } | ||
4233 | } | ||
4234 | |||
4235 | if ((o->style_pad.l != style_pad_l) || (o->style_pad.r != style_pad_r) || | ||
4236 | (o->style_pad.t != style_pad_t) || (o->style_pad.b != style_pad_b)) | ||
4237 | { | ||
4238 | o->style_pad.l = style_pad_l; | ||
4239 | o->style_pad.r = style_pad_r; | ||
4240 | o->style_pad.t = style_pad_t; | ||
4241 | o->style_pad.b = style_pad_b; | ||
4242 | _paragraphs_clear(obj, c->paragraphs); | ||
4243 | _layout(obj, w, h, w_ret, h_ret); | ||
4244 | } | ||
4245 | } | ||
4246 | |||
4247 | /* | ||
4248 | * @internal | ||
4249 | * Relayout the object according to current object size. | ||
4250 | * | ||
4251 | * @param obj the evas object - NOT NULL. | ||
4252 | */ | ||
4253 | static void | ||
4254 | _relayout(const Evas_Object *obj) | ||
4255 | { | ||
4256 | Evas_Object_Textblock *o; | ||
4257 | |||
4258 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4259 | _layout(obj, obj->cur.geometry.w, obj->cur.geometry.h, | ||
4260 | &o->formatted.w, &o->formatted.h); | ||
4261 | o->formatted.valid = 1; | ||
4262 | o->last_w = obj->cur.geometry.w; | ||
4263 | o->last_h = obj->cur.geometry.h; | ||
4264 | o->changed = 0; | ||
4265 | o->content_changed = 0; | ||
4266 | o->format_changed = EINA_FALSE; | ||
4267 | o->redraw = 1; | ||
4268 | } | ||
4269 | |||
4270 | /** | ||
4271 | * @internal | ||
4272 | * Find the layout item and line that match the text node and position passed. | ||
4273 | * | ||
4274 | * @param obj the evas object - NOT NULL. | ||
4275 | * @param n the text node - Not null. | ||
4276 | * @param pos the position to look for - valid. | ||
4277 | * @param[out] lnr the line found - not null. | ||
4278 | * @param[out] tir the item found - not null. | ||
4279 | * @see _find_layout_format_item_line_match() | ||
4280 | */ | ||
4281 | static void | ||
4282 | _find_layout_item_line_match(Evas_Object *obj, Evas_Object_Textblock_Node_Text *n, int pos, Evas_Object_Textblock_Line **lnr, Evas_Object_Textblock_Item **itr) | ||
4283 | { | ||
4284 | Evas_Object_Textblock_Paragraph *found_par; | ||
4285 | Evas_Object_Textblock_Line *ln; | ||
4286 | Evas_Object_Textblock *o; | ||
4287 | |||
4288 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4289 | if (!o->formatted.valid) _relayout(obj); | ||
4290 | |||
4291 | found_par = n->par; | ||
4292 | if (found_par) | ||
4293 | { | ||
4294 | _layout_paragraph_render(o, found_par); | ||
4295 | EINA_INLIST_FOREACH(found_par->lines, ln) | ||
4296 | { | ||
4297 | Evas_Object_Textblock_Item *it; | ||
4298 | |||
4299 | EINA_INLIST_FOREACH(ln->items, it) | ||
4300 | { | ||
4301 | /* FIXME: p should be size_t, same goes for pos */ | ||
4302 | int p = (int) it->text_pos; | ||
4303 | |||
4304 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
4305 | { | ||
4306 | Evas_Object_Textblock_Text_Item *ti = | ||
4307 | _ITEM_TEXT(it); | ||
4308 | |||
4309 | p += (int) ti->text_props.text_len; | ||
4310 | } | ||
4311 | else | ||
4312 | { | ||
4313 | p++; | ||
4314 | } | ||
4315 | |||
4316 | if (((pos >= (int) it->text_pos) && (pos < p))) | ||
4317 | { | ||
4318 | *lnr = ln; | ||
4319 | *itr = it; | ||
4320 | return; | ||
4321 | } | ||
4322 | else if (p == pos) | ||
4323 | { | ||
4324 | *lnr = ln; | ||
4325 | *itr = it; | ||
4326 | } | ||
4327 | } | ||
4328 | } | ||
4329 | } | ||
4330 | } | ||
4331 | |||
4332 | /** | ||
4333 | * @internal | ||
4334 | * Return the line number 'line'. | ||
4335 | * | ||
4336 | * @param obj the evas object - NOT NULL. | ||
4337 | * @param line the line to find | ||
4338 | * @return the line of line number or NULL if no line found. | ||
4339 | */ | ||
4340 | static Evas_Object_Textblock_Line * | ||
4341 | _find_layout_line_num(const Evas_Object *obj, int line) | ||
4342 | { | ||
4343 | Evas_Object_Textblock_Paragraph *par; | ||
4344 | Evas_Object_Textblock_Line *ln; | ||
4345 | Evas_Object_Textblock *o; | ||
4346 | |||
4347 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4348 | |||
4349 | par = _layout_find_paragraph_by_line_no(o, line); | ||
4350 | if (par) | ||
4351 | { | ||
4352 | _layout_paragraph_render(o, par); | ||
4353 | EINA_INLIST_FOREACH(par->lines, ln) | ||
4354 | { | ||
4355 | if (par->line_no + ln->line_no == line) return ln; | ||
4356 | } | ||
4357 | } | ||
4358 | return NULL; | ||
4359 | } | ||
4360 | |||
4361 | EAPI Evas_Object * | ||
4362 | evas_object_textblock_add(Evas *e) | ||
4363 | { | ||
4364 | Evas_Object *obj; | ||
4365 | |||
4366 | MAGIC_CHECK(e, Evas, MAGIC_EVAS); | ||
4367 | return NULL; | ||
4368 | MAGIC_CHECK_END(); | ||
4369 | obj = evas_object_new(e); | ||
4370 | evas_object_textblock_init(obj); | ||
4371 | evas_object_inject(obj, e); | ||
4372 | return obj; | ||
4373 | } | ||
4374 | |||
4375 | EAPI Evas_Textblock_Style * | ||
4376 | evas_textblock_style_new(void) | ||
4377 | { | ||
4378 | Evas_Textblock_Style *ts; | ||
4379 | |||
4380 | ts = calloc(1, sizeof(Evas_Textblock_Style)); | ||
4381 | return ts; | ||
4382 | } | ||
4383 | |||
4384 | EAPI void | ||
4385 | evas_textblock_style_free(Evas_Textblock_Style *ts) | ||
4386 | { | ||
4387 | if (!ts) return; | ||
4388 | if (ts->objects) | ||
4389 | { | ||
4390 | ts->delete_me = 1; | ||
4391 | return; | ||
4392 | } | ||
4393 | _style_clear(ts); | ||
4394 | free(ts); | ||
4395 | } | ||
4396 | |||
4397 | EAPI void | ||
4398 | evas_textblock_style_set(Evas_Textblock_Style *ts, const char *text) | ||
4399 | { | ||
4400 | Eina_List *l; | ||
4401 | Evas_Object *obj; | ||
4402 | |||
4403 | if (!ts) return; | ||
4404 | /* If the style wasn't really changed, abort. */ | ||
4405 | if ((!ts->style_text && !text) || | ||
4406 | (ts->style_text && text && !strcmp(text, ts->style_text))) | ||
4407 | return; | ||
4408 | |||
4409 | EINA_LIST_FOREACH(ts->objects, l, obj) | ||
4410 | { | ||
4411 | Evas_Object_Textblock *o; | ||
4412 | |||
4413 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
4414 | _evas_textblock_invalidate_all(o); | ||
4415 | _evas_textblock_changed(o, obj); | ||
4416 | } | ||
4417 | |||
4418 | _style_replace(ts, text); | ||
4419 | |||
4420 | if (ts->style_text) | ||
4421 | { | ||
4422 | // format MUST be KEY='VALUE'[KEY='VALUE']... | ||
4423 | const char *p; | ||
4424 | const char *key_start, *key_stop, *val_start, *val_stop; | ||
4425 | |||
4426 | key_start = key_stop = val_start = val_stop = NULL; | ||
4427 | p = ts->style_text; | ||
4428 | while (*p) | ||
4429 | { | ||
4430 | if (!key_start) | ||
4431 | { | ||
4432 | if (!isspace(*p)) | ||
4433 | key_start = p; | ||
4434 | } | ||
4435 | else if (!key_stop) | ||
4436 | { | ||
4437 | if ((*p == '=') || (isspace(*p))) | ||
4438 | key_stop = p; | ||
4439 | } | ||
4440 | else if (!val_start) | ||
4441 | { | ||
4442 | if (((*p) == '\'') && (*(p + 1))) | ||
4443 | val_start = p + 1; | ||
4444 | } | ||
4445 | else if (!val_stop) | ||
4446 | { | ||
4447 | if (((*p) == '\'') && (p > ts->style_text) && (p[-1] != '\\')) | ||
4448 | val_stop = p; | ||
4449 | } | ||
4450 | if ((key_start) && (key_stop) && (val_start) && (val_stop)) | ||
4451 | { | ||
4452 | char *tags, *replaces; | ||
4453 | Evas_Object_Style_Tag *tag; | ||
4454 | size_t tag_len = key_stop - key_start; | ||
4455 | size_t replace_len = val_stop - val_start; | ||
4456 | |||
4457 | tags = malloc(tag_len + 1); | ||
4458 | if (tags) | ||
4459 | { | ||
4460 | memcpy(tags, key_start, tag_len); | ||
4461 | tags[tag_len] = 0; | ||
4462 | } | ||
4463 | |||
4464 | replaces = malloc(replace_len + 1); | ||
4465 | if (replaces) | ||
4466 | { | ||
4467 | memcpy(replaces, val_start, replace_len); | ||
4468 | replaces[replace_len] = 0; | ||
4469 | } | ||
4470 | if ((tags) && (replaces)) | ||
4471 | { | ||
4472 | if (!strcmp(tags, "DEFAULT")) | ||
4473 | { | ||
4474 | ts->default_tag = replaces; | ||
4475 | free(tags); | ||
4476 | } | ||
4477 | else | ||
4478 | { | ||
4479 | tag = calloc(1, sizeof(Evas_Object_Style_Tag)); | ||
4480 | if (tag) | ||
4481 | { | ||
4482 | tag->tag = tags; | ||
4483 | tag->replace = replaces; | ||
4484 | tag->tag_len = tag_len; | ||
4485 | tag->replace_len = replace_len; | ||
4486 | ts->tags = (Evas_Object_Style_Tag *)eina_inlist_append(EINA_INLIST_GET(ts->tags), EINA_INLIST_GET(tag)); | ||
4487 | } | ||
4488 | else | ||
4489 | { | ||
4490 | free(tags); | ||
4491 | free(replaces); | ||
4492 | } | ||
4493 | } | ||
4494 | } | ||
4495 | else | ||
4496 | { | ||
4497 | if (tags) free(tags); | ||
4498 | if (replaces) free(replaces); | ||
4499 | } | ||
4500 | key_start = key_stop = val_start = val_stop = NULL; | ||
4501 | } | ||
4502 | p++; | ||
4503 | } | ||
4504 | } | ||
4505 | } | ||
4506 | |||
4507 | EAPI const char * | ||
4508 | evas_textblock_style_get(const Evas_Textblock_Style *ts) | ||
4509 | { | ||
4510 | if (!ts) return NULL; | ||
4511 | return ts->style_text; | ||
4512 | } | ||
4513 | |||
4514 | /* textblock styles */ | ||
4515 | EAPI void | ||
4516 | evas_object_textblock_style_set(Evas_Object *obj, Evas_Textblock_Style *ts) | ||
4517 | { | ||
4518 | TB_HEAD(); | ||
4519 | if (ts == o->style) return; | ||
4520 | if ((ts) && (ts->delete_me)) return; | ||
4521 | if (o->style) | ||
4522 | { | ||
4523 | Evas_Textblock_Style *old_ts; | ||
4524 | if (o->markup_text) | ||
4525 | { | ||
4526 | free(o->markup_text); | ||
4527 | o->markup_text = NULL; | ||
4528 | } | ||
4529 | |||
4530 | old_ts = o->style; | ||
4531 | old_ts->objects = eina_list_remove(old_ts->objects, obj); | ||
4532 | if ((old_ts->delete_me) && (!old_ts->objects)) | ||
4533 | evas_textblock_style_free(old_ts); | ||
4534 | } | ||
4535 | if (ts) | ||
4536 | { | ||
4537 | ts->objects = eina_list_append(ts->objects, obj); | ||
4538 | } | ||
4539 | o->style = ts; | ||
4540 | |||
4541 | _evas_textblock_invalidate_all(o); | ||
4542 | _evas_textblock_changed(o, obj); | ||
4543 | } | ||
4544 | |||
4545 | EAPI const Evas_Textblock_Style * | ||
4546 | evas_object_textblock_style_get(const Evas_Object *obj) | ||
4547 | { | ||
4548 | TB_HEAD_RETURN(NULL); | ||
4549 | return o->style; | ||
4550 | } | ||
4551 | |||
4552 | EAPI void | ||
4553 | evas_object_textblock_replace_char_set(Evas_Object *obj, const char *ch) | ||
4554 | { | ||
4555 | TB_HEAD(); | ||
4556 | if (o->repch) eina_stringshare_del(o->repch); | ||
4557 | if (ch) o->repch = eina_stringshare_add(ch); | ||
4558 | else o->repch = NULL; | ||
4559 | _evas_textblock_invalidate_all(o); | ||
4560 | _evas_textblock_changed(o, obj); | ||
4561 | } | ||
4562 | |||
4563 | EAPI void | ||
4564 | evas_object_textblock_legacy_newline_set(Evas_Object *obj, Eina_Bool mode) | ||
4565 | { | ||
4566 | TB_HEAD(); | ||
4567 | if (o->legacy_newline == mode) | ||
4568 | return; | ||
4569 | |||
4570 | o->legacy_newline = mode; | ||
4571 | /* FIXME: Should recreate all the textnodes... For now, it's just | ||
4572 | * for new text inserted. */ | ||
4573 | } | ||
4574 | |||
4575 | EAPI Eina_Bool | ||
4576 | evas_object_textblock_legacy_newline_get(const Evas_Object *obj) | ||
4577 | { | ||
4578 | TB_HEAD_RETURN(EINA_FALSE); | ||
4579 | return o->legacy_newline; | ||
4580 | } | ||
4581 | |||
4582 | EAPI void | ||
4583 | evas_object_textblock_valign_set(Evas_Object *obj, double align) | ||
4584 | { | ||
4585 | TB_HEAD(); | ||
4586 | if (align < 0.0) align = 0.0; | ||
4587 | else if (align > 1.0) align = 1.0; | ||
4588 | if (o->valign == align) return; | ||
4589 | o->valign = align; | ||
4590 | _evas_textblock_changed(o, obj); | ||
4591 | } | ||
4592 | |||
4593 | EAPI double | ||
4594 | evas_object_textblock_valign_get(const Evas_Object *obj) | ||
4595 | { | ||
4596 | TB_HEAD_RETURN(0.0); | ||
4597 | return o->valign; | ||
4598 | } | ||
4599 | |||
4600 | EAPI void | ||
4601 | evas_object_textblock_bidi_delimiters_set(Evas_Object *obj, const char *delim) | ||
4602 | { | ||
4603 | TB_HEAD(); | ||
4604 | eina_stringshare_replace(&o->bidi_delimiters, delim); | ||
4605 | } | ||
4606 | |||
4607 | EAPI const char * | ||
4608 | evas_object_textblock_bidi_delimiters_get(const Evas_Object *obj) | ||
4609 | { | ||
4610 | TB_HEAD_RETURN(NULL); | ||
4611 | return o->bidi_delimiters; | ||
4612 | } | ||
4613 | |||
4614 | EAPI const char * | ||
4615 | evas_object_textblock_replace_char_get(Evas_Object *obj) | ||
4616 | { | ||
4617 | TB_HEAD_RETURN(NULL); | ||
4618 | return o->repch; | ||
4619 | } | ||
4620 | |||
4621 | /** | ||
4622 | * @internal | ||
4623 | * Advance p_buff to point after the end of the string. It's used with the | ||
4624 | * @ref escaped_strings[] variable. | ||
4625 | * | ||
4626 | * @param p_buff the pointer to the current string. | ||
4627 | */ | ||
4628 | static inline void | ||
4629 | _escaped_advance_after_end_of_string(const char **p_buf) | ||
4630 | { | ||
4631 | while (**p_buf != 0) (*p_buf)++; | ||
4632 | (*p_buf)++; | ||
4633 | } | ||
4634 | |||
4635 | /** | ||
4636 | * @internal | ||
4637 | * Advance p_buff to point after the end of the string. It's used with the | ||
4638 | * @ref escaped_strings[] variable. Also chec if matches. | ||
4639 | * FIXME: doc. | ||
4640 | * | ||
4641 | * @param p_buff the pointer to the current string. | ||
4642 | */ | ||
4643 | static inline int | ||
4644 | _escaped_is_eq_and_advance(const char *s, const char *s_end, | ||
4645 | const char **p_m, const char *m_end) | ||
4646 | { | ||
4647 | Eina_Bool reached_end; | ||
4648 | for (;((s < s_end) && (*p_m < m_end)); s++, (*p_m)++) | ||
4649 | { | ||
4650 | if (*s != **p_m) | ||
4651 | { | ||
4652 | _escaped_advance_after_end_of_string(p_m); | ||
4653 | return 0; | ||
4654 | } | ||
4655 | } | ||
4656 | |||
4657 | reached_end = !**p_m; | ||
4658 | if (*p_m < m_end) | ||
4659 | _escaped_advance_after_end_of_string(p_m); | ||
4660 | |||
4661 | return ((s == s_end) && reached_end); | ||
4662 | } | ||
4663 | |||
4664 | /** | ||
4665 | * @internal | ||
4666 | * | ||
4667 | * @param s the string to match | ||
4668 | */ | ||
4669 | static inline const char * | ||
4670 | _escaped_char_match(const char *s, int *adv) | ||
4671 | { | ||
4672 | const char *map_itr, *map_end, *mc, *sc; | ||
4673 | |||
4674 | map_itr = escape_strings; | ||
4675 | map_end = map_itr + sizeof(escape_strings); | ||
4676 | |||
4677 | while (map_itr < map_end) | ||
4678 | { | ||
4679 | const char *escape; | ||
4680 | int match; | ||
4681 | |||
4682 | escape = map_itr; | ||
4683 | _escaped_advance_after_end_of_string(&map_itr); | ||
4684 | if (map_itr >= map_end) break; | ||
4685 | |||
4686 | mc = map_itr; | ||
4687 | sc = s; | ||
4688 | match = 1; | ||
4689 | while ((*mc) && (*sc)) | ||
4690 | { | ||
4691 | if ((unsigned char)*sc < (unsigned char)*mc) return NULL; | ||
4692 | if (*sc != *mc) match = 0; | ||
4693 | mc++; | ||
4694 | sc++; | ||
4695 | } | ||
4696 | if (match) | ||
4697 | { | ||
4698 | *adv = mc - map_itr; | ||
4699 | return escape; | ||
4700 | } | ||
4701 | _escaped_advance_after_end_of_string(&map_itr); | ||
4702 | } | ||
4703 | return NULL; | ||
4704 | } | ||
4705 | |||
4706 | /** | ||
4707 | * @internal | ||
4708 | * FIXME: TBD. | ||
4709 | * | ||
4710 | * @param s the string to match | ||
4711 | */ | ||
4712 | static inline const char * | ||
4713 | _escaped_char_get(const char *s, const char *s_end) | ||
4714 | { | ||
4715 | /* Handle numeric escape codes. */ | ||
4716 | if (s[1] == '#') | ||
4717 | { | ||
4718 | static char utf8_escape[7]; /* Support up to 6 bytes utf8 */ | ||
4719 | char ustr[10]; | ||
4720 | Eina_Unicode uchar[2] = { 0, 0 }; | ||
4721 | char *utf8_char; | ||
4722 | size_t len = 0; | ||
4723 | int base = 10; | ||
4724 | s += 2; /* Skip "&#" */ | ||
4725 | |||
4726 | if (tolower(*s) == 'x') | ||
4727 | { | ||
4728 | s++; | ||
4729 | base = 16; | ||
4730 | } | ||
4731 | |||
4732 | len = s_end - s; | ||
4733 | if (len >= sizeof(ustr) + 1) | ||
4734 | len = sizeof(ustr); | ||
4735 | |||
4736 | memcpy(ustr, s, len); | ||
4737 | ustr[len] = '\0'; | ||
4738 | uchar[0] = strtol(ustr, NULL, base); | ||
4739 | |||
4740 | if (uchar[0] == 0) | ||
4741 | return NULL; | ||
4742 | |||
4743 | utf8_char = eina_unicode_unicode_to_utf8(uchar, NULL); | ||
4744 | strcpy(utf8_escape, utf8_char); | ||
4745 | free(utf8_char); | ||
4746 | |||
4747 | return utf8_escape; | ||
4748 | } | ||
4749 | else | ||
4750 | { | ||
4751 | const char *map_itr, *map_end; | ||
4752 | |||
4753 | map_itr = escape_strings; | ||
4754 | map_end = map_itr + sizeof(escape_strings); | ||
4755 | |||
4756 | while (map_itr < map_end) | ||
4757 | { | ||
4758 | if (_escaped_is_eq_and_advance(s, s_end, &map_itr, map_end)) | ||
4759 | return map_itr; | ||
4760 | if (map_itr < map_end) | ||
4761 | _escaped_advance_after_end_of_string(&map_itr); | ||
4762 | } | ||
4763 | } | ||
4764 | |||
4765 | return NULL; | ||
4766 | } | ||
4767 | |||
4768 | EAPI const char * | ||
4769 | evas_textblock_escape_string_get(const char *escape) | ||
4770 | { | ||
4771 | /* & -> & */ | ||
4772 | return _escaped_char_get(escape, escape + strlen(escape)); | ||
4773 | } | ||
4774 | |||
4775 | EAPI const char * | ||
4776 | evas_textblock_escape_string_range_get(const char *escape_start, const char *escape_end) | ||
4777 | { | ||
4778 | return _escaped_char_get(escape_start, escape_end); | ||
4779 | } | ||
4780 | |||
4781 | EAPI const char * | ||
4782 | evas_textblock_string_escape_get(const char *string, int *len_ret) | ||
4783 | { | ||
4784 | /* & -> & */ | ||
4785 | return _escaped_char_match(string, len_ret); | ||
4786 | } | ||
4787 | |||
4788 | /** | ||
4789 | * @internal | ||
4790 | * Appends the escaped char beteewn s and s_end to the curosr | ||
4791 | * | ||
4792 | * | ||
4793 | * @param s the start of the string | ||
4794 | * @param s_end the end of the string. | ||
4795 | */ | ||
4796 | static inline void | ||
4797 | _append_escaped_char(Evas_Textblock_Cursor *cur, const char *s, | ||
4798 | const char *s_end) | ||
4799 | { | ||
4800 | const char *escape; | ||
4801 | |||
4802 | escape = _escaped_char_get(s, s_end); | ||
4803 | if (escape) | ||
4804 | evas_textblock_cursor_text_append(cur, escape); | ||
4805 | } | ||
4806 | |||
4807 | /** | ||
4808 | * @internal | ||
4809 | * prepends the escaped char beteewn s and s_end to the curosr | ||
4810 | * | ||
4811 | * | ||
4812 | * @param s the start of the string | ||
4813 | * @param s_end the end of the string. | ||
4814 | */ | ||
4815 | static inline void | ||
4816 | _prepend_escaped_char(Evas_Textblock_Cursor *cur, const char *s, | ||
4817 | const char *s_end) | ||
4818 | { | ||
4819 | const char *escape; | ||
4820 | |||
4821 | escape = _escaped_char_get(s, s_end); | ||
4822 | if (escape) | ||
4823 | evas_textblock_cursor_text_prepend(cur, escape); | ||
4824 | } | ||
4825 | |||
4826 | |||
4827 | EAPI void | ||
4828 | evas_object_textblock_text_markup_set(Evas_Object *obj, const char *text) | ||
4829 | { | ||
4830 | TB_HEAD(); | ||
4831 | if ((text != o->markup_text) && (o->markup_text)) | ||
4832 | { | ||
4833 | free(o->markup_text); | ||
4834 | o->markup_text = NULL; | ||
4835 | } | ||
4836 | _nodes_clear(obj); | ||
4837 | if (!o->style) | ||
4838 | { | ||
4839 | if (text != o->markup_text) | ||
4840 | { | ||
4841 | if (text) o->markup_text = strdup(text); | ||
4842 | } | ||
4843 | return; | ||
4844 | } | ||
4845 | evas_textblock_cursor_paragraph_first(o->cursor); | ||
4846 | |||
4847 | evas_object_textblock_text_markup_prepend(o->cursor, text); | ||
4848 | /* Point all the cursors to the starrt */ | ||
4849 | { | ||
4850 | Eina_List *l; | ||
4851 | Evas_Textblock_Cursor *data; | ||
4852 | |||
4853 | evas_textblock_cursor_paragraph_first(o->cursor); | ||
4854 | EINA_LIST_FOREACH(o->cursors, l, data) | ||
4855 | evas_textblock_cursor_paragraph_first(data); | ||
4856 | } | ||
4857 | } | ||
4858 | |||
4859 | EAPI void | ||
4860 | evas_object_textblock_text_markup_prepend(Evas_Textblock_Cursor *cur, const char *text) | ||
4861 | { | ||
4862 | Evas_Object *obj = cur->obj; | ||
4863 | TB_HEAD(); | ||
4864 | if (text) | ||
4865 | { | ||
4866 | char *s, *p; | ||
4867 | char *tag_start, *tag_end, *esc_start, *esc_end; | ||
4868 | |||
4869 | tag_start = tag_end = esc_start = esc_end = NULL; | ||
4870 | p = (char *)text; | ||
4871 | s = p; | ||
4872 | /* This loop goes through all of the mark up text until it finds format | ||
4873 | * tags, escape sequences or the terminating NULL. When it finds either | ||
4874 | * of those, it appends the text found up until that point to the textblock | ||
4875 | * proccesses whatever found. It repeats itself until the termainating | ||
4876 | * NULL is reached. */ | ||
4877 | for (;;) | ||
4878 | { | ||
4879 | /* If we got to the end of string or just finished/started tag | ||
4880 | * or escape sequence handling. */ | ||
4881 | if ((*p == 0) || | ||
4882 | (tag_end) || (esc_end) || | ||
4883 | (tag_start) || (esc_start)) | ||
4884 | { | ||
4885 | if (tag_end) | ||
4886 | { | ||
4887 | /* If we reached to a tag ending, analyze the tag */ | ||
4888 | char *ttag; | ||
4889 | size_t ttag_len = tag_end - tag_start; | ||
4890 | |||
4891 | |||
4892 | ttag = malloc(ttag_len + 1); | ||
4893 | if (ttag) | ||
4894 | { | ||
4895 | memcpy(ttag, tag_start, ttag_len); | ||
4896 | ttag[ttag_len] = 0; | ||
4897 | evas_textblock_cursor_format_prepend(cur, ttag); | ||
4898 | free(ttag); | ||
4899 | } | ||
4900 | tag_start = tag_end = NULL; | ||
4901 | } | ||
4902 | else if (esc_end) | ||
4903 | { | ||
4904 | _prepend_escaped_char(cur, esc_start, esc_end + 1); | ||
4905 | esc_start = esc_end = NULL; | ||
4906 | } | ||
4907 | else if (*p == 0) | ||
4908 | { | ||
4909 | _prepend_text_run(cur, s, p); | ||
4910 | s = NULL; | ||
4911 | } | ||
4912 | if (*p == 0) | ||
4913 | break; | ||
4914 | } | ||
4915 | if (*p == '<') | ||
4916 | { | ||
4917 | if (!esc_start) | ||
4918 | { | ||
4919 | /* Append the text prior to this to the textblock and mark | ||
4920 | * the start of the tag */ | ||
4921 | tag_start = p; | ||
4922 | tag_end = NULL; | ||
4923 | _prepend_text_run(cur, s, p); | ||
4924 | s = NULL; | ||
4925 | } | ||
4926 | } | ||
4927 | else if (*p == '>') | ||
4928 | { | ||
4929 | if (tag_start) | ||
4930 | { | ||
4931 | tag_end = p + 1; | ||
4932 | s = p + 1; | ||
4933 | } | ||
4934 | } | ||
4935 | else if (*p == '&') | ||
4936 | { | ||
4937 | if (!tag_start) | ||
4938 | { | ||
4939 | /* Append the text prior to this to the textblock and mark | ||
4940 | * the start of the escape sequence */ | ||
4941 | esc_start = p; | ||
4942 | esc_end = NULL; | ||
4943 | _prepend_text_run(cur, s, p); | ||
4944 | s = NULL; | ||
4945 | } | ||
4946 | } | ||
4947 | else if (*p == ';') | ||
4948 | { | ||
4949 | if (esc_start) | ||
4950 | { | ||
4951 | esc_end = p; | ||
4952 | s = p + 1; | ||
4953 | } | ||
4954 | } | ||
4955 | /* Unicode object replcament char */ | ||
4956 | else if (!strncmp("\xEF\xBF\xBC", p, 3)) | ||
4957 | { | ||
4958 | /*FIXME: currently just remove them, maybe do something | ||
4959 | * fancier in the future, atm it breaks if this char | ||
4960 | * is inside <> */ | ||
4961 | _prepend_text_run(cur, s, p); | ||
4962 | p += 2; /* it's also advanced later in this loop need +3 | ||
4963 | * in total*/ | ||
4964 | s = p + 1; /* One after the end of the replacement char */ | ||
4965 | } | ||
4966 | p++; | ||
4967 | } | ||
4968 | } | ||
4969 | _evas_textblock_changed(o, obj); | ||
4970 | } | ||
4971 | |||
4972 | |||
4973 | /** | ||
4974 | * @internal | ||
4975 | * An helper function to markup get. Appends the format from fnode to the strbugf txt. | ||
4976 | * | ||
4977 | * @param o the textblock object. | ||
4978 | * @param txt the strbuf to append to. | ||
4979 | * @param fnode the format node to process. | ||
4980 | */ | ||
4981 | static void | ||
4982 | _markup_get_format_append(Evas_Object_Textblock *o __UNUSED__, Eina_Strbuf *txt, Evas_Object_Textblock_Node_Format *fnode) | ||
4983 | { | ||
4984 | eina_strbuf_append_char(txt, '<'); | ||
4985 | { | ||
4986 | const char *s; | ||
4987 | int pop = 0; | ||
4988 | |||
4989 | // FIXME: need to escape | ||
4990 | s = fnode->orig_format; | ||
4991 | if (*s == '-') pop = 1; | ||
4992 | while ((*s == ' ') || (*s == '+') || (*s == '-')) s++; | ||
4993 | if (pop) eina_strbuf_append_char(txt, '/'); | ||
4994 | eina_strbuf_append(txt, s); | ||
4995 | } | ||
4996 | eina_strbuf_append_char(txt, '>'); | ||
4997 | } | ||
4998 | |||
4999 | /** | ||
5000 | * @internal | ||
5001 | * An helper function to markup get. Appends the text in text. | ||
5002 | * | ||
5003 | * @param txt the strbuf to append to. | ||
5004 | * @param text the text to process. | ||
5005 | */ | ||
5006 | static void | ||
5007 | _markup_get_text_append(Eina_Strbuf *txt, const Eina_Unicode *text) | ||
5008 | { | ||
5009 | char *p = eina_unicode_unicode_to_utf8(text, NULL); | ||
5010 | char *base = p; | ||
5011 | while (*p) | ||
5012 | { | ||
5013 | const char *escape; | ||
5014 | int adv; | ||
5015 | |||
5016 | escape = _escaped_char_match(p, &adv); | ||
5017 | if (escape) | ||
5018 | { | ||
5019 | p += adv; | ||
5020 | eina_strbuf_append(txt, escape); | ||
5021 | } | ||
5022 | else | ||
5023 | { | ||
5024 | eina_strbuf_append_char(txt, *p); | ||
5025 | p++; | ||
5026 | } | ||
5027 | } | ||
5028 | free(base); | ||
5029 | } | ||
5030 | EAPI const char * | ||
5031 | evas_object_textblock_text_markup_get(const Evas_Object *obj) | ||
5032 | { | ||
5033 | Evas_Object_Textblock_Node_Text *n; | ||
5034 | Eina_Strbuf *txt = NULL; | ||
5035 | |||
5036 | TB_HEAD_RETURN(NULL); | ||
5037 | if (o->markup_text) return(o->markup_text); | ||
5038 | txt = eina_strbuf_new(); | ||
5039 | EINA_INLIST_FOREACH(o->text_nodes, n) | ||
5040 | { | ||
5041 | Evas_Object_Textblock_Node_Format *fnode; | ||
5042 | Eina_Unicode *text_base, *text; | ||
5043 | int off; | ||
5044 | |||
5045 | /* For each text node to thorugh all of it's format nodes | ||
5046 | * append text from the start to the offset of the next format | ||
5047 | * using the last format got. if needed it also creates format items | ||
5048 | * this is the core algorithm of the layout mechanism. | ||
5049 | * Skip the unicode replacement chars when there are because | ||
5050 | * we don't want to print them. */ | ||
5051 | text_base = text = | ||
5052 | eina_unicode_strndup(eina_ustrbuf_string_get(n->unicode), | ||
5053 | eina_ustrbuf_length_get(n->unicode)); | ||
5054 | fnode = n->format_node; | ||
5055 | off = 0; | ||
5056 | while (fnode && (fnode->text_node == n)) | ||
5057 | { | ||
5058 | Eina_Unicode tmp_ch; | ||
5059 | off += fnode->offset; | ||
5060 | /* No need to skip on the first run */ | ||
5061 | tmp_ch = text[off]; | ||
5062 | text[off] = 0; /* Null terminate the part of the string */ | ||
5063 | _markup_get_text_append(txt, text); | ||
5064 | _markup_get_format_append(o, txt, fnode); | ||
5065 | text[off] = tmp_ch; /* Restore the char */ | ||
5066 | text += off; | ||
5067 | if (fnode->visible) | ||
5068 | { | ||
5069 | off = -1; | ||
5070 | text++; | ||
5071 | } | ||
5072 | else | ||
5073 | { | ||
5074 | off = 0; | ||
5075 | } | ||
5076 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
5077 | } | ||
5078 | /* Add the rest, skip replacement */ | ||
5079 | _markup_get_text_append(txt, text); | ||
5080 | free(text_base); | ||
5081 | } | ||
5082 | |||
5083 | |||
5084 | o->markup_text = eina_strbuf_string_steal(txt); | ||
5085 | eina_strbuf_free(txt); | ||
5086 | return o->markup_text; | ||
5087 | } | ||
5088 | |||
5089 | /* cursors */ | ||
5090 | |||
5091 | /** | ||
5092 | * @internal | ||
5093 | * Merge the current node with the next, no need to remove PS, already | ||
5094 | * not there. | ||
5095 | * | ||
5096 | * @param o the text block object. | ||
5097 | * @param to merge into to. | ||
5098 | */ | ||
5099 | static void | ||
5100 | _evas_textblock_nodes_merge(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *to) | ||
5101 | { | ||
5102 | Evas_Object_Textblock_Node_Format *itr; | ||
5103 | Evas_Object_Textblock_Node_Format *pnode; | ||
5104 | Evas_Object_Textblock_Node_Text *from; | ||
5105 | const Eina_Unicode *text; | ||
5106 | int to_len, len; | ||
5107 | |||
5108 | if (!to) return; | ||
5109 | from = _NODE_TEXT(EINA_INLIST_GET(to)->next); | ||
5110 | |||
5111 | to_len = eina_ustrbuf_length_get(to->unicode); | ||
5112 | text = eina_ustrbuf_string_get(from->unicode); | ||
5113 | len = eina_ustrbuf_length_get(from->unicode); | ||
5114 | eina_ustrbuf_append_length(to->unicode, text, len); | ||
5115 | |||
5116 | itr = from->format_node; | ||
5117 | if (itr && (itr->text_node == from)) | ||
5118 | { | ||
5119 | pnode = _NODE_FORMAT(EINA_INLIST_GET(itr)->prev); | ||
5120 | if (pnode && (pnode->text_node == to)) | ||
5121 | { | ||
5122 | itr->offset += to_len - _evas_textblock_node_format_pos_get(pnode); | ||
5123 | } | ||
5124 | else | ||
5125 | { | ||
5126 | itr->offset += to_len; | ||
5127 | } | ||
5128 | } | ||
5129 | |||
5130 | while (itr && (itr->text_node == from)) | ||
5131 | { | ||
5132 | itr->text_node = to; | ||
5133 | itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
5134 | } | ||
5135 | if (!to->format_node || (to->format_node->text_node != to)) | ||
5136 | { | ||
5137 | to->format_node = from->format_node; | ||
5138 | } | ||
5139 | |||
5140 | /* When it comes to how we handle it, merging is like removing both nodes | ||
5141 | * and creating a new one, se we need to do the needed cleanups. */ | ||
5142 | if (to->par) | ||
5143 | to->par->text_node = NULL; | ||
5144 | to->par = NULL; | ||
5145 | |||
5146 | to->is_new = EINA_TRUE; | ||
5147 | |||
5148 | _evas_textblock_cursors_set_node(o, from, to); | ||
5149 | _evas_textblock_node_text_remove(o, from); | ||
5150 | } | ||
5151 | |||
5152 | /** | ||
5153 | * @internal | ||
5154 | * Merge the current node with the next, no need to remove PS, already | ||
5155 | * not there. | ||
5156 | * | ||
5157 | * @param cur the cursor that points to the current node | ||
5158 | */ | ||
5159 | static void | ||
5160 | _evas_textblock_cursor_nodes_merge(Evas_Textblock_Cursor *cur) | ||
5161 | { | ||
5162 | Evas_Object_Textblock_Node_Text *nnode; | ||
5163 | Evas_Object_Textblock *o; | ||
5164 | int len; | ||
5165 | if (!cur) return; | ||
5166 | |||
5167 | len = eina_ustrbuf_length_get(cur->node->unicode); | ||
5168 | |||
5169 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5170 | nnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->next); | ||
5171 | _evas_textblock_nodes_merge(o, cur->node); | ||
5172 | _evas_textblock_cursors_update_offset(cur, nnode, 0, len); | ||
5173 | _evas_textblock_cursors_set_node(o, nnode, cur->node); | ||
5174 | if (nnode == o->cursor->node) | ||
5175 | { | ||
5176 | o->cursor->node = cur->node; | ||
5177 | o->cursor->pos += len; | ||
5178 | } | ||
5179 | } | ||
5180 | |||
5181 | /** | ||
5182 | * @internal | ||
5183 | * Return the format at a specific position. | ||
5184 | * | ||
5185 | * @param cur the cursor to the position. | ||
5186 | * @return the format node at the specific position or NULL if not found. | ||
5187 | */ | ||
5188 | static Evas_Object_Textblock_Node_Format * | ||
5189 | _evas_textblock_cursor_node_format_at_pos_get(const Evas_Textblock_Cursor *cur) | ||
5190 | { | ||
5191 | Evas_Object_Textblock_Node_Format *node; | ||
5192 | Evas_Object_Textblock_Node_Format *itr; | ||
5193 | int position = 0; | ||
5194 | |||
5195 | if (!cur->node) return NULL; | ||
5196 | |||
5197 | node = cur->node->format_node; | ||
5198 | if (!node) return NULL; | ||
5199 | /* If there is no exclusive format node to this paragraph return the | ||
5200 | * previous's node */ | ||
5201 | /* Find the main format node */ | ||
5202 | EINA_INLIST_FOREACH(node, itr) | ||
5203 | { | ||
5204 | if (itr->text_node != cur->node) | ||
5205 | { | ||
5206 | return NULL; | ||
5207 | } | ||
5208 | if ((position + itr->offset) == cur->pos) | ||
5209 | { | ||
5210 | return itr; | ||
5211 | } | ||
5212 | position += itr->offset; | ||
5213 | } | ||
5214 | return NULL; | ||
5215 | } | ||
5216 | |||
5217 | /** | ||
5218 | * @internal | ||
5219 | * Return the last format node at the position of the format node n. | ||
5220 | * | ||
5221 | * @param n a format node at the position. | ||
5222 | * @return the last format node at the position of n. | ||
5223 | */ | ||
5224 | static Evas_Object_Textblock_Node_Format * | ||
5225 | _evas_textblock_node_format_last_at_off(const Evas_Object_Textblock_Node_Format *n) | ||
5226 | { | ||
5227 | const Evas_Object_Textblock_Node_Format *nnode; | ||
5228 | const Evas_Object_Textblock_Node_Text *tnode; | ||
5229 | if (!n) return NULL; | ||
5230 | nnode = n; | ||
5231 | tnode = n->text_node; | ||
5232 | do | ||
5233 | { | ||
5234 | n = nnode; | ||
5235 | nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); | ||
5236 | } | ||
5237 | while (nnode && (nnode->text_node == tnode) && (nnode->offset == 0)); | ||
5238 | |||
5239 | return (Evas_Object_Textblock_Node_Format *) n; | ||
5240 | } | ||
5241 | |||
5242 | /** | ||
5243 | * @internal | ||
5244 | * Returns the visible format at a specific location. | ||
5245 | * | ||
5246 | * @param n a format at the specific position. | ||
5247 | * @return the format node at the specific position or NULL if not found. | ||
5248 | */ | ||
5249 | static Evas_Object_Textblock_Node_Format * | ||
5250 | _evas_textblock_node_visible_at_pos_get(const Evas_Object_Textblock_Node_Format *n) | ||
5251 | { | ||
5252 | const Evas_Object_Textblock_Node_Format *nnode; | ||
5253 | if (!n) return NULL; | ||
5254 | /* The visible format is the last one, because it inserts a replacement | ||
5255 | * char that advances the next formats. */ | ||
5256 | |||
5257 | nnode = n; | ||
5258 | do | ||
5259 | { | ||
5260 | n = nnode; | ||
5261 | if (n->visible) return (Evas_Object_Textblock_Node_Format *) n; | ||
5262 | nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); | ||
5263 | } | ||
5264 | while (nnode && (nnode->offset == 0)); | ||
5265 | |||
5266 | return NULL; | ||
5267 | } | ||
5268 | |||
5269 | /** | ||
5270 | * @internal | ||
5271 | * Return the last format that applies to a specific cursor or at the specific | ||
5272 | * position the cursor points to. This means either a cursor at or before the | ||
5273 | * position of the cursor in the text node is returned or the previous's text | ||
5274 | * node's format node. | ||
5275 | * | ||
5276 | * @param cur the position to look at. | ||
5277 | * @return the format node found. | ||
5278 | */ | ||
5279 | static Evas_Object_Textblock_Node_Format * | ||
5280 | _evas_textblock_cursor_node_format_before_or_at_pos_get(const Evas_Textblock_Cursor *cur) | ||
5281 | { | ||
5282 | Evas_Object_Textblock_Node_Format *node, *pitr = NULL; | ||
5283 | Evas_Object_Textblock_Node_Format *itr; | ||
5284 | size_t position = 0; | ||
5285 | |||
5286 | if (!cur->node) return NULL; | ||
5287 | |||
5288 | node = cur->node->format_node; | ||
5289 | if (!node) return NULL; | ||
5290 | /* If there is no exclusive format node to this paragraph return the | ||
5291 | * previous's node */ | ||
5292 | if (node->text_node != cur->node) | ||
5293 | { | ||
5294 | return node; | ||
5295 | } | ||
5296 | else if (node->offset > cur->pos) | ||
5297 | { | ||
5298 | return _NODE_FORMAT(EINA_INLIST_GET(node)->prev); | ||
5299 | } | ||
5300 | /* Find the main format node */ | ||
5301 | pitr = _NODE_FORMAT(EINA_INLIST_GET(node)->prev); | ||
5302 | EINA_INLIST_FOREACH(node, itr) | ||
5303 | { | ||
5304 | if ((itr->text_node != cur->node) || | ||
5305 | ((position + itr->offset) > cur->pos)) | ||
5306 | { | ||
5307 | return pitr; | ||
5308 | } | ||
5309 | else if ((position + itr->offset) == cur->pos) | ||
5310 | { | ||
5311 | return itr; | ||
5312 | } | ||
5313 | pitr = itr; | ||
5314 | position += itr->offset; | ||
5315 | } | ||
5316 | return pitr; | ||
5317 | } | ||
5318 | |||
5319 | /** | ||
5320 | * @internal | ||
5321 | * Find the layout item and line that match the cursor. | ||
5322 | * | ||
5323 | * @param cur the cursor we are currently at. - NOT NULL. | ||
5324 | * @param[out] lnr the line found - not null. | ||
5325 | * @param[out] itr the item found - not null. | ||
5326 | * @return EINA_TRUE if we matched the previous format, EINA_FALSE otherwise. | ||
5327 | */ | ||
5328 | static Eina_Bool | ||
5329 | _find_layout_item_match(const Evas_Textblock_Cursor *cur, Evas_Object_Textblock_Line **lnr, Evas_Object_Textblock_Item **itr) | ||
5330 | { | ||
5331 | Evas_Textblock_Cursor cur2; | ||
5332 | Eina_Bool previous_format = EINA_FALSE; | ||
5333 | |||
5334 | cur2.obj = cur->obj; | ||
5335 | evas_textblock_cursor_copy(cur, &cur2); | ||
5336 | if (cur2.pos > 0) | ||
5337 | { | ||
5338 | cur2.pos--; | ||
5339 | } | ||
5340 | |||
5341 | if (_evas_textblock_cursor_is_at_the_end(cur) && | ||
5342 | evas_textblock_cursor_format_is_visible_get(&cur2)) | ||
5343 | { | ||
5344 | _find_layout_item_line_match(cur2.obj, cur2.node, cur2.pos, lnr, itr); | ||
5345 | previous_format = EINA_TRUE; | ||
5346 | } | ||
5347 | else | ||
5348 | { | ||
5349 | _find_layout_item_line_match(cur->obj, cur->node, cur->pos, lnr, itr); | ||
5350 | } | ||
5351 | return previous_format; | ||
5352 | } | ||
5353 | |||
5354 | EAPI Evas_Textblock_Cursor * | ||
5355 | evas_object_textblock_cursor_get(const Evas_Object *obj) | ||
5356 | { | ||
5357 | TB_HEAD_RETURN(NULL); | ||
5358 | return o->cursor; | ||
5359 | } | ||
5360 | |||
5361 | EAPI Evas_Textblock_Cursor * | ||
5362 | evas_object_textblock_cursor_new(const Evas_Object *obj) | ||
5363 | { | ||
5364 | Evas_Textblock_Cursor *cur; | ||
5365 | |||
5366 | TB_HEAD_RETURN(NULL); | ||
5367 | cur = calloc(1, sizeof(Evas_Textblock_Cursor)); | ||
5368 | cur->obj = (Evas_Object *) obj; | ||
5369 | cur->node = o->text_nodes; | ||
5370 | cur->pos = 0; | ||
5371 | |||
5372 | o->cursors = eina_list_append(o->cursors, cur); | ||
5373 | return cur; | ||
5374 | } | ||
5375 | |||
5376 | EAPI void | ||
5377 | evas_textblock_cursor_free(Evas_Textblock_Cursor *cur) | ||
5378 | { | ||
5379 | Evas_Object_Textblock *o; | ||
5380 | |||
5381 | if (!cur) return; | ||
5382 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5383 | if (cur == o->cursor) return; | ||
5384 | o->cursors = eina_list_remove(o->cursors, cur); | ||
5385 | free(cur); | ||
5386 | } | ||
5387 | |||
5388 | EAPI Eina_Bool | ||
5389 | evas_textblock_cursor_is_format(const Evas_Textblock_Cursor *cur) | ||
5390 | { | ||
5391 | if (!cur || !cur->node) return EINA_FALSE; | ||
5392 | if (evas_textblock_cursor_format_is_visible_get(cur)) return EINA_TRUE; | ||
5393 | return (_evas_textblock_cursor_node_format_at_pos_get(cur)) ? | ||
5394 | EINA_TRUE : EINA_FALSE; | ||
5395 | } | ||
5396 | |||
5397 | EAPI const Eina_List * | ||
5398 | evas_textblock_node_format_list_get(const Evas_Object *obj, const char *anchor) | ||
5399 | { | ||
5400 | TB_HEAD_RETURN(NULL); | ||
5401 | if (!strcmp(anchor, "a")) | ||
5402 | return o->anchors_a; | ||
5403 | else if (!strcmp(anchor, "item")) | ||
5404 | return o->anchors_item; | ||
5405 | |||
5406 | return NULL; | ||
5407 | } | ||
5408 | |||
5409 | EAPI const Evas_Object_Textblock_Node_Format * | ||
5410 | evas_textblock_node_format_first_get(const Evas_Object *obj) | ||
5411 | { | ||
5412 | TB_HEAD_RETURN(NULL); | ||
5413 | return o->format_nodes; | ||
5414 | } | ||
5415 | |||
5416 | EAPI const Evas_Object_Textblock_Node_Format * | ||
5417 | evas_textblock_node_format_last_get(const Evas_Object *obj) | ||
5418 | { | ||
5419 | TB_HEAD_RETURN(NULL); | ||
5420 | if (o->format_nodes) | ||
5421 | { | ||
5422 | return _NODE_FORMAT(EINA_INLIST_GET(o->format_nodes)->last); | ||
5423 | } | ||
5424 | return NULL; | ||
5425 | } | ||
5426 | |||
5427 | EAPI const Evas_Object_Textblock_Node_Format * | ||
5428 | evas_textblock_node_format_next_get(const Evas_Object_Textblock_Node_Format *n) | ||
5429 | { | ||
5430 | return _NODE_FORMAT(EINA_INLIST_GET(n)->next); | ||
5431 | } | ||
5432 | |||
5433 | EAPI const Evas_Object_Textblock_Node_Format * | ||
5434 | evas_textblock_node_format_prev_get(const Evas_Object_Textblock_Node_Format *n) | ||
5435 | { | ||
5436 | return _NODE_FORMAT(EINA_INLIST_GET(n)->prev); | ||
5437 | } | ||
5438 | |||
5439 | EAPI void | ||
5440 | evas_textblock_node_format_remove_pair(Evas_Object *obj, | ||
5441 | Evas_Object_Textblock_Node_Format *n) | ||
5442 | { | ||
5443 | Evas_Object_Textblock_Node_Text *tnode1; | ||
5444 | Evas_Object_Textblock_Node_Format *fmt, *found_node = NULL; | ||
5445 | Eina_List *fstack = NULL; | ||
5446 | TB_HEAD(); | ||
5447 | |||
5448 | if (!n) return; | ||
5449 | |||
5450 | fmt = n; | ||
5451 | |||
5452 | do | ||
5453 | { | ||
5454 | const char *fstr = fmt->orig_format; | ||
5455 | |||
5456 | if (fstr && (*fstr == '+')) | ||
5457 | { | ||
5458 | fstack = eina_list_prepend(fstack, fmt); | ||
5459 | } | ||
5460 | else if (fstr && (*fstr == '-')) | ||
5461 | { | ||
5462 | size_t fstr_len; | ||
5463 | /* Skip the '-' */ | ||
5464 | fstr++; | ||
5465 | fstr_len = strlen(fstr); | ||
5466 | /* Generic popper, just pop */ | ||
5467 | if (((fstr[0] == ' ') && !fstr[1]) || !fstr[0]) | ||
5468 | { | ||
5469 | fstack = eina_list_remove_list(fstack, fstack); | ||
5470 | if (!fstack) | ||
5471 | { | ||
5472 | found_node = fmt; | ||
5473 | goto found; | ||
5474 | } | ||
5475 | } | ||
5476 | /* Find the matching format and pop it, if the matching format | ||
5477 | * is out format, i.e the last one, pop and break. */ | ||
5478 | else | ||
5479 | { | ||
5480 | Eina_List *i; | ||
5481 | Evas_Object_Textblock_Node_Format *fnode; | ||
5482 | EINA_LIST_FOREACH(fstack, i, fnode) | ||
5483 | { | ||
5484 | if (_FORMAT_IS_CLOSER_OF( | ||
5485 | fnode->orig_format, fstr, fstr_len)) | ||
5486 | { | ||
5487 | /* Last one, this is our item! */ | ||
5488 | if (!eina_list_next(i)) | ||
5489 | { | ||
5490 | found_node = fmt; | ||
5491 | goto found; | ||
5492 | } | ||
5493 | fstack = eina_list_remove_list(fstack, i); | ||
5494 | break; | ||
5495 | } | ||
5496 | } | ||
5497 | } | ||
5498 | } | ||
5499 | |||
5500 | fmt = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next); | ||
5501 | } | ||
5502 | while (fmt && fstack); | ||
5503 | |||
5504 | found: | ||
5505 | |||
5506 | fstack = eina_list_free(fstack); | ||
5507 | |||
5508 | if (n->visible) | ||
5509 | { | ||
5510 | size_t ind = _evas_textblock_node_format_pos_get(n); | ||
5511 | const char *format = n->format; | ||
5512 | Evas_Textblock_Cursor cur; | ||
5513 | cur.obj = obj; | ||
5514 | |||
5515 | eina_ustrbuf_remove(n->text_node->unicode, ind, ind + 1); | ||
5516 | if (format && _IS_PARAGRAPH_SEPARATOR(o, format)) | ||
5517 | { | ||
5518 | evas_textblock_cursor_at_format_set(&cur, n); | ||
5519 | _evas_textblock_cursor_nodes_merge(&cur); | ||
5520 | } | ||
5521 | _evas_textblock_cursors_update_offset(&cur, n->text_node, ind, -1); | ||
5522 | } | ||
5523 | tnode1 = n->text_node; | ||
5524 | _evas_textblock_node_format_remove(o, n, 0); | ||
5525 | if (found_node && (found_node != n)) | ||
5526 | { | ||
5527 | Evas_Object_Textblock_Node_Text *tnode2; | ||
5528 | tnode2 = found_node->text_node; | ||
5529 | /* found_node can never be visible! (it's the closing format) */ | ||
5530 | _evas_textblock_node_format_remove(o, found_node, 0); | ||
5531 | |||
5532 | /* FIXME: Should be unified in the layout, for example, added to a list | ||
5533 | * that checks this kind of removals. But until then, this is very fast | ||
5534 | * and works. */ | ||
5535 | /* Mark all the text nodes in between the removed formats as dirty. */ | ||
5536 | while (tnode1) | ||
5537 | { | ||
5538 | tnode1->dirty = EINA_TRUE; | ||
5539 | if (tnode1 == tnode2) | ||
5540 | break; | ||
5541 | tnode1 = | ||
5542 | _NODE_TEXT(EINA_INLIST_GET(tnode1)->next); | ||
5543 | } | ||
5544 | } | ||
5545 | |||
5546 | _evas_textblock_changed(o, obj); | ||
5547 | } | ||
5548 | |||
5549 | EAPI void | ||
5550 | evas_textblock_cursor_paragraph_first(Evas_Textblock_Cursor *cur) | ||
5551 | { | ||
5552 | Evas_Object_Textblock *o; | ||
5553 | if (!cur) return; | ||
5554 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5555 | cur->node = o->text_nodes; | ||
5556 | cur->pos = 0; | ||
5557 | |||
5558 | } | ||
5559 | |||
5560 | EAPI void | ||
5561 | evas_textblock_cursor_paragraph_last(Evas_Textblock_Cursor *cur) | ||
5562 | { | ||
5563 | Evas_Object_Textblock *o; | ||
5564 | Evas_Object_Textblock_Node_Text *node; | ||
5565 | |||
5566 | if (!cur) return; | ||
5567 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5568 | node = o->text_nodes; | ||
5569 | if (node) | ||
5570 | { | ||
5571 | node = _NODE_TEXT(EINA_INLIST_GET(node)->last); | ||
5572 | cur->node = node; | ||
5573 | cur->pos = 0; | ||
5574 | |||
5575 | evas_textblock_cursor_paragraph_char_last(cur); | ||
5576 | } | ||
5577 | else | ||
5578 | { | ||
5579 | cur->node = NULL; | ||
5580 | cur->pos = 0; | ||
5581 | |||
5582 | } | ||
5583 | } | ||
5584 | |||
5585 | EAPI Eina_Bool | ||
5586 | evas_textblock_cursor_paragraph_next(Evas_Textblock_Cursor *cur) | ||
5587 | { | ||
5588 | if (!cur) return EINA_FALSE; | ||
5589 | if (!cur->node) return EINA_FALSE; | ||
5590 | /* If there is a current text node, return the next text node (if exists) | ||
5591 | * otherwise, just return False. */ | ||
5592 | if (cur->node) | ||
5593 | { | ||
5594 | Evas_Object_Textblock_Node_Text *nnode; | ||
5595 | nnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->next); | ||
5596 | if (nnode) | ||
5597 | { | ||
5598 | cur->node = nnode; | ||
5599 | cur->pos = 0; | ||
5600 | |||
5601 | return EINA_TRUE; | ||
5602 | } | ||
5603 | } | ||
5604 | return EINA_FALSE; | ||
5605 | } | ||
5606 | |||
5607 | EAPI Eina_Bool | ||
5608 | evas_textblock_cursor_paragraph_prev(Evas_Textblock_Cursor *cur) | ||
5609 | { | ||
5610 | Evas_Object_Textblock_Node_Text *node; | ||
5611 | if (!cur) return EINA_FALSE; | ||
5612 | if (!cur->node) return EINA_FALSE; | ||
5613 | /* If the current node is a text node, just get the prev if any, | ||
5614 | * if it's a format, get the current text node out of the format and return | ||
5615 | * the prev text node if any. */ | ||
5616 | node = cur->node; | ||
5617 | /* If there is a current text node, return the prev text node | ||
5618 | * (if exists) otherwise, just return False. */ | ||
5619 | if (node) | ||
5620 | { | ||
5621 | Evas_Object_Textblock_Node_Text *pnode; | ||
5622 | pnode = _NODE_TEXT(EINA_INLIST_GET(cur->node)->prev); | ||
5623 | if (pnode) | ||
5624 | { | ||
5625 | cur->node = pnode; | ||
5626 | evas_textblock_cursor_paragraph_char_last(cur); | ||
5627 | return EINA_TRUE; | ||
5628 | } | ||
5629 | } | ||
5630 | return EINA_FALSE; | ||
5631 | } | ||
5632 | |||
5633 | EAPI void | ||
5634 | evas_textblock_cursor_set_at_format(Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Format *n) | ||
5635 | { | ||
5636 | evas_textblock_cursor_at_format_set(cur, n); | ||
5637 | } | ||
5638 | |||
5639 | EAPI Eina_Bool | ||
5640 | evas_textblock_cursor_format_next(Evas_Textblock_Cursor *cur) | ||
5641 | { | ||
5642 | Evas_Object_Textblock_Node_Format *node; | ||
5643 | |||
5644 | if (!cur) return EINA_FALSE; | ||
5645 | if (!cur->node) return EINA_FALSE; | ||
5646 | /* If the current node is a format node, just get the next if any, | ||
5647 | * if it's a text, get the current format node out of the text and return | ||
5648 | * the next format node if any. */ | ||
5649 | node = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
5650 | node = _evas_textblock_node_format_last_at_off(node); | ||
5651 | if (!node) | ||
5652 | { | ||
5653 | if (cur->node->format_node) | ||
5654 | { | ||
5655 | cur->pos = _evas_textblock_node_format_pos_get(node); | ||
5656 | return EINA_TRUE; | ||
5657 | } | ||
5658 | } | ||
5659 | /* If there is a current text node, return the next format node (if exists) | ||
5660 | * otherwise, just return False. */ | ||
5661 | else | ||
5662 | { | ||
5663 | Evas_Object_Textblock_Node_Format *nnode; | ||
5664 | nnode = _NODE_FORMAT(EINA_INLIST_GET(node)->next); | ||
5665 | if (nnode) | ||
5666 | { | ||
5667 | cur->node = nnode->text_node; | ||
5668 | cur->pos = _evas_textblock_node_format_pos_get(nnode); | ||
5669 | |||
5670 | return EINA_TRUE; | ||
5671 | } | ||
5672 | } | ||
5673 | return EINA_FALSE; | ||
5674 | } | ||
5675 | |||
5676 | EAPI Eina_Bool | ||
5677 | evas_textblock_cursor_format_prev(Evas_Textblock_Cursor *cur) | ||
5678 | { | ||
5679 | const Evas_Object_Textblock_Node_Format *node; | ||
5680 | if (!cur) return EINA_FALSE; | ||
5681 | if (!cur->node) return EINA_FALSE; | ||
5682 | node = evas_textblock_cursor_format_get(cur); | ||
5683 | if (!node) | ||
5684 | { | ||
5685 | node = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
5686 | if (node) | ||
5687 | { | ||
5688 | cur->node = node->text_node; | ||
5689 | cur->pos = _evas_textblock_node_format_pos_get(node); | ||
5690 | |||
5691 | return EINA_TRUE; | ||
5692 | } | ||
5693 | } | ||
5694 | /* If there is a current text node, return the next text node (if exists) | ||
5695 | * otherwise, just return False. */ | ||
5696 | if (node) | ||
5697 | { | ||
5698 | Evas_Object_Textblock_Node_Format *pnode; | ||
5699 | pnode = _NODE_FORMAT(EINA_INLIST_GET(node)->prev); | ||
5700 | if (pnode) | ||
5701 | { | ||
5702 | cur->node = pnode->text_node; | ||
5703 | cur->pos = _evas_textblock_node_format_pos_get(pnode); | ||
5704 | |||
5705 | return EINA_TRUE; | ||
5706 | } | ||
5707 | } | ||
5708 | return EINA_FALSE; | ||
5709 | } | ||
5710 | |||
5711 | EAPI Eina_Bool | ||
5712 | evas_textblock_cursor_char_next(Evas_Textblock_Cursor *cur) | ||
5713 | { | ||
5714 | int ind; | ||
5715 | const Eina_Unicode *text; | ||
5716 | |||
5717 | if (!cur) return EINA_FALSE; | ||
5718 | if (!cur->node) return EINA_FALSE; | ||
5719 | |||
5720 | ind = cur->pos; | ||
5721 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
5722 | if (text[ind]) ind++; | ||
5723 | /* Only allow pointing a null if it's the last paragraph. | ||
5724 | * because we don't have a PS there. */ | ||
5725 | if (text[ind]) | ||
5726 | { | ||
5727 | cur->pos = ind; | ||
5728 | return EINA_TRUE; | ||
5729 | } | ||
5730 | else | ||
5731 | { | ||
5732 | if (!evas_textblock_cursor_paragraph_next(cur)) | ||
5733 | { | ||
5734 | /* If we already were at the end, that means we don't have | ||
5735 | * where to go next we should return FALSE */ | ||
5736 | if (cur->pos == (size_t) ind) | ||
5737 | return EINA_FALSE; | ||
5738 | |||
5739 | cur->pos = ind; | ||
5740 | return EINA_TRUE; | ||
5741 | } | ||
5742 | else | ||
5743 | { | ||
5744 | return EINA_TRUE; | ||
5745 | } | ||
5746 | } | ||
5747 | } | ||
5748 | |||
5749 | EAPI Eina_Bool | ||
5750 | evas_textblock_cursor_char_prev(Evas_Textblock_Cursor *cur) | ||
5751 | { | ||
5752 | if (!cur) return EINA_FALSE; | ||
5753 | if (!cur->node) return EINA_FALSE; | ||
5754 | |||
5755 | if (cur->pos != 0) | ||
5756 | { | ||
5757 | cur->pos--; | ||
5758 | return EINA_TRUE; | ||
5759 | } | ||
5760 | return evas_textblock_cursor_paragraph_prev(cur); | ||
5761 | } | ||
5762 | |||
5763 | EAPI void | ||
5764 | evas_textblock_cursor_paragraph_char_first(Evas_Textblock_Cursor *cur) | ||
5765 | { | ||
5766 | if (!cur) return; | ||
5767 | cur->pos = 0; | ||
5768 | |||
5769 | } | ||
5770 | |||
5771 | EAPI void | ||
5772 | evas_textblock_cursor_paragraph_char_last(Evas_Textblock_Cursor *cur) | ||
5773 | { | ||
5774 | int ind; | ||
5775 | |||
5776 | if (!cur) return; | ||
5777 | if (!cur->node) return; | ||
5778 | ind = eina_ustrbuf_length_get(cur->node->unicode); | ||
5779 | /* If it's not the last paragraph, go back one, because we want to point | ||
5780 | * to the PS, not the NULL */ | ||
5781 | if (EINA_INLIST_GET(cur->node)->next) | ||
5782 | ind--; | ||
5783 | |||
5784 | if (ind >= 0) | ||
5785 | cur->pos = ind; | ||
5786 | else | ||
5787 | cur->pos = 0; | ||
5788 | |||
5789 | } | ||
5790 | |||
5791 | EAPI void | ||
5792 | evas_textblock_cursor_line_char_first(Evas_Textblock_Cursor *cur) | ||
5793 | { | ||
5794 | Evas_Object_Textblock *o; | ||
5795 | Evas_Object_Textblock_Line *ln = NULL; | ||
5796 | Evas_Object_Textblock_Item *it = NULL; | ||
5797 | |||
5798 | if (!cur) return; | ||
5799 | if (!cur->node) return; | ||
5800 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5801 | if (!o->formatted.valid) _relayout(cur->obj); | ||
5802 | |||
5803 | _find_layout_item_match(cur, &ln, &it); | ||
5804 | |||
5805 | if (!ln) return; | ||
5806 | if (ln->items) | ||
5807 | { | ||
5808 | Evas_Object_Textblock_Item *i; | ||
5809 | it = ln->items; | ||
5810 | EINA_INLIST_FOREACH(ln->items, i) | ||
5811 | { | ||
5812 | if (it->text_pos > i->text_pos) | ||
5813 | { | ||
5814 | it = i; | ||
5815 | } | ||
5816 | } | ||
5817 | } | ||
5818 | if (it) | ||
5819 | { | ||
5820 | cur->pos = it->text_pos; | ||
5821 | cur->node = it->text_node; | ||
5822 | } | ||
5823 | } | ||
5824 | |||
5825 | EAPI void | ||
5826 | evas_textblock_cursor_line_char_last(Evas_Textblock_Cursor *cur) | ||
5827 | { | ||
5828 | Evas_Object_Textblock *o; | ||
5829 | Evas_Object_Textblock_Line *ln = NULL; | ||
5830 | Evas_Object_Textblock_Item *it = NULL; | ||
5831 | |||
5832 | if (!cur) return; | ||
5833 | if (!cur->node) return; | ||
5834 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
5835 | if (!o->formatted.valid) _relayout(cur->obj); | ||
5836 | |||
5837 | _find_layout_item_match(cur, &ln, &it); | ||
5838 | |||
5839 | if (!ln) return; | ||
5840 | if (ln->items) | ||
5841 | { | ||
5842 | Evas_Object_Textblock_Item *i; | ||
5843 | it = ln->items; | ||
5844 | EINA_INLIST_FOREACH(ln->items, i) | ||
5845 | { | ||
5846 | if (it->text_pos < i->text_pos) | ||
5847 | { | ||
5848 | it = i; | ||
5849 | } | ||
5850 | } | ||
5851 | } | ||
5852 | if (it) | ||
5853 | { | ||
5854 | size_t ind; | ||
5855 | |||
5856 | cur->node = it->text_node; | ||
5857 | cur->pos = it->text_pos; | ||
5858 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
5859 | { | ||
5860 | ind = _ITEM_TEXT(it)->text_props.text_len - 1; | ||
5861 | if (!IS_AT_END(_ITEM_TEXT(it), ind)) ind++; | ||
5862 | cur->pos += ind; | ||
5863 | } | ||
5864 | else if (!EINA_INLIST_GET(ln)->next && !EINA_INLIST_GET(ln->par)->next) | ||
5865 | { | ||
5866 | cur->pos++; | ||
5867 | } | ||
5868 | } | ||
5869 | } | ||
5870 | |||
5871 | /** | ||
5872 | * @internal | ||
5873 | * checks if a format (as a string) is visible/changes format and sets the | ||
5874 | * fnode properties accordingly. | ||
5875 | * | ||
5876 | * @param fnode the format node | ||
5877 | * @param s the string. | ||
5878 | */ | ||
5879 | static void | ||
5880 | _evas_textblock_format_is_visible(Evas_Object_Textblock_Node_Format *fnode, | ||
5881 | const char *s) | ||
5882 | { | ||
5883 | const char *item; | ||
5884 | Eina_Bool is_opener = EINA_TRUE; | ||
5885 | |||
5886 | fnode->visible = fnode->format_change = EINA_FALSE; | ||
5887 | fnode->anchor = ANCHOR_NONE; | ||
5888 | if (!s) return; | ||
5889 | |||
5890 | if (s[0] == '+' || s[0] == '-') | ||
5891 | { | ||
5892 | is_opener = (s[0] == '+'); | ||
5893 | s++; | ||
5894 | fnode->format_change = EINA_TRUE; | ||
5895 | } | ||
5896 | |||
5897 | while ((item = _format_parse(&s))) | ||
5898 | { | ||
5899 | int itlen = s - item; | ||
5900 | /* We care about all of the formats even after a - except for | ||
5901 | * item which we don't care after a - because it's just a standard | ||
5902 | * closing */ | ||
5903 | if ((!strncmp(item, "\n", itlen) || !strncmp(item, "\\n", itlen)) || | ||
5904 | (!strncmp(item, "\t", itlen) || !strncmp(item, "\\t", itlen)) || | ||
5905 | (!strncmp(item, "ps", itlen) && (itlen >= 2)) || | ||
5906 | (!strncmp(item, "item", itlen) && (itlen >= 4) && is_opener)) | ||
5907 | { | ||
5908 | fnode->visible = EINA_TRUE; | ||
5909 | } | ||
5910 | |||
5911 | if (is_opener && !strncmp(item, "a", itlen)) | ||
5912 | { | ||
5913 | fnode->anchor = ANCHOR_A; | ||
5914 | } | ||
5915 | else if (is_opener && !strncmp(item, "item", itlen) && (itlen >= 4)) | ||
5916 | { | ||
5917 | fnode->anchor = ANCHOR_ITEM; | ||
5918 | } | ||
5919 | } | ||
5920 | } | ||
5921 | |||
5922 | /** | ||
5923 | * Sets the cursor to the position of where the fmt points to. | ||
5924 | * | ||
5925 | * @param cur the cursor to update. | ||
5926 | * @param fmt the format to set according to. | ||
5927 | * @return nothing. | ||
5928 | */ | ||
5929 | static void __UNUSED__ | ||
5930 | _evas_textblock_cursor_node_text_at_format(Evas_Textblock_Cursor *cur, Evas_Object_Textblock_Node_Format *fmt) | ||
5931 | { | ||
5932 | Evas_Object_Textblock_Node_Text *text; | ||
5933 | Evas_Object_Textblock_Node_Format *base_format; | ||
5934 | Evas_Object_Textblock_Node_Format *itr; | ||
5935 | size_t position = 0; | ||
5936 | |||
5937 | if (!cur || !fmt) return; | ||
5938 | /* Find the main format node */ | ||
5939 | text = fmt->text_node; | ||
5940 | cur->node = text; | ||
5941 | base_format = text->format_node; | ||
5942 | EINA_INLIST_FOREACH(base_format, itr) | ||
5943 | { | ||
5944 | if (itr == fmt) | ||
5945 | { | ||
5946 | break; | ||
5947 | } | ||
5948 | position += itr->offset; | ||
5949 | } | ||
5950 | cur->pos = position; | ||
5951 | |||
5952 | } | ||
5953 | |||
5954 | |||
5955 | /** | ||
5956 | * @internal | ||
5957 | * Remove pairs of + and - formats and also remove formats without + or - | ||
5958 | * i.e formats that pair to themselves. Only removes invisible formats | ||
5959 | * that pair themselves, if you want to remove invisible formats that pair | ||
5960 | * themselves, please first change fmt->visible to EINA_FALSE. | ||
5961 | * | ||
5962 | * @param o the textblock object. | ||
5963 | * @param fmt the current format. | ||
5964 | */ | ||
5965 | static void | ||
5966 | _evas_textblock_node_format_remove_matching(Evas_Object_Textblock *o, | ||
5967 | Evas_Object_Textblock_Node_Format *fmt) | ||
5968 | { | ||
5969 | Evas_Object_Textblock_Node_Text *tnode; | ||
5970 | Eina_List *formats = NULL; | ||
5971 | size_t offset = 0; | ||
5972 | |||
5973 | if (!fmt) return; | ||
5974 | |||
5975 | tnode = fmt->text_node; | ||
5976 | |||
5977 | do | ||
5978 | { | ||
5979 | Evas_Object_Textblock_Node_Format *nnode; | ||
5980 | const char *fstr = fmt->orig_format; | ||
5981 | |||
5982 | nnode = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next); | ||
5983 | if (nnode) | ||
5984 | { | ||
5985 | offset = nnode->offset; | ||
5986 | } | ||
5987 | |||
5988 | |||
5989 | if (fstr && (*fstr == '+')) | ||
5990 | { | ||
5991 | formats = eina_list_prepend(formats, fmt); | ||
5992 | } | ||
5993 | else if (fstr && (*fstr == '-')) | ||
5994 | { | ||
5995 | Evas_Object_Textblock_Node_Format *fnode; | ||
5996 | size_t fstr_len; | ||
5997 | /* Skip the '-' */ | ||
5998 | fstr++; | ||
5999 | fstr_len = strlen(fstr); | ||
6000 | /* Generic popper, just pop */ | ||
6001 | if (((fstr[0] == ' ') && !fstr[1]) || !fstr[0]) | ||
6002 | { | ||
6003 | fnode = eina_list_data_get(formats); | ||
6004 | formats = eina_list_remove_list(formats, formats); | ||
6005 | _evas_textblock_node_format_remove(o, fnode, 0); | ||
6006 | _evas_textblock_node_format_remove(o, fmt, 0); | ||
6007 | } | ||
6008 | /* Find the matching format and pop it, if the matching format | ||
6009 | * is our format, i.e the last one, pop and break. */ | ||
6010 | else | ||
6011 | { | ||
6012 | Eina_List *i, *next; | ||
6013 | EINA_LIST_FOREACH_SAFE(formats, i, next, fnode) | ||
6014 | { | ||
6015 | if (_FORMAT_IS_CLOSER_OF( | ||
6016 | fnode->orig_format, fstr, fstr_len)) | ||
6017 | { | ||
6018 | fnode = eina_list_data_get(i); | ||
6019 | formats = eina_list_remove_list(formats, i); | ||
6020 | _evas_textblock_node_format_remove(o, fnode, 0); | ||
6021 | _evas_textblock_node_format_remove(o, fmt, 0); | ||
6022 | break; | ||
6023 | } | ||
6024 | } | ||
6025 | } | ||
6026 | } | ||
6027 | else if (!fmt->visible) | ||
6028 | { | ||
6029 | _evas_textblock_node_format_remove(o, fmt, 0); | ||
6030 | } | ||
6031 | fmt = nnode; | ||
6032 | } | ||
6033 | while (fmt && (offset == 0) && (fmt->text_node == tnode)); | ||
6034 | eina_list_free(formats); | ||
6035 | } | ||
6036 | /** | ||
6037 | * @internal | ||
6038 | * Add the offset (may be negative) to the first node after fmt which is | ||
6039 | * pointing to the text node tnode or to o->format_nodes if fmt is null | ||
6040 | * and it points to tnode. | ||
6041 | * | ||
6042 | * @param o the textblock object. | ||
6043 | * @param tnode the text node the format should point to. | ||
6044 | * @param fmt the current format. | ||
6045 | * @param offset the offest to add (may be negative). | ||
6046 | */ | ||
6047 | static void | ||
6048 | _evas_textblock_node_format_adjust_offset(Evas_Object_Textblock *o, | ||
6049 | Evas_Object_Textblock_Node_Text *tnode, | ||
6050 | Evas_Object_Textblock_Node_Format *fmt, int offset) | ||
6051 | { | ||
6052 | if (fmt) | ||
6053 | { | ||
6054 | fmt = _NODE_FORMAT(EINA_INLIST_GET(fmt)->next); | ||
6055 | } | ||
6056 | else | ||
6057 | { | ||
6058 | fmt = o->format_nodes; | ||
6059 | } | ||
6060 | if (fmt && (tnode == fmt->text_node)) | ||
6061 | { | ||
6062 | fmt->offset += offset; | ||
6063 | } | ||
6064 | } | ||
6065 | |||
6066 | /** | ||
6067 | * @internal | ||
6068 | * Removes a format node updating the offset of the next format node and the | ||
6069 | * text nodes pointing to this node. | ||
6070 | * | ||
6071 | * @param o the textblock object. | ||
6072 | * @param n the fromat node to remove | ||
6073 | */ | ||
6074 | static void | ||
6075 | _evas_textblock_node_format_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Format *n, int visible_adjustment) | ||
6076 | { | ||
6077 | /* Update the text nodes about the change */ | ||
6078 | { | ||
6079 | Evas_Object_Textblock_Node_Format *nnode; | ||
6080 | nnode = _NODE_FORMAT(EINA_INLIST_GET(n)->next); | ||
6081 | /* If there's a next node that belongs to the same text node | ||
6082 | * and the curret node was the main one, advance the format node */ | ||
6083 | if (nnode && (nnode->text_node == n->text_node)) | ||
6084 | { | ||
6085 | if (nnode->text_node->format_node == n) | ||
6086 | { | ||
6087 | nnode->text_node->format_node = nnode; | ||
6088 | } | ||
6089 | } | ||
6090 | else | ||
6091 | { | ||
6092 | Evas_Object_Textblock_Node_Text *tnode; | ||
6093 | /* If there's no next one update the text nodes */ | ||
6094 | nnode = _NODE_FORMAT(EINA_INLIST_GET(n)->prev); | ||
6095 | tnode = n->text_node; | ||
6096 | /* Even if it's not the current text_node's main node | ||
6097 | * it can still be the next's. */ | ||
6098 | if (tnode && (tnode->format_node != n)) | ||
6099 | { | ||
6100 | tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next); | ||
6101 | } | ||
6102 | while (tnode && (tnode->format_node == n)) | ||
6103 | { | ||
6104 | tnode->format_node = nnode; | ||
6105 | tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next); | ||
6106 | } | ||
6107 | } | ||
6108 | } | ||
6109 | _evas_textblock_node_format_adjust_offset(o, n->text_node, n, | ||
6110 | n->offset - visible_adjustment); | ||
6111 | |||
6112 | o->format_nodes = _NODE_FORMAT(eina_inlist_remove( | ||
6113 | EINA_INLIST_GET(o->format_nodes), EINA_INLIST_GET(n))); | ||
6114 | _evas_textblock_node_format_free(o, n); | ||
6115 | } | ||
6116 | |||
6117 | /** | ||
6118 | * @internal | ||
6119 | * Sets all the offsets of the format nodes between start and end in the text | ||
6120 | * node n to 0 and sets visibility to EINA_FALSE. | ||
6121 | * If end == -1 end means the end of the string. | ||
6122 | * Assumes there is a prev node or the current node will be preserved. | ||
6123 | * | ||
6124 | * @param n the text node the positinos refer to. | ||
6125 | * @param start the start of where to delete from. | ||
6126 | * @param end the end of the section to delete, if end == -1 it means the end of the string. | ||
6127 | * @returns #EINA_TRUE if removed a PS, false otherwise. | ||
6128 | */ | ||
6129 | static Eina_Bool | ||
6130 | _evas_textblock_node_text_adjust_offsets_to_start(Evas_Object_Textblock *o, | ||
6131 | Evas_Object_Textblock_Node_Text *n, size_t start, int end) | ||
6132 | { | ||
6133 | Evas_Object_Textblock_Node_Format *last_node, *itr; | ||
6134 | Evas_Object_Textblock_Node_Text *new_node; | ||
6135 | int use_end = 1; | ||
6136 | int delta = 0; | ||
6137 | int first = 1; | ||
6138 | int update_format_node; | ||
6139 | size_t pos = 0; | ||
6140 | int orig_end; | ||
6141 | |||
6142 | itr = n->format_node; | ||
6143 | if (!itr || (itr->text_node != n)) return EINA_FALSE; | ||
6144 | |||
6145 | orig_end = end; | ||
6146 | if ((end < 0) || ((size_t) end == eina_ustrbuf_length_get(n->unicode))) | ||
6147 | { | ||
6148 | use_end = 0; | ||
6149 | } | ||
6150 | else if (end > 0) | ||
6151 | { | ||
6152 | /* We don't want the last one */ | ||
6153 | end--; | ||
6154 | } | ||
6155 | |||
6156 | /* If we are not removing the text node, all should stay in this text | ||
6157 | * node, otherwise, everything should move to the previous node */ | ||
6158 | if ((start == 0) && !use_end) | ||
6159 | { | ||
6160 | new_node = _NODE_TEXT(EINA_INLIST_GET(n)->prev); | ||
6161 | if (!new_node) | ||
6162 | { | ||
6163 | new_node = n; | ||
6164 | } | ||
6165 | } | ||
6166 | else | ||
6167 | { | ||
6168 | new_node = n; | ||
6169 | } | ||
6170 | |||
6171 | /* Find the first node after start */ | ||
6172 | while (itr && (itr->text_node == n)) | ||
6173 | { | ||
6174 | pos += itr->offset; | ||
6175 | if (pos >= start) | ||
6176 | { | ||
6177 | break; | ||
6178 | } | ||
6179 | itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
6180 | } | ||
6181 | |||
6182 | if (!itr || (itr->text_node != n)) | ||
6183 | { | ||
6184 | return EINA_FALSE; | ||
6185 | } | ||
6186 | |||
6187 | update_format_node = ((itr == n->format_node) && (new_node != n)); | ||
6188 | delta = orig_end - pos; | ||
6189 | itr->offset -= pos - start; | ||
6190 | |||
6191 | while (itr && (itr->text_node == n)) | ||
6192 | { | ||
6193 | last_node = itr; | ||
6194 | itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
6195 | |||
6196 | if (!first) | ||
6197 | { | ||
6198 | pos += last_node->offset; | ||
6199 | } | ||
6200 | |||
6201 | /* start is negative when this gets relevant */ | ||
6202 | if (use_end && (pos > (size_t) end)) | ||
6203 | { | ||
6204 | last_node->offset -= delta; | ||
6205 | break; | ||
6206 | } | ||
6207 | |||
6208 | delta = orig_end - pos; | ||
6209 | if (!first) | ||
6210 | { | ||
6211 | last_node->offset = 0; | ||
6212 | } | ||
6213 | else | ||
6214 | { | ||
6215 | first = 0; | ||
6216 | } | ||
6217 | last_node->visible = EINA_FALSE; | ||
6218 | |||
6219 | if (!itr || (itr && (itr->text_node != n))) | ||
6220 | { | ||
6221 | /* Remove the PS, and return since it's the end of the node */ | ||
6222 | if (_IS_PARAGRAPH_SEPARATOR(o, last_node->format)) | ||
6223 | { | ||
6224 | _evas_textblock_node_format_remove(o, last_node, 0); | ||
6225 | return EINA_TRUE; | ||
6226 | } | ||
6227 | |||
6228 | } | ||
6229 | last_node->text_node = new_node; | ||
6230 | if (update_format_node) | ||
6231 | { | ||
6232 | n->format_node = last_node; | ||
6233 | } | ||
6234 | } | ||
6235 | |||
6236 | return EINA_FALSE; | ||
6237 | } | ||
6238 | |||
6239 | /** | ||
6240 | * @internal | ||
6241 | * Removes all the format nodes between start and end in the text node n. | ||
6242 | * This function updates the offset of the next format node and the | ||
6243 | * text nodes pointing to it. if end == -1 end means the end of the string. | ||
6244 | * | ||
6245 | * @param o the textblock object. | ||
6246 | * @param n the text node the positinos refer to. | ||
6247 | * @param start the start of where to delete from. | ||
6248 | * @param end the end of the section to delete, if end == -1 it means the end of the string. | ||
6249 | */ | ||
6250 | static void | ||
6251 | _evas_textblock_node_text_remove_formats_between(Evas_Object_Textblock *o, | ||
6252 | Evas_Object_Textblock_Node_Text *n, int start, int end) | ||
6253 | { | ||
6254 | Evas_Object_Textblock_Node_Format *itr; | ||
6255 | int use_end = 1; | ||
6256 | int offset = end - start; | ||
6257 | itr = n->format_node; | ||
6258 | |||
6259 | if (itr) | ||
6260 | start -= itr->offset; | ||
6261 | if (offset < 0) offset = 0; | ||
6262 | if (end < 0) use_end = 0; | ||
6263 | while (itr && (itr->text_node == n)) | ||
6264 | { | ||
6265 | Evas_Object_Textblock_Node_Format *nnode; | ||
6266 | int tmp_offset = 0; | ||
6267 | |||
6268 | /* start is negative when this gets relevant */ | ||
6269 | if ((offset + start < 0) && use_end) | ||
6270 | { | ||
6271 | break; | ||
6272 | } | ||
6273 | nnode = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
6274 | if (nnode) | ||
6275 | { | ||
6276 | tmp_offset = nnode->offset; | ||
6277 | } | ||
6278 | if (start <= 0) | ||
6279 | { | ||
6280 | /* Don't do visible adjustments because we are removing the visual | ||
6281 | * chars anyway and taking those into account */ | ||
6282 | _evas_textblock_node_format_remove(o, itr, 0); | ||
6283 | } | ||
6284 | start -= tmp_offset; | ||
6285 | itr = nnode; | ||
6286 | } | ||
6287 | } | ||
6288 | |||
6289 | /** | ||
6290 | * @internal | ||
6291 | * Returns the first format in the range between start and end in the textblock | ||
6292 | * n. | ||
6293 | * | ||
6294 | * @param o the textblock object. | ||
6295 | * @param n the text node the positinos refer to. | ||
6296 | * @param start the start of where to delete from. | ||
6297 | * @param end the end of the section to delete, if end == -1 it means the end of the string. | ||
6298 | */ | ||
6299 | static Evas_Object_Textblock_Node_Format * | ||
6300 | _evas_textblock_node_text_get_first_format_between( | ||
6301 | Evas_Object_Textblock_Node_Text *n, int start, int end) | ||
6302 | { | ||
6303 | Evas_Object_Textblock_Node_Format *itr; | ||
6304 | int use_end = 1; | ||
6305 | itr = n->format_node; | ||
6306 | if (end < 0) use_end = 0; | ||
6307 | while (itr && (itr->text_node == n)) | ||
6308 | { | ||
6309 | start -= itr->offset; | ||
6310 | end -= itr->offset; | ||
6311 | if ((end <= 0) && use_end) | ||
6312 | { | ||
6313 | break; | ||
6314 | } | ||
6315 | if (start <= 0) | ||
6316 | { | ||
6317 | return itr; | ||
6318 | } | ||
6319 | itr = _NODE_FORMAT(EINA_INLIST_GET(itr)->next); | ||
6320 | } | ||
6321 | return NULL; | ||
6322 | } | ||
6323 | |||
6324 | /** | ||
6325 | * Removes a text node and the corresponding format nodes. | ||
6326 | * | ||
6327 | * @param o the textblock objec.t | ||
6328 | * @param n the node to remove. | ||
6329 | */ | ||
6330 | static void | ||
6331 | _evas_textblock_node_text_remove(Evas_Object_Textblock *o, Evas_Object_Textblock_Node_Text *n) | ||
6332 | { | ||
6333 | _evas_textblock_node_text_adjust_offsets_to_start(o, n, 0, -1); | ||
6334 | |||
6335 | o->text_nodes = _NODE_TEXT(eina_inlist_remove( | ||
6336 | EINA_INLIST_GET(o->text_nodes), EINA_INLIST_GET(n))); | ||
6337 | _evas_textblock_node_text_free(n); | ||
6338 | } | ||
6339 | |||
6340 | /** | ||
6341 | * @internal | ||
6342 | * Return the position where the formats starts at. | ||
6343 | * | ||
6344 | * @param fmt the format to return the position of. | ||
6345 | * @return the position of the format in the text node it points to. | ||
6346 | */ | ||
6347 | static size_t | ||
6348 | _evas_textblock_node_format_pos_get(const Evas_Object_Textblock_Node_Format *fmt) | ||
6349 | { | ||
6350 | Evas_Object_Textblock_Node_Text *text; | ||
6351 | Evas_Object_Textblock_Node_Format *base_format; | ||
6352 | Evas_Object_Textblock_Node_Format *itr; | ||
6353 | size_t position = 0; | ||
6354 | |||
6355 | if (!fmt) return 0; | ||
6356 | /* Find the main format node */ | ||
6357 | text = fmt->text_node; | ||
6358 | base_format = text->format_node; | ||
6359 | EINA_INLIST_FOREACH(base_format, itr) | ||
6360 | { | ||
6361 | if (itr == fmt) | ||
6362 | { | ||
6363 | break; | ||
6364 | } | ||
6365 | position += itr->offset; | ||
6366 | } | ||
6367 | return position + fmt->offset; | ||
6368 | } | ||
6369 | |||
6370 | EAPI int | ||
6371 | evas_textblock_cursor_pos_get(const Evas_Textblock_Cursor *cur) | ||
6372 | { | ||
6373 | Evas_Object_Textblock *o; | ||
6374 | Evas_Object_Textblock_Node_Text *n; | ||
6375 | size_t npos = 0; | ||
6376 | |||
6377 | if (!cur) return -1; | ||
6378 | if (!cur->node) return 0; | ||
6379 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6380 | n = o->text_nodes; | ||
6381 | while (n != cur->node) | ||
6382 | { | ||
6383 | npos += eina_ustrbuf_length_get(n->unicode); | ||
6384 | n = _NODE_TEXT(EINA_INLIST_GET(n)->next); | ||
6385 | } | ||
6386 | return npos + cur->pos; | ||
6387 | } | ||
6388 | |||
6389 | EAPI void | ||
6390 | evas_textblock_cursor_pos_set(Evas_Textblock_Cursor *cur, int _pos) | ||
6391 | { | ||
6392 | Evas_Object_Textblock *o; | ||
6393 | Evas_Object_Textblock_Node_Text *n; | ||
6394 | size_t pos; | ||
6395 | |||
6396 | if (!cur) return; | ||
6397 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6398 | |||
6399 | if (_pos < 0) | ||
6400 | { | ||
6401 | pos = 0; | ||
6402 | } | ||
6403 | else | ||
6404 | { | ||
6405 | pos = (size_t) _pos; | ||
6406 | } | ||
6407 | |||
6408 | n = o->text_nodes; | ||
6409 | while (n && (pos >= eina_ustrbuf_length_get(n->unicode))) | ||
6410 | { | ||
6411 | pos -= eina_ustrbuf_length_get(n->unicode); | ||
6412 | n = _NODE_TEXT(EINA_INLIST_GET(n)->next); | ||
6413 | } | ||
6414 | |||
6415 | if (n) | ||
6416 | { | ||
6417 | cur->node = n; | ||
6418 | cur->pos = pos; | ||
6419 | } | ||
6420 | else if (o->text_nodes) | ||
6421 | { | ||
6422 | /* In case we went pass the last node, we need to put the cursor | ||
6423 | * at the absolute end. */ | ||
6424 | Evas_Object_Textblock_Node_Text *last_n; | ||
6425 | |||
6426 | last_n = _NODE_TEXT(EINA_INLIST_GET(o->text_nodes)->last); | ||
6427 | pos = eina_ustrbuf_length_get(last_n->unicode); | ||
6428 | |||
6429 | cur->node = last_n; | ||
6430 | cur->pos = pos; | ||
6431 | } | ||
6432 | |||
6433 | } | ||
6434 | |||
6435 | EAPI Eina_Bool | ||
6436 | evas_textblock_cursor_line_set(Evas_Textblock_Cursor *cur, int line) | ||
6437 | { | ||
6438 | Evas_Object_Textblock *o; | ||
6439 | Evas_Object_Textblock_Line *ln; | ||
6440 | Evas_Object_Textblock_Item *it; | ||
6441 | |||
6442 | if (!cur) return EINA_FALSE; | ||
6443 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6444 | if (!o->formatted.valid) _relayout(cur->obj); | ||
6445 | |||
6446 | ln = _find_layout_line_num(cur->obj, line); | ||
6447 | if (!ln) return EINA_FALSE; | ||
6448 | it = (Evas_Object_Textblock_Item *)ln->items; | ||
6449 | if (it) | ||
6450 | { | ||
6451 | cur->pos = it->text_pos; | ||
6452 | cur->node = it->text_node; | ||
6453 | } | ||
6454 | else | ||
6455 | { | ||
6456 | cur->pos = 0; | ||
6457 | |||
6458 | cur->node = o->text_nodes; | ||
6459 | } | ||
6460 | return EINA_TRUE; | ||
6461 | } | ||
6462 | |||
6463 | EAPI int | ||
6464 | evas_textblock_cursor_compare(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2) | ||
6465 | { | ||
6466 | Eina_Inlist *l1, *l2; | ||
6467 | |||
6468 | if (!cur1) return 0; | ||
6469 | if (!cur2) return 0; | ||
6470 | if (cur1->obj != cur2->obj) return 0; | ||
6471 | if ((!cur1->node) || (!cur2->node)) return 0; | ||
6472 | if (cur1->node == cur2->node) | ||
6473 | { | ||
6474 | if (cur1->pos < cur2->pos) return -1; /* cur1 < cur2 */ | ||
6475 | else if (cur1->pos > cur2->pos) return 1; /* cur2 < cur1 */ | ||
6476 | return 0; | ||
6477 | } | ||
6478 | for (l1 = EINA_INLIST_GET(cur1->node), | ||
6479 | l2 = EINA_INLIST_GET(cur1->node); (l1) || (l2);) | ||
6480 | { | ||
6481 | if (l1 == EINA_INLIST_GET(cur2->node)) return 1; /* cur2 < cur 1 */ | ||
6482 | else if (l2 == EINA_INLIST_GET(cur2->node)) return -1; /* cur1 < cur 2 */ | ||
6483 | else if (!l1) return -1; /* cur1 < cur 2 */ | ||
6484 | else if (!l2) return 1; /* cur2 < cur 1 */ | ||
6485 | l1 = l1->prev; | ||
6486 | l2 = l2->next; | ||
6487 | } | ||
6488 | return 0; | ||
6489 | } | ||
6490 | |||
6491 | EAPI void | ||
6492 | evas_textblock_cursor_copy(const Evas_Textblock_Cursor *cur, Evas_Textblock_Cursor *cur_dest) | ||
6493 | { | ||
6494 | if (!cur) return; | ||
6495 | if (!cur_dest) return; | ||
6496 | if (cur->obj != cur_dest->obj) return; | ||
6497 | cur_dest->pos = cur->pos; | ||
6498 | cur_dest->node = cur->node; | ||
6499 | |||
6500 | } | ||
6501 | |||
6502 | |||
6503 | /* text controls */ | ||
6504 | /** | ||
6505 | * @internal | ||
6506 | * Free a text node. Shouldn't be used usually, it's better to use | ||
6507 | * @ref _evas_textblock_node_text_remove for most cases . | ||
6508 | * | ||
6509 | * @param n the text node to free | ||
6510 | * @see _evas_textblock_node_text_remove | ||
6511 | */ | ||
6512 | static void | ||
6513 | _evas_textblock_node_text_free(Evas_Object_Textblock_Node_Text *n) | ||
6514 | { | ||
6515 | if (!n) return; | ||
6516 | eina_ustrbuf_free(n->unicode); | ||
6517 | if (n->utf8) | ||
6518 | free(n->utf8); | ||
6519 | if (n->par) | ||
6520 | n->par->text_node = NULL; | ||
6521 | free(n); | ||
6522 | } | ||
6523 | |||
6524 | /** | ||
6525 | * @internal | ||
6526 | * Create a new text node | ||
6527 | * | ||
6528 | * @return the new text node. | ||
6529 | */ | ||
6530 | static Evas_Object_Textblock_Node_Text * | ||
6531 | _evas_textblock_node_text_new(void) | ||
6532 | { | ||
6533 | Evas_Object_Textblock_Node_Text *n; | ||
6534 | |||
6535 | n = calloc(1, sizeof(Evas_Object_Textblock_Node_Text)); | ||
6536 | n->unicode = eina_ustrbuf_new(); | ||
6537 | /* We want to layout each paragraph at least once. */ | ||
6538 | n->dirty = EINA_TRUE; | ||
6539 | n->is_new = EINA_TRUE; | ||
6540 | |||
6541 | return n; | ||
6542 | } | ||
6543 | |||
6544 | /** | ||
6545 | * @internal | ||
6546 | * Break a paragraph. This does not add a PS but only splits the paragraph | ||
6547 | * where a ps was just added! | ||
6548 | * | ||
6549 | * @param cur the cursor to break at. | ||
6550 | * @param fnode the format node of the PS just added. | ||
6551 | * @return Returns no value. | ||
6552 | */ | ||
6553 | static void | ||
6554 | _evas_textblock_cursor_break_paragraph(Evas_Textblock_Cursor *cur, | ||
6555 | Evas_Object_Textblock_Node_Format *fnode) | ||
6556 | { | ||
6557 | Evas_Object_Textblock *o; | ||
6558 | Evas_Object_Textblock_Node_Text *n; | ||
6559 | |||
6560 | if (!cur) return; | ||
6561 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6562 | |||
6563 | n = _evas_textblock_node_text_new(); | ||
6564 | o->text_nodes = _NODE_TEXT(eina_inlist_append_relative( | ||
6565 | EINA_INLIST_GET(o->text_nodes), | ||
6566 | EINA_INLIST_GET(n), | ||
6567 | EINA_INLIST_GET(cur->node))); | ||
6568 | /* Handle text and format changes. */ | ||
6569 | if (cur->node) | ||
6570 | { | ||
6571 | Evas_Object_Textblock_Node_Format *nnode; | ||
6572 | size_t len, start; | ||
6573 | const Eina_Unicode *text; | ||
6574 | |||
6575 | /* If there was a format node in the delete range, | ||
6576 | * make it our format and update the text_node fields, | ||
6577 | * otherwise, use the paragraph separator | ||
6578 | * of the previous paragraph. */ | ||
6579 | nnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
6580 | if (nnode && (nnode->text_node == cur->node)) | ||
6581 | { | ||
6582 | n->format_node = nnode; | ||
6583 | nnode->offset--; /* We don't have to take the replacement char | ||
6584 | into account anymore */ | ||
6585 | while (nnode && (nnode->text_node == cur->node)) | ||
6586 | { | ||
6587 | nnode->text_node = n; | ||
6588 | nnode = _NODE_FORMAT(EINA_INLIST_GET(nnode)->next); | ||
6589 | } | ||
6590 | } | ||
6591 | else | ||
6592 | { | ||
6593 | n->format_node = fnode; | ||
6594 | } | ||
6595 | |||
6596 | /* cur->pos now points to the PS, move after. */ | ||
6597 | start = cur->pos + 1; | ||
6598 | len = eina_ustrbuf_length_get(cur->node->unicode) - start; | ||
6599 | if (len > 0) | ||
6600 | { | ||
6601 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
6602 | eina_ustrbuf_append_length(n->unicode, text + start, len); | ||
6603 | eina_ustrbuf_remove(cur->node->unicode, start, start + len); | ||
6604 | cur->node->dirty = EINA_TRUE; | ||
6605 | } | ||
6606 | } | ||
6607 | else | ||
6608 | { | ||
6609 | fnode = o->format_nodes; | ||
6610 | if (fnode) | ||
6611 | { | ||
6612 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->last); | ||
6613 | } | ||
6614 | n->format_node = fnode; | ||
6615 | } | ||
6616 | } | ||
6617 | |||
6618 | /** | ||
6619 | * @internal | ||
6620 | * Set the node and offset of all the curs after cur. | ||
6621 | * | ||
6622 | * @param cur the cursor. | ||
6623 | * @param n the current textblock node. | ||
6624 | * @param new_node the new node to set. | ||
6625 | */ | ||
6626 | static void | ||
6627 | _evas_textblock_cursors_set_node(Evas_Object_Textblock *o, | ||
6628 | const Evas_Object_Textblock_Node_Text *n, | ||
6629 | Evas_Object_Textblock_Node_Text *new_node) | ||
6630 | { | ||
6631 | Eina_List *l; | ||
6632 | Evas_Textblock_Cursor *data; | ||
6633 | |||
6634 | if (n == o->cursor->node) | ||
6635 | { | ||
6636 | o->cursor->pos = 0; | ||
6637 | o->cursor->node = new_node; | ||
6638 | } | ||
6639 | EINA_LIST_FOREACH(o->cursors, l, data) | ||
6640 | { | ||
6641 | if (n == data->node) | ||
6642 | { | ||
6643 | data->pos = 0; | ||
6644 | data->node = new_node; | ||
6645 | } | ||
6646 | } | ||
6647 | } | ||
6648 | |||
6649 | /** | ||
6650 | * @internal | ||
6651 | * Update the offset of all the cursors after cur. | ||
6652 | * | ||
6653 | * @param cur the cursor. | ||
6654 | * @param n the current textblock node. | ||
6655 | * @param start the starting pos. | ||
6656 | * @param offset how much to adjust (can be negative). | ||
6657 | */ | ||
6658 | static void | ||
6659 | _evas_textblock_cursors_update_offset(const Evas_Textblock_Cursor *cur, | ||
6660 | const Evas_Object_Textblock_Node_Text *n, | ||
6661 | size_t start, int offset) | ||
6662 | { | ||
6663 | Eina_List *l; | ||
6664 | Evas_Textblock_Cursor *data; | ||
6665 | Evas_Object_Textblock *o; | ||
6666 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6667 | |||
6668 | if (cur != o->cursor) | ||
6669 | { | ||
6670 | if ((n == o->cursor->node) && | ||
6671 | (o->cursor->pos > start)) | ||
6672 | { | ||
6673 | if ((offset < 0) && (o->cursor->pos <= (size_t) (-1 * offset))) | ||
6674 | { | ||
6675 | o->cursor->pos = 0; | ||
6676 | } | ||
6677 | else | ||
6678 | { | ||
6679 | o->cursor->pos += offset; | ||
6680 | } | ||
6681 | } | ||
6682 | } | ||
6683 | EINA_LIST_FOREACH(o->cursors, l, data) | ||
6684 | { | ||
6685 | if (data != cur) | ||
6686 | { | ||
6687 | if ((n == data->node) && | ||
6688 | (data->pos > start)) | ||
6689 | { | ||
6690 | if ((offset < 0) && (data->pos <= (size_t) (-1 * offset))) | ||
6691 | { | ||
6692 | data->pos = 0; | ||
6693 | } | ||
6694 | else | ||
6695 | { | ||
6696 | data->pos += offset; | ||
6697 | } | ||
6698 | } | ||
6699 | else if (!data->node) | ||
6700 | { | ||
6701 | data->node = o->text_nodes; | ||
6702 | data->pos = 0; | ||
6703 | } | ||
6704 | } | ||
6705 | } | ||
6706 | } | ||
6707 | |||
6708 | /** | ||
6709 | * @internal | ||
6710 | * Mark that the textblock has changed. | ||
6711 | * | ||
6712 | * @param o the textblock object. | ||
6713 | * @param obj the evas object. | ||
6714 | */ | ||
6715 | static void | ||
6716 | _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj) | ||
6717 | { | ||
6718 | o->formatted.valid = 0; | ||
6719 | o->native.valid = 0; | ||
6720 | o->content_changed = 1; | ||
6721 | if (o->markup_text) | ||
6722 | { | ||
6723 | free(o->markup_text); | ||
6724 | o->markup_text = NULL; | ||
6725 | } | ||
6726 | |||
6727 | evas_object_change(obj); | ||
6728 | } | ||
6729 | |||
6730 | static void | ||
6731 | _evas_textblock_invalidate_all(Evas_Object_Textblock *o) | ||
6732 | { | ||
6733 | Evas_Object_Textblock_Node_Text *n; | ||
6734 | |||
6735 | EINA_INLIST_FOREACH(o->text_nodes, n) | ||
6736 | { | ||
6737 | n->dirty = EINA_TRUE; | ||
6738 | } | ||
6739 | } | ||
6740 | |||
6741 | EAPI int | ||
6742 | evas_textblock_cursor_text_append(Evas_Textblock_Cursor *cur, const char *_text) | ||
6743 | { | ||
6744 | Evas_Object_Textblock *o; | ||
6745 | Evas_Object_Textblock_Node_Text *n; | ||
6746 | Evas_Object_Textblock_Node_Format *fnode = NULL; | ||
6747 | Eina_Unicode *text; | ||
6748 | int len = 0; | ||
6749 | |||
6750 | if (!cur) return 0; | ||
6751 | text = eina_unicode_utf8_to_unicode(_text, &len); | ||
6752 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6753 | |||
6754 | n = cur->node; | ||
6755 | if (n) | ||
6756 | { | ||
6757 | Evas_Object_Textblock_Node_Format *nnode; | ||
6758 | fnode = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
6759 | fnode = _evas_textblock_node_format_last_at_off(fnode); | ||
6760 | /* find the node after the current in the same paragraph | ||
6761 | * either we find one and then take the next, or we try to get | ||
6762 | * the first for the paragraph which must be after our position */ | ||
6763 | if (fnode) | ||
6764 | { | ||
6765 | if (!evas_textblock_cursor_format_is_visible_get(cur)) | ||
6766 | { | ||
6767 | nnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
6768 | if (nnode && (nnode->text_node == n)) | ||
6769 | { | ||
6770 | fnode = nnode; | ||
6771 | } | ||
6772 | else | ||
6773 | { | ||
6774 | fnode = NULL; | ||
6775 | } | ||
6776 | } | ||
6777 | } | ||
6778 | else | ||
6779 | { | ||
6780 | fnode = n->format_node; | ||
6781 | } | ||
6782 | } | ||
6783 | else if (o->text_nodes) | ||
6784 | { | ||
6785 | n = cur->node = o->text_nodes; | ||
6786 | cur->pos = 0; | ||
6787 | } | ||
6788 | else | ||
6789 | { | ||
6790 | n = _evas_textblock_node_text_new(); | ||
6791 | o->text_nodes = _NODE_TEXT(eina_inlist_append( | ||
6792 | EINA_INLIST_GET(o->text_nodes), | ||
6793 | EINA_INLIST_GET(n))); | ||
6794 | cur->node = n; | ||
6795 | } | ||
6796 | |||
6797 | eina_ustrbuf_insert_length(n->unicode, text, len, cur->pos); | ||
6798 | /* Advance the formats */ | ||
6799 | if (fnode && (fnode->text_node == cur->node)) | ||
6800 | fnode->offset += len; | ||
6801 | |||
6802 | /* Update all the cursors after our position. */ | ||
6803 | _evas_textblock_cursors_update_offset(cur, cur->node, cur->pos, len); | ||
6804 | |||
6805 | _evas_textblock_changed(o, cur->obj); | ||
6806 | n->dirty = EINA_TRUE; | ||
6807 | free(text); | ||
6808 | |||
6809 | if (!o->cursor->node) | ||
6810 | o->cursor->node = o->text_nodes; | ||
6811 | return len; | ||
6812 | } | ||
6813 | |||
6814 | EAPI int | ||
6815 | evas_textblock_cursor_text_prepend(Evas_Textblock_Cursor *cur, const char *_text) | ||
6816 | { | ||
6817 | int len; | ||
6818 | /*append is essentially prepend without advancing */ | ||
6819 | len = evas_textblock_cursor_text_append(cur, _text); | ||
6820 | cur->pos += len; /*Advance */ | ||
6821 | return len; | ||
6822 | } | ||
6823 | |||
6824 | /** | ||
6825 | * @internal | ||
6826 | * Free a format node | ||
6827 | * | ||
6828 | * @param o the textblock object | ||
6829 | * @param n the format node to free | ||
6830 | */ | ||
6831 | static void | ||
6832 | _evas_textblock_node_format_free(Evas_Object_Textblock *o, | ||
6833 | Evas_Object_Textblock_Node_Format *n) | ||
6834 | { | ||
6835 | if (!n) return; | ||
6836 | eina_stringshare_del(n->format); | ||
6837 | eina_stringshare_del(n->orig_format); | ||
6838 | if (n->anchor == ANCHOR_ITEM) | ||
6839 | o->anchors_item = eina_list_remove(o->anchors_item, n); | ||
6840 | else if (n->anchor == ANCHOR_A) | ||
6841 | o->anchors_a = eina_list_remove(o->anchors_a, n); | ||
6842 | free(n); | ||
6843 | } | ||
6844 | |||
6845 | /** | ||
6846 | * @internal | ||
6847 | * Create a new format node. | ||
6848 | * | ||
6849 | * @param format the text to create the format node from. | ||
6850 | * @param o the textblock object. | ||
6851 | * @return Returns the new format node | ||
6852 | */ | ||
6853 | static Evas_Object_Textblock_Node_Format * | ||
6854 | _evas_textblock_node_format_new(Evas_Object_Textblock *o, const char *_format) | ||
6855 | { | ||
6856 | Evas_Object_Textblock_Node_Format *n; | ||
6857 | const char *format = _format; | ||
6858 | |||
6859 | n = calloc(1, sizeof(Evas_Object_Textblock_Node_Format)); | ||
6860 | /* Create orig_format and format */ | ||
6861 | if (format[0] == '<') | ||
6862 | { | ||
6863 | const char *match; | ||
6864 | size_t format_len; | ||
6865 | size_t replace_len; | ||
6866 | |||
6867 | format++; /* Advance after '<' */ | ||
6868 | format_len = strlen(format); | ||
6869 | if (format[format_len - 1] == '>') | ||
6870 | format_len--; /* We don't care about '>' */ | ||
6871 | |||
6872 | match = _style_match_tag(o->style, format, format_len, &replace_len); | ||
6873 | if (match) | ||
6874 | { | ||
6875 | if ((match[0] == '+') || (match[0] == '-')) | ||
6876 | { | ||
6877 | char *norm_format; | ||
6878 | norm_format = malloc(format_len + 2 + 1); | ||
6879 | memcpy(norm_format, match, 2); | ||
6880 | memcpy(norm_format + 2, format, format_len); | ||
6881 | norm_format[format_len + 2] = '\0'; | ||
6882 | n->orig_format = | ||
6883 | eina_stringshare_add_length(norm_format, format_len + 2); | ||
6884 | free(norm_format); | ||
6885 | } | ||
6886 | else | ||
6887 | { | ||
6888 | n->orig_format = | ||
6889 | eina_stringshare_add_length(format, format_len); | ||
6890 | } | ||
6891 | n->format = eina_stringshare_add(match); | ||
6892 | } | ||
6893 | else | ||
6894 | { | ||
6895 | char *norm_format; | ||
6896 | |||
6897 | norm_format = malloc(format_len + 2 + 1); | ||
6898 | if (norm_format) | ||
6899 | { | ||
6900 | if (format[0] == '/') | ||
6901 | { | ||
6902 | memcpy(norm_format, "- ", 2); | ||
6903 | memcpy(norm_format + 2, format + 1, format_len - 1); | ||
6904 | norm_format[format_len + 2 - 1] = '\0'; | ||
6905 | } | ||
6906 | else | ||
6907 | { | ||
6908 | memcpy(norm_format, "+ ", 2); | ||
6909 | memcpy(norm_format + 2, format, format_len); | ||
6910 | norm_format[format_len + 2] = '\0'; | ||
6911 | } | ||
6912 | n->orig_format = eina_stringshare_add(norm_format); | ||
6913 | free(norm_format); | ||
6914 | } | ||
6915 | n->format = eina_stringshare_ref(n->orig_format); | ||
6916 | } | ||
6917 | } | ||
6918 | /* Just use as is, it's a special format. */ | ||
6919 | else | ||
6920 | { | ||
6921 | n->orig_format = eina_stringshare_add(format); | ||
6922 | n->format = eina_stringshare_ref(n->orig_format); | ||
6923 | } | ||
6924 | |||
6925 | format = n->format; | ||
6926 | |||
6927 | _evas_textblock_format_is_visible(n, format); | ||
6928 | if (n->anchor == ANCHOR_A) | ||
6929 | { | ||
6930 | o->anchors_a = eina_list_append(o->anchors_a, n); | ||
6931 | } | ||
6932 | else if (n->anchor == ANCHOR_ITEM) | ||
6933 | { | ||
6934 | o->anchors_item = eina_list_append(o->anchors_item, n); | ||
6935 | } | ||
6936 | n->is_new = EINA_TRUE; | ||
6937 | |||
6938 | return n; | ||
6939 | } | ||
6940 | |||
6941 | static Eina_Bool | ||
6942 | _evas_textblock_cursor_is_at_the_end(const Evas_Textblock_Cursor *cur) | ||
6943 | { | ||
6944 | const Eina_Unicode *text; | ||
6945 | |||
6946 | if (!cur) return EINA_FALSE; | ||
6947 | if (!cur->node) return EINA_FALSE; | ||
6948 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
6949 | return ((text[cur->pos] == 0) && (!EINA_INLIST_GET(cur->node)->next)) ? | ||
6950 | EINA_TRUE : EINA_FALSE; | ||
6951 | } | ||
6952 | |||
6953 | EAPI Eina_Bool | ||
6954 | evas_textblock_cursor_format_append(Evas_Textblock_Cursor *cur, const char *format) | ||
6955 | { | ||
6956 | Evas_Object_Textblock *o; | ||
6957 | Evas_Object_Textblock_Node_Format *n; | ||
6958 | Eina_Bool is_visible; | ||
6959 | |||
6960 | if (!cur) return EINA_FALSE; | ||
6961 | if ((!format) || (format[0] == 0)) return EINA_FALSE; | ||
6962 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
6963 | /* We should always have at least one text node */ | ||
6964 | if (!o->text_nodes) | ||
6965 | { | ||
6966 | evas_textblock_cursor_text_prepend(cur, ""); | ||
6967 | } | ||
6968 | |||
6969 | n = _evas_textblock_node_format_new(o, format); | ||
6970 | is_visible = n->visible; | ||
6971 | format = n->format; | ||
6972 | if (!cur->node) | ||
6973 | { | ||
6974 | o->format_nodes = _NODE_FORMAT(eina_inlist_append( | ||
6975 | EINA_INLIST_GET(o->format_nodes), | ||
6976 | EINA_INLIST_GET(n))); | ||
6977 | cur->pos = 0; | ||
6978 | n->text_node = (EINA_INLIST_GET(n)->prev) ? | ||
6979 | _NODE_FORMAT(EINA_INLIST_GET(n)->prev)->text_node : | ||
6980 | o->text_nodes; | ||
6981 | cur->node = n->text_node; | ||
6982 | } | ||
6983 | else | ||
6984 | { | ||
6985 | Evas_Object_Textblock_Node_Format *fmt; | ||
6986 | fmt = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
6987 | n->text_node = cur->node; | ||
6988 | if (!fmt) | ||
6989 | { | ||
6990 | o->format_nodes = _NODE_FORMAT(eina_inlist_prepend( | ||
6991 | EINA_INLIST_GET(o->format_nodes), | ||
6992 | EINA_INLIST_GET(n))); | ||
6993 | n->offset = cur->pos; | ||
6994 | } | ||
6995 | else | ||
6996 | { | ||
6997 | if (evas_textblock_cursor_format_is_visible_get(cur)) | ||
6998 | { | ||
6999 | o->format_nodes = _NODE_FORMAT(eina_inlist_prepend_relative( | ||
7000 | EINA_INLIST_GET(o->format_nodes), | ||
7001 | EINA_INLIST_GET(n), | ||
7002 | EINA_INLIST_GET(fmt) | ||
7003 | )); | ||
7004 | n->offset = fmt->offset; | ||
7005 | if (fmt->text_node->format_node == fmt) | ||
7006 | { | ||
7007 | fmt->text_node->format_node = n; | ||
7008 | } | ||
7009 | } | ||
7010 | else | ||
7011 | { | ||
7012 | fmt = _evas_textblock_node_format_last_at_off(fmt); | ||
7013 | o->format_nodes = _NODE_FORMAT(eina_inlist_append_relative( | ||
7014 | EINA_INLIST_GET(o->format_nodes), | ||
7015 | EINA_INLIST_GET(n), | ||
7016 | EINA_INLIST_GET(fmt) | ||
7017 | )); | ||
7018 | if (fmt->text_node != cur->node) | ||
7019 | { | ||
7020 | n->offset = cur->pos; | ||
7021 | } | ||
7022 | else | ||
7023 | { | ||
7024 | n->offset = cur->pos - | ||
7025 | _evas_textblock_node_format_pos_get(fmt); | ||
7026 | } | ||
7027 | } | ||
7028 | } | ||
7029 | /* Adjust differently if we insert a format char */ | ||
7030 | if (is_visible) | ||
7031 | { | ||
7032 | _evas_textblock_node_format_adjust_offset(o, cur->node, n, | ||
7033 | -(n->offset - 1)); | ||
7034 | } | ||
7035 | else | ||
7036 | { | ||
7037 | _evas_textblock_node_format_adjust_offset(o, cur->node, n, | ||
7038 | -n->offset); | ||
7039 | } | ||
7040 | |||
7041 | if (!fmt || (fmt->text_node != cur->node)) | ||
7042 | { | ||
7043 | cur->node->format_node = n; | ||
7044 | } | ||
7045 | } | ||
7046 | if (is_visible && cur->node) | ||
7047 | { | ||
7048 | Eina_Unicode insert_char; | ||
7049 | /* Insert a visual representation according to the type of the | ||
7050 | format */ | ||
7051 | if (_IS_PARAGRAPH_SEPARATOR(o, format)) | ||
7052 | insert_char = _PARAGRAPH_SEPARATOR; | ||
7053 | else if (_IS_LINE_SEPARATOR(format)) | ||
7054 | insert_char = '\n'; | ||
7055 | else if (_IS_TAB(format)) | ||
7056 | insert_char = '\t'; | ||
7057 | else | ||
7058 | insert_char = EVAS_TEXTBLOCK_REPLACEMENT_CHAR; | ||
7059 | |||
7060 | eina_ustrbuf_insert_char(cur->node->unicode, insert_char, cur->pos); | ||
7061 | |||
7062 | /* Advance all the cursors after our cursor */ | ||
7063 | _evas_textblock_cursors_update_offset(cur, cur->node, cur->pos, 1); | ||
7064 | if (_IS_PARAGRAPH_SEPARATOR(o, format)) | ||
7065 | { | ||
7066 | _evas_textblock_cursor_break_paragraph(cur, n); | ||
7067 | } | ||
7068 | else | ||
7069 | { | ||
7070 | /* Handle visible format nodes here */ | ||
7071 | cur->node->dirty = EINA_TRUE; | ||
7072 | n->is_new = EINA_FALSE; | ||
7073 | } | ||
7074 | } | ||
7075 | else | ||
7076 | { | ||
7077 | o->format_changed = EINA_TRUE; | ||
7078 | } | ||
7079 | |||
7080 | _evas_textblock_changed(o, cur->obj); | ||
7081 | |||
7082 | if (!o->cursor->node) | ||
7083 | o->cursor->node = o->text_nodes; | ||
7084 | return is_visible; | ||
7085 | } | ||
7086 | |||
7087 | EAPI Eina_Bool | ||
7088 | evas_textblock_cursor_format_prepend(Evas_Textblock_Cursor *cur, const char *format) | ||
7089 | { | ||
7090 | Eina_Bool is_visible; | ||
7091 | /* append is essentially prepend without advancing */ | ||
7092 | is_visible = evas_textblock_cursor_format_append(cur, format); | ||
7093 | if (is_visible) | ||
7094 | { | ||
7095 | /* Advance after the replacement char */ | ||
7096 | evas_textblock_cursor_char_next(cur); | ||
7097 | } | ||
7098 | |||
7099 | return is_visible; | ||
7100 | } | ||
7101 | |||
7102 | |||
7103 | EAPI void | ||
7104 | evas_textblock_cursor_char_delete(Evas_Textblock_Cursor *cur) | ||
7105 | { | ||
7106 | Evas_Object_Textblock *o; | ||
7107 | Evas_Object_Textblock_Node_Text *n, *n2; | ||
7108 | const Eina_Unicode *text; | ||
7109 | int chr, ind, ppos; | ||
7110 | |||
7111 | if (!cur || !cur->node) return; | ||
7112 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7113 | n = cur->node; | ||
7114 | |||
7115 | text = eina_ustrbuf_string_get(n->unicode); | ||
7116 | ind = cur->pos; | ||
7117 | if (text[ind]) | ||
7118 | chr = text[ind++]; | ||
7119 | else | ||
7120 | chr = 0; | ||
7121 | |||
7122 | if (chr == 0) return; | ||
7123 | ppos = cur->pos; | ||
7124 | eina_ustrbuf_remove(n->unicode, cur->pos, ind); | ||
7125 | /* Remove a format node if needed, and remove the char only if the | ||
7126 | * fmt node is not visible */ | ||
7127 | { | ||
7128 | Eina_Bool should_merge = EINA_FALSE; | ||
7129 | Evas_Object_Textblock_Node_Format *fmt, *fmt2; | ||
7130 | fmt = _evas_textblock_cursor_node_format_at_pos_get(cur); | ||
7131 | if (fmt) | ||
7132 | { | ||
7133 | const char *format = NULL; | ||
7134 | Evas_Object_Textblock_Node_Format *last_fmt; | ||
7135 | /* If there's a PS it must be the last become it delimits paragraphs */ | ||
7136 | last_fmt = _evas_textblock_node_format_last_at_off(fmt); | ||
7137 | format = last_fmt->format; | ||
7138 | if (format && _IS_PARAGRAPH_SEPARATOR(o, format)) | ||
7139 | { | ||
7140 | /* If it was a paragraph separator, we should merge the | ||
7141 | * current with the next, there must be a next. */ | ||
7142 | should_merge = EINA_TRUE; | ||
7143 | } | ||
7144 | /* If a singnular, mark as invisible, so we'll delete it. */ | ||
7145 | if (!format || ((*format != '+') && (*format != '-'))) | ||
7146 | { | ||
7147 | last_fmt->visible = EINA_FALSE; | ||
7148 | } | ||
7149 | } | ||
7150 | |||
7151 | fmt2 = _evas_textblock_cursor_node_format_before_or_at_pos_get(cur); | ||
7152 | fmt2 = _evas_textblock_node_format_last_at_off(fmt2); | ||
7153 | _evas_textblock_node_format_adjust_offset(o, cur->node, fmt2, | ||
7154 | -(ind - cur->pos)); | ||
7155 | |||
7156 | if (should_merge) | ||
7157 | { | ||
7158 | _evas_textblock_cursor_nodes_merge(cur); | ||
7159 | } | ||
7160 | |||
7161 | _evas_textblock_node_format_remove_matching(o, fmt); | ||
7162 | } | ||
7163 | |||
7164 | if (cur->pos == eina_ustrbuf_length_get(n->unicode)) | ||
7165 | { | ||
7166 | n2 = _NODE_TEXT(EINA_INLIST_GET(n)->next); | ||
7167 | if (n2) | ||
7168 | { | ||
7169 | cur->node = n2; | ||
7170 | cur->pos = 0; | ||
7171 | } | ||
7172 | } | ||
7173 | |||
7174 | _evas_textblock_cursors_update_offset(cur, n, ppos, -(ind - ppos)); | ||
7175 | _evas_textblock_changed(o, cur->obj); | ||
7176 | cur->node->dirty = EINA_TRUE; | ||
7177 | } | ||
7178 | |||
7179 | EAPI void | ||
7180 | evas_textblock_cursor_range_delete(Evas_Textblock_Cursor *cur1, Evas_Textblock_Cursor *cur2) | ||
7181 | { | ||
7182 | Evas_Object_Textblock_Node_Format *fnode = NULL; | ||
7183 | Evas_Object_Textblock *o; | ||
7184 | Evas_Object_Textblock_Node_Text *n1, *n2; | ||
7185 | Eina_Bool should_merge = EINA_FALSE, reset_cursor = EINA_FALSE; | ||
7186 | |||
7187 | if (!cur1 || !cur1->node) return; | ||
7188 | if (!cur2 || !cur2->node) return; | ||
7189 | if (cur1->obj != cur2->obj) return; | ||
7190 | o = (Evas_Object_Textblock *)(cur1->obj->object_data); | ||
7191 | if (evas_textblock_cursor_compare(cur1, cur2) > 0) | ||
7192 | { | ||
7193 | Evas_Textblock_Cursor *tc; | ||
7194 | |||
7195 | tc = cur1; | ||
7196 | cur1 = cur2; | ||
7197 | cur2 = tc; | ||
7198 | } | ||
7199 | n1 = cur1->node; | ||
7200 | n2 = cur2->node; | ||
7201 | if ((evas_textblock_cursor_compare(o->cursor, cur1) >= 0) && | ||
7202 | (evas_textblock_cursor_compare(cur2, o->cursor) >= 0)) | ||
7203 | { | ||
7204 | reset_cursor = EINA_TRUE; | ||
7205 | } | ||
7206 | |||
7207 | |||
7208 | if (n1 == n2) | ||
7209 | { | ||
7210 | if ((cur1->pos == 0) && | ||
7211 | (cur2->pos == eina_ustrbuf_length_get(n1->unicode))) | ||
7212 | { | ||
7213 | _evas_textblock_node_text_remove_formats_between(o, n1, 0, -1); | ||
7214 | } | ||
7215 | else | ||
7216 | { | ||
7217 | should_merge = _evas_textblock_node_text_adjust_offsets_to_start(o, | ||
7218 | n1, cur1->pos, cur2->pos); | ||
7219 | } | ||
7220 | eina_ustrbuf_remove(n1->unicode, cur1->pos, cur2->pos); | ||
7221 | _evas_textblock_cursors_update_offset(cur1, cur1->node, cur1->pos, - (cur2->pos - cur1->pos)); | ||
7222 | } | ||
7223 | else | ||
7224 | { | ||
7225 | Evas_Object_Textblock_Node_Text *n; | ||
7226 | int len; | ||
7227 | _evas_textblock_node_text_adjust_offsets_to_start(o, n1, cur1->pos, -1); | ||
7228 | n = _NODE_TEXT(EINA_INLIST_GET(n1)->next); | ||
7229 | /* Remove all the text nodes between */ | ||
7230 | while (n && (n != n2)) | ||
7231 | { | ||
7232 | Evas_Object_Textblock_Node_Text *nnode; | ||
7233 | |||
7234 | nnode = _NODE_TEXT(EINA_INLIST_GET(n)->next); | ||
7235 | _evas_textblock_cursors_set_node(o, n, n1); | ||
7236 | _evas_textblock_node_text_remove(o, n); | ||
7237 | n = nnode; | ||
7238 | } | ||
7239 | should_merge = _evas_textblock_node_text_adjust_offsets_to_start(o, n2, | ||
7240 | 0, cur2->pos); | ||
7241 | |||
7242 | /* Remove the formats and the strings in the first and last nodes */ | ||
7243 | len = eina_ustrbuf_length_get(n1->unicode); | ||
7244 | eina_ustrbuf_remove(n1->unicode, cur1->pos, len); | ||
7245 | eina_ustrbuf_remove(n2->unicode, 0, cur2->pos); | ||
7246 | /* Merge the nodes because we removed the PS */ | ||
7247 | _evas_textblock_cursors_update_offset(cur1, cur1->node, cur1->pos, | ||
7248 | - cur1->pos); | ||
7249 | _evas_textblock_cursors_update_offset(cur2, cur2->node, 0, - cur2->pos); | ||
7250 | _evas_textblock_nodes_merge(o, n1); | ||
7251 | } | ||
7252 | fnode = _evas_textblock_cursor_node_format_at_pos_get(cur1); | ||
7253 | |||
7254 | if (should_merge) | ||
7255 | { | ||
7256 | /* We call this function instead of the cursor one because we already | ||
7257 | * updated the cursors */ | ||
7258 | _evas_textblock_nodes_merge(o, n1); | ||
7259 | } | ||
7260 | _evas_textblock_node_format_remove_matching(o, fnode); | ||
7261 | |||
7262 | evas_textblock_cursor_copy(cur1, cur2); | ||
7263 | if (reset_cursor) | ||
7264 | evas_textblock_cursor_copy(cur1, o->cursor); | ||
7265 | |||
7266 | _evas_textblock_changed(o, cur1->obj); | ||
7267 | n1->dirty = n2->dirty = EINA_TRUE; | ||
7268 | } | ||
7269 | |||
7270 | |||
7271 | EAPI char * | ||
7272 | evas_textblock_cursor_content_get(const Evas_Textblock_Cursor *cur) | ||
7273 | { | ||
7274 | const Eina_Unicode *ustr; | ||
7275 | Eina_Unicode buf[2]; | ||
7276 | char *s; | ||
7277 | if (!cur || !cur->node) return NULL; | ||
7278 | if (evas_textblock_cursor_format_is_visible_get(cur)) | ||
7279 | { | ||
7280 | size_t len; | ||
7281 | const char *fstr; | ||
7282 | char *ret; | ||
7283 | int pop = 0; | ||
7284 | fstr = evas_textblock_node_format_text_get( | ||
7285 | _evas_textblock_node_visible_at_pos_get( | ||
7286 | evas_textblock_cursor_format_get(cur))); | ||
7287 | |||
7288 | if (!fstr) | ||
7289 | return NULL; | ||
7290 | |||
7291 | if (*fstr == '-') pop = 1; | ||
7292 | while ((*fstr == ' ') || (*fstr == '+') || (*fstr == '-')) fstr++; | ||
7293 | len = strlen(fstr); | ||
7294 | |||
7295 | { | ||
7296 | char *tmp; | ||
7297 | if (pop) | ||
7298 | { | ||
7299 | ret = tmp = malloc(len + 3 + 1); /* </> and the null */ | ||
7300 | memcpy(tmp, "</", 2); | ||
7301 | tmp += 2; | ||
7302 | } | ||
7303 | else | ||
7304 | { | ||
7305 | ret = tmp = malloc(len + 2 + 1); /* <> and the null */ | ||
7306 | *tmp = '<'; | ||
7307 | tmp++; | ||
7308 | } | ||
7309 | memcpy(tmp, fstr, len); | ||
7310 | memcpy(tmp + len, ">", 2); /* Including the null */ | ||
7311 | } | ||
7312 | |||
7313 | return ret; | ||
7314 | } | ||
7315 | |||
7316 | ustr = eina_ustrbuf_string_get(cur->node->unicode); | ||
7317 | buf[0] = ustr[cur->pos]; | ||
7318 | buf[1] = 0; | ||
7319 | s = eina_unicode_unicode_to_utf8(buf, NULL); | ||
7320 | |||
7321 | return s; | ||
7322 | } | ||
7323 | |||
7324 | static char * | ||
7325 | _evas_textblock_cursor_range_text_markup_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *_cur2) | ||
7326 | { | ||
7327 | Evas_Object_Textblock *o; | ||
7328 | Evas_Object_Textblock_Node_Text *tnode; | ||
7329 | Eina_Strbuf *buf; | ||
7330 | Evas_Textblock_Cursor *cur2; | ||
7331 | buf = eina_strbuf_new(); | ||
7332 | |||
7333 | if (!cur1 || !cur1->node) return NULL; | ||
7334 | if (!_cur2 || !_cur2->node) return NULL; | ||
7335 | if (cur1->obj != _cur2->obj) return NULL; | ||
7336 | o = (Evas_Object_Textblock *)(cur1->obj->object_data); | ||
7337 | if (evas_textblock_cursor_compare(cur1, _cur2) > 0) | ||
7338 | { | ||
7339 | const Evas_Textblock_Cursor *tc; | ||
7340 | |||
7341 | tc = cur1; | ||
7342 | cur1 = _cur2; | ||
7343 | _cur2 = tc; | ||
7344 | } | ||
7345 | /* Work on a local copy of the cur */ | ||
7346 | cur2 = alloca(sizeof(Evas_Textblock_Cursor)); | ||
7347 | cur2->obj = _cur2->obj; | ||
7348 | evas_textblock_cursor_copy(_cur2, cur2); | ||
7349 | |||
7350 | /* Parse the text between the cursors. */ | ||
7351 | for (tnode = cur1->node ; tnode ; | ||
7352 | tnode = _NODE_TEXT(EINA_INLIST_GET(tnode)->next)) | ||
7353 | { | ||
7354 | Evas_Object_Textblock_Node_Format *fnode; | ||
7355 | Eina_Unicode *text_base, *text; | ||
7356 | int off = 0; | ||
7357 | |||
7358 | text_base = text = | ||
7359 | eina_unicode_strndup(eina_ustrbuf_string_get(tnode->unicode), | ||
7360 | eina_ustrbuf_length_get(tnode->unicode)); | ||
7361 | if (tnode == cur2->node) | ||
7362 | { | ||
7363 | fnode = _evas_textblock_node_text_get_first_format_between(tnode, | ||
7364 | cur1->pos, cur2->pos); | ||
7365 | } | ||
7366 | else if (tnode == cur1->node) | ||
7367 | { | ||
7368 | fnode = _evas_textblock_node_text_get_first_format_between(tnode, | ||
7369 | cur1->pos, -1); | ||
7370 | } | ||
7371 | else | ||
7372 | { | ||
7373 | fnode = _evas_textblock_node_text_get_first_format_between(tnode, | ||
7374 | 0, -1); | ||
7375 | } | ||
7376 | /* Init the offset so the first one will count starting from cur1->pos | ||
7377 | * and not the previous format node */ | ||
7378 | if (tnode == cur1->node) | ||
7379 | { | ||
7380 | if (fnode) | ||
7381 | { | ||
7382 | off = _evas_textblock_node_format_pos_get(fnode) - | ||
7383 | cur1->pos - fnode->offset; | ||
7384 | } | ||
7385 | text += cur1->pos; | ||
7386 | } | ||
7387 | else | ||
7388 | { | ||
7389 | off = 0; | ||
7390 | } | ||
7391 | while (fnode && (fnode->text_node == tnode)) | ||
7392 | { | ||
7393 | Eina_Unicode tmp_ch; | ||
7394 | off += fnode->offset; | ||
7395 | if ((tnode == cur2->node) && | ||
7396 | ((size_t) (text - text_base + off) >= cur2->pos)) | ||
7397 | { | ||
7398 | break; | ||
7399 | } | ||
7400 | /* No need to skip on the first run */ | ||
7401 | tmp_ch = text[off]; | ||
7402 | text[off] = 0; /* Null terminate the part of the string */ | ||
7403 | _markup_get_text_append(buf, text); | ||
7404 | _markup_get_format_append(o, buf, fnode); | ||
7405 | text[off] = tmp_ch; /* Restore the char */ | ||
7406 | text += off; | ||
7407 | if (fnode->visible) | ||
7408 | { | ||
7409 | off = -1; | ||
7410 | text++; | ||
7411 | } | ||
7412 | else | ||
7413 | { | ||
7414 | off = 0; | ||
7415 | } | ||
7416 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
7417 | } | ||
7418 | /* If we got to the last node, stop and add the rest outside */ | ||
7419 | if (cur2->node == tnode) | ||
7420 | { | ||
7421 | /* Add the rest, skip replacement */ | ||
7422 | /* Don't go past the second cursor pos */ | ||
7423 | text_base[cur2->pos] = '\0'; | ||
7424 | _markup_get_text_append(buf, text); | ||
7425 | free(text_base); | ||
7426 | break; | ||
7427 | } | ||
7428 | else | ||
7429 | { | ||
7430 | /* Add the rest, skip replacement */ | ||
7431 | _markup_get_text_append(buf, text); | ||
7432 | free(text_base); | ||
7433 | } | ||
7434 | } | ||
7435 | /* return the string */ | ||
7436 | { | ||
7437 | char *ret; | ||
7438 | ret = eina_strbuf_string_steal(buf); | ||
7439 | eina_strbuf_free(buf); | ||
7440 | return ret; | ||
7441 | } | ||
7442 | } | ||
7443 | |||
7444 | static char * | ||
7445 | _evas_textblock_cursor_range_text_plain_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *_cur2) | ||
7446 | { | ||
7447 | Eina_UStrbuf *buf; | ||
7448 | Evas_Object_Textblock_Node_Text *n1, *n2; | ||
7449 | Evas_Textblock_Cursor *cur2; | ||
7450 | |||
7451 | buf = eina_ustrbuf_new(); | ||
7452 | |||
7453 | if (!cur1 || !cur1->node) return NULL; | ||
7454 | if (!_cur2 || !_cur2->node) return NULL; | ||
7455 | if (cur1->obj != _cur2->obj) return NULL; | ||
7456 | if (evas_textblock_cursor_compare(cur1, _cur2) > 0) | ||
7457 | { | ||
7458 | const Evas_Textblock_Cursor *tc; | ||
7459 | |||
7460 | tc = cur1; | ||
7461 | cur1 = _cur2; | ||
7462 | _cur2 = tc; | ||
7463 | } | ||
7464 | n1 = cur1->node; | ||
7465 | n2 = _cur2->node; | ||
7466 | /* Work on a local copy of the cur */ | ||
7467 | cur2 = alloca(sizeof(Evas_Textblock_Cursor)); | ||
7468 | cur2->obj = _cur2->obj; | ||
7469 | evas_textblock_cursor_copy(_cur2, cur2); | ||
7470 | |||
7471 | |||
7472 | if (n1 == n2) | ||
7473 | { | ||
7474 | const Eina_Unicode *tmp; | ||
7475 | tmp = eina_ustrbuf_string_get(n1->unicode); | ||
7476 | eina_ustrbuf_append_length(buf, tmp + cur1->pos, cur2->pos - cur1->pos); | ||
7477 | } | ||
7478 | else | ||
7479 | { | ||
7480 | const Eina_Unicode *tmp; | ||
7481 | tmp = eina_ustrbuf_string_get(n1->unicode); | ||
7482 | eina_ustrbuf_append(buf, tmp + cur1->pos); | ||
7483 | n1 = _NODE_TEXT(EINA_INLIST_GET(n1)->next); | ||
7484 | while (n1 != n2) | ||
7485 | { | ||
7486 | tmp = eina_ustrbuf_string_get(n1->unicode); | ||
7487 | eina_ustrbuf_append_length(buf, tmp, | ||
7488 | eina_ustrbuf_length_get(n1->unicode)); | ||
7489 | n1 = _NODE_TEXT(EINA_INLIST_GET(n1)->next); | ||
7490 | } | ||
7491 | tmp = eina_ustrbuf_string_get(n2->unicode); | ||
7492 | eina_ustrbuf_append_length(buf, tmp, cur2->pos); | ||
7493 | } | ||
7494 | |||
7495 | /* Free and return */ | ||
7496 | { | ||
7497 | char *ret; | ||
7498 | ret = eina_unicode_unicode_to_utf8(eina_ustrbuf_string_get(buf), NULL); | ||
7499 | eina_ustrbuf_free(buf); | ||
7500 | return ret; | ||
7501 | } | ||
7502 | } | ||
7503 | |||
7504 | EAPI Eina_List * | ||
7505 | evas_textblock_cursor_range_formats_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2) | ||
7506 | { | ||
7507 | Evas_Object *obj = cur1->obj; | ||
7508 | Eina_List *ret = NULL; | ||
7509 | Evas_Object_Textblock_Node_Text *n1, *n2; | ||
7510 | Evas_Object_Textblock_Node_Format *first, *last; | ||
7511 | TB_HEAD_RETURN(NULL); | ||
7512 | if (!cur1 || !cur1->node) return NULL; | ||
7513 | if (!cur2 || !cur2->node) return NULL; | ||
7514 | if (cur1->obj != cur2->obj) return NULL; | ||
7515 | if (evas_textblock_cursor_compare(cur1, cur2) > 0) | ||
7516 | { | ||
7517 | const Evas_Textblock_Cursor *tc; | ||
7518 | |||
7519 | tc = cur1; | ||
7520 | cur1 = cur2; | ||
7521 | cur2 = tc; | ||
7522 | } | ||
7523 | n1 = cur1->node; | ||
7524 | n2 = cur2->node; | ||
7525 | |||
7526 | /* FIXME: Change first and last getting to format_before_or_at_pos_get */ | ||
7527 | |||
7528 | last = n2->format_node; | ||
7529 | |||
7530 | /* If n2->format_node is NULL, we don't have formats in the tb/range. */ | ||
7531 | if (!last) | ||
7532 | return NULL; | ||
7533 | /* If the found format is on our text node, we should go to the last | ||
7534 | * one, otherwise, the one we found is good enough. */ | ||
7535 | if (last->text_node == n2) | ||
7536 | { | ||
7537 | Evas_Object_Textblock_Node_Format *fnode = last; | ||
7538 | while (fnode && (fnode->text_node == n2)) | ||
7539 | { | ||
7540 | last = fnode; | ||
7541 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
7542 | } | ||
7543 | } | ||
7544 | |||
7545 | /* If the first format node is within the range (i.e points to n1) or if | ||
7546 | * we have other formats in the range, go through them */ | ||
7547 | first = n1->format_node; | ||
7548 | if ((first->text_node == n1) || (first != last)) | ||
7549 | { | ||
7550 | Evas_Object_Textblock_Node_Format *fnode = first; | ||
7551 | /* Go to the first one in the range */ | ||
7552 | if (first->text_node != n1) | ||
7553 | { | ||
7554 | first = _NODE_FORMAT(EINA_INLIST_GET(first)->next); | ||
7555 | } | ||
7556 | |||
7557 | while (fnode) | ||
7558 | { | ||
7559 | ret = eina_list_append(ret, fnode); | ||
7560 | if (fnode == last) | ||
7561 | break; | ||
7562 | fnode = _NODE_FORMAT(EINA_INLIST_GET(fnode)->next); | ||
7563 | } | ||
7564 | } | ||
7565 | |||
7566 | return ret; | ||
7567 | |||
7568 | } | ||
7569 | |||
7570 | EAPI char * | ||
7571 | evas_textblock_cursor_range_text_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2, Evas_Textblock_Text_Type format) | ||
7572 | { | ||
7573 | if (format == EVAS_TEXTBLOCK_TEXT_MARKUP) | ||
7574 | return _evas_textblock_cursor_range_text_markup_get(cur1, cur2); | ||
7575 | else if (format == EVAS_TEXTBLOCK_TEXT_PLAIN) | ||
7576 | return _evas_textblock_cursor_range_text_plain_get(cur1, cur2); | ||
7577 | else | ||
7578 | return NULL; /* Not yet supported */ | ||
7579 | } | ||
7580 | |||
7581 | EAPI const char * | ||
7582 | evas_textblock_cursor_paragraph_text_get(const Evas_Textblock_Cursor *cur) | ||
7583 | { | ||
7584 | Evas_Textblock_Cursor cur1, cur2; | ||
7585 | if (!cur) return NULL; | ||
7586 | if (!cur->node) return NULL; | ||
7587 | if (cur->node->utf8) | ||
7588 | { | ||
7589 | free(cur->node->utf8); | ||
7590 | } | ||
7591 | cur1.obj = cur2.obj = cur->obj; | ||
7592 | cur1.node = cur2.node = cur->node; | ||
7593 | evas_textblock_cursor_paragraph_char_first(&cur1); | ||
7594 | evas_textblock_cursor_paragraph_char_last(&cur2); | ||
7595 | |||
7596 | cur->node->utf8 = evas_textblock_cursor_range_text_get(&cur1, &cur2, | ||
7597 | EVAS_TEXTBLOCK_TEXT_MARKUP); | ||
7598 | return cur->node->utf8; | ||
7599 | } | ||
7600 | |||
7601 | EAPI int | ||
7602 | evas_textblock_cursor_paragraph_text_length_get(const Evas_Textblock_Cursor *cur) | ||
7603 | { | ||
7604 | int len; | ||
7605 | if (!cur) return -1; | ||
7606 | if (!cur->node) return -1; | ||
7607 | len = eina_ustrbuf_length_get(cur->node->unicode); | ||
7608 | |||
7609 | if (EINA_INLIST_GET(cur->node)->next) | ||
7610 | return len - 1; /* Remove the paragraph separator */ | ||
7611 | else | ||
7612 | return len; | ||
7613 | } | ||
7614 | |||
7615 | EAPI const Evas_Object_Textblock_Node_Format * | ||
7616 | evas_textblock_cursor_format_get(const Evas_Textblock_Cursor *cur) | ||
7617 | { | ||
7618 | if (!cur) return NULL; | ||
7619 | if (!cur->node) return NULL; | ||
7620 | return _evas_textblock_cursor_node_format_at_pos_get(cur); | ||
7621 | } | ||
7622 | |||
7623 | EAPI const char * | ||
7624 | evas_textblock_node_format_text_get(const Evas_Object_Textblock_Node_Format *fmt) | ||
7625 | { | ||
7626 | if (!fmt) return NULL; | ||
7627 | return fmt->orig_format; | ||
7628 | } | ||
7629 | |||
7630 | EAPI void | ||
7631 | evas_textblock_cursor_at_format_set(Evas_Textblock_Cursor *cur, const Evas_Object_Textblock_Node_Format *fmt) | ||
7632 | { | ||
7633 | if (!fmt || !cur) return; | ||
7634 | cur->node = fmt->text_node; | ||
7635 | cur->pos = _evas_textblock_node_format_pos_get(fmt); | ||
7636 | } | ||
7637 | |||
7638 | EAPI Eina_Bool | ||
7639 | evas_textblock_cursor_format_is_visible_get(const Evas_Textblock_Cursor *cur) | ||
7640 | { | ||
7641 | const Eina_Unicode *text; | ||
7642 | |||
7643 | if (!cur) return EINA_FALSE; | ||
7644 | if (!cur->node) return EINA_FALSE; | ||
7645 | text = eina_ustrbuf_string_get(cur->node->unicode); | ||
7646 | return EVAS_TEXTBLOCK_IS_VISIBLE_FORMAT_CHAR(text[cur->pos]); | ||
7647 | } | ||
7648 | |||
7649 | EAPI int | ||
7650 | evas_textblock_cursor_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch, Evas_BiDi_Direction *dir, Evas_Textblock_Cursor_Type ctype) | ||
7651 | { | ||
7652 | int ret = -1; | ||
7653 | const Evas_Textblock_Cursor *dir_cur; | ||
7654 | Evas_Textblock_Cursor cur2; | ||
7655 | Evas_Object_Textblock *o; | ||
7656 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7657 | if (!o->formatted.valid) _relayout(cur->obj); | ||
7658 | |||
7659 | dir_cur = cur; | ||
7660 | if (ctype == EVAS_TEXTBLOCK_CURSOR_UNDER) | ||
7661 | { | ||
7662 | ret = evas_textblock_cursor_pen_geometry_get(cur, cx, cy, cw, ch); | ||
7663 | } | ||
7664 | else if (ctype == EVAS_TEXTBLOCK_CURSOR_BEFORE) | ||
7665 | { | ||
7666 | /* In the case of a "before cursor", we should get the coordinates | ||
7667 | * of just after the previous char (which in bidi text may not be | ||
7668 | * just before the current char). */ | ||
7669 | Evas_Coord x, y, h, w; | ||
7670 | Evas_Object_Textblock_Node_Format *fmt; | ||
7671 | |||
7672 | /* If it's at the end of the line, we want to get the position, not | ||
7673 | * the position of the previous */ | ||
7674 | if ((cur->pos > 0) && !_evas_textblock_cursor_is_at_the_end(cur)) | ||
7675 | { | ||
7676 | Eina_Bool before_char = EINA_FALSE; | ||
7677 | cur2.obj = cur->obj; | ||
7678 | evas_textblock_cursor_copy(cur, &cur2); | ||
7679 | evas_textblock_cursor_char_prev(&cur2); | ||
7680 | |||
7681 | fmt = _evas_textblock_cursor_node_format_at_pos_get(&cur2); | ||
7682 | |||
7683 | if (!fmt || !_IS_LINE_SEPARATOR(fmt->format)) | ||
7684 | { | ||
7685 | dir_cur = &cur2; | ||
7686 | before_char = EINA_FALSE; | ||
7687 | } | ||
7688 | else | ||
7689 | { | ||
7690 | before_char = EINA_TRUE; | ||
7691 | } | ||
7692 | ret = evas_textblock_cursor_pen_geometry_get( | ||
7693 | dir_cur, &x, &y, &w, &h); | ||
7694 | #ifdef BIDI_SUPPORT | ||
7695 | /* Adjust if the char is an rtl char */ | ||
7696 | if (ret >= 0) | ||
7697 | { | ||
7698 | Eina_Bool is_rtl = EINA_FALSE; | ||
7699 | if (dir_cur->node->par->is_bidi) | ||
7700 | { | ||
7701 | Evas_Object_Textblock_Line *ln; | ||
7702 | Evas_Object_Textblock_Item *it; | ||
7703 | _find_layout_item_match(dir_cur, &ln, &it); | ||
7704 | if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
7705 | (_ITEM_TEXT(it)->text_props.bidi.dir == | ||
7706 | EVAS_BIDI_DIRECTION_RTL)) | ||
7707 | is_rtl = EINA_TRUE; | ||
7708 | else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
7709 | (_ITEM_FORMAT(it)->bidi_dir == | ||
7710 | EVAS_BIDI_DIRECTION_RTL)) | ||
7711 | is_rtl = EINA_TRUE; | ||
7712 | } | ||
7713 | |||
7714 | if ((!before_char && is_rtl) || | ||
7715 | (before_char && !is_rtl)) | ||
7716 | { | ||
7717 | /* Just don't advance the width */ | ||
7718 | w = 0; | ||
7719 | } | ||
7720 | } | ||
7721 | #endif | ||
7722 | } | ||
7723 | else if (cur->pos == 0) | ||
7724 | { | ||
7725 | ret = evas_textblock_cursor_pen_geometry_get( | ||
7726 | dir_cur, &x, &y, &w, &h); | ||
7727 | #ifdef BIDI_SUPPORT | ||
7728 | Eina_Bool is_rtl = EINA_FALSE; | ||
7729 | if (dir_cur->node && dir_cur->node->par->is_bidi) | ||
7730 | { | ||
7731 | Evas_Object_Textblock_Line *ln; | ||
7732 | Evas_Object_Textblock_Item *it; | ||
7733 | _find_layout_item_match(dir_cur, &ln, &it); | ||
7734 | if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
7735 | (_ITEM_TEXT(it)->text_props.bidi.dir == | ||
7736 | EVAS_BIDI_DIRECTION_RTL)) | ||
7737 | is_rtl = EINA_TRUE; | ||
7738 | else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
7739 | (_ITEM_FORMAT(it)->bidi_dir == | ||
7740 | EVAS_BIDI_DIRECTION_RTL)) | ||
7741 | is_rtl = EINA_TRUE; | ||
7742 | } | ||
7743 | |||
7744 | /* Adjust if the char is an rtl char */ | ||
7745 | if ((ret >= 0) && (!is_rtl)) | ||
7746 | { | ||
7747 | /* Just don't advance the width */ | ||
7748 | w = 0; | ||
7749 | } | ||
7750 | #endif | ||
7751 | } | ||
7752 | else | ||
7753 | { | ||
7754 | ret = evas_textblock_cursor_pen_geometry_get( | ||
7755 | dir_cur, &x, &y, &w, &h); | ||
7756 | } | ||
7757 | if (ret >= 0) | ||
7758 | { | ||
7759 | if (cx) *cx = x + w; | ||
7760 | if (cy) *cy = y; | ||
7761 | if (cw) *cw = 0; | ||
7762 | if (ch) *ch = h; | ||
7763 | } | ||
7764 | } | ||
7765 | |||
7766 | if (dir && dir_cur && dir_cur->node) | ||
7767 | { | ||
7768 | #ifdef BIDI_SUPPORT | ||
7769 | Eina_Bool is_rtl = EINA_FALSE; | ||
7770 | if (dir_cur->node->par->is_bidi) | ||
7771 | { | ||
7772 | Evas_Object_Textblock_Line *ln; | ||
7773 | Evas_Object_Textblock_Item *it; | ||
7774 | _find_layout_item_match(dir_cur, &ln, &it); | ||
7775 | if ((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
7776 | (_ITEM_TEXT(it)->text_props.bidi.dir == | ||
7777 | EVAS_BIDI_DIRECTION_RTL)) | ||
7778 | is_rtl = EINA_TRUE; | ||
7779 | else if ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
7780 | (_ITEM_FORMAT(it)->bidi_dir == | ||
7781 | EVAS_BIDI_DIRECTION_RTL)) | ||
7782 | is_rtl = EINA_TRUE; | ||
7783 | } | ||
7784 | |||
7785 | if (_evas_textblock_cursor_is_at_the_end(dir_cur) && (dir_cur->pos > 0)) | ||
7786 | { | ||
7787 | *dir = (is_rtl) ? | ||
7788 | EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR; | ||
7789 | } | ||
7790 | else if (dir_cur->pos > 0) | ||
7791 | { | ||
7792 | *dir = (is_rtl) ? | ||
7793 | EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR; | ||
7794 | } | ||
7795 | else | ||
7796 | #endif | ||
7797 | { | ||
7798 | *dir = EVAS_BIDI_DIRECTION_LTR; | ||
7799 | } | ||
7800 | } | ||
7801 | return ret; | ||
7802 | } | ||
7803 | |||
7804 | /** | ||
7805 | * @internal | ||
7806 | * Returns the geometry/pen position (depending on query_func) of the char | ||
7807 | * at pos. | ||
7808 | * | ||
7809 | * @param cur the position of the char. | ||
7810 | * @param query_func the query function to use. | ||
7811 | * @param cx the x of the char (or pen_x in the case of pen position). | ||
7812 | * @param cy the y of the char. | ||
7813 | * @param cw the w of the char (or advance in the case pen position). | ||
7814 | * @param ch the h of the char. | ||
7815 | * @return line number of the char on success, -1 on error. | ||
7816 | */ | ||
7817 | static int | ||
7818 | _evas_textblock_cursor_char_pen_geometry_common_get(int (*query_func) (void *data, Evas_Font_Set *font, const Evas_Text_Props *intl_props, int pos, int *cx, int *cy, int *cw, int *ch), const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
7819 | { | ||
7820 | Evas_Object_Textblock *o; | ||
7821 | Evas_Object_Textblock_Line *ln = NULL; | ||
7822 | Evas_Object_Textblock_Item *it = NULL; | ||
7823 | Evas_Object_Textblock_Text_Item *ti = NULL; | ||
7824 | Evas_Object_Textblock_Format_Item *fi = NULL; | ||
7825 | int x = 0, y = 0, w = 0, h = 0; | ||
7826 | int pos; | ||
7827 | Eina_Bool previous_format; | ||
7828 | |||
7829 | if (!cur) return -1; | ||
7830 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7831 | if (!o->formatted.valid) _relayout(cur->obj); | ||
7832 | |||
7833 | if (!cur->node) | ||
7834 | { | ||
7835 | if (!o->text_nodes) | ||
7836 | { | ||
7837 | if (!o->paragraphs) return -1; | ||
7838 | ln = o->paragraphs->lines; | ||
7839 | if (!ln) return -1; | ||
7840 | if (cx) *cx = ln->x; | ||
7841 | if (cy) *cy = ln->par->y + ln->y; | ||
7842 | if (cw) *cw = ln->w; | ||
7843 | if (ch) *ch = ln->h; | ||
7844 | return ln->par->line_no + ln->line_no; | ||
7845 | } | ||
7846 | else | ||
7847 | return -1; | ||
7848 | } | ||
7849 | |||
7850 | previous_format = _find_layout_item_match(cur, &ln, &it); | ||
7851 | if (!it) | ||
7852 | { | ||
7853 | return -1; | ||
7854 | } | ||
7855 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
7856 | { | ||
7857 | ti = _ITEM_TEXT(it); | ||
7858 | } | ||
7859 | else | ||
7860 | { | ||
7861 | fi = _ITEM_FORMAT(it); | ||
7862 | } | ||
7863 | |||
7864 | if (ln && ti) | ||
7865 | { | ||
7866 | pos = cur->pos - ti->parent.text_pos; | ||
7867 | |||
7868 | if (pos < 0) pos = 0; | ||
7869 | if (ti->parent.format->font.font) | ||
7870 | { | ||
7871 | query_func(cur->ENDT, | ||
7872 | ti->parent.format->font.font, | ||
7873 | &ti->text_props, | ||
7874 | pos, | ||
7875 | &x, &y, &w, &h); | ||
7876 | } | ||
7877 | |||
7878 | x += ln->x + _ITEM(ti)->x; | ||
7879 | |||
7880 | if (x < ln->x) | ||
7881 | { | ||
7882 | x = ln->x; | ||
7883 | } | ||
7884 | y = ln->par->y + ln->y; | ||
7885 | h = ln->h; | ||
7886 | } | ||
7887 | else if (ln && fi) | ||
7888 | { | ||
7889 | if (previous_format) | ||
7890 | { | ||
7891 | if (_IS_LINE_SEPARATOR(fi->item)) | ||
7892 | { | ||
7893 | x = 0; | ||
7894 | y = ln->par->y + ln->y + ln->h; | ||
7895 | } | ||
7896 | else | ||
7897 | { | ||
7898 | #ifdef BIDI_SUPPORT | ||
7899 | if (ln->par->direction == EVAS_BIDI_DIRECTION_RTL) | ||
7900 | { | ||
7901 | x = ln->x; | ||
7902 | } | ||
7903 | else | ||
7904 | #endif | ||
7905 | { | ||
7906 | x = ln->x + ln->w; | ||
7907 | } | ||
7908 | y = ln->par->y + ln->y; | ||
7909 | } | ||
7910 | w = 0; | ||
7911 | h = ln->h; | ||
7912 | } | ||
7913 | else | ||
7914 | { | ||
7915 | x = ln->x + _ITEM(fi)->x; | ||
7916 | y = ln->par->y + ln->y; | ||
7917 | w = _ITEM(fi)->w; | ||
7918 | h = ln->h; | ||
7919 | } | ||
7920 | } | ||
7921 | else | ||
7922 | { | ||
7923 | return -1; | ||
7924 | } | ||
7925 | if (cx) *cx = x; | ||
7926 | if (cy) *cy = y; | ||
7927 | if (cw) *cw = w; | ||
7928 | if (ch) *ch = h; | ||
7929 | return ln->par->line_no + ln->line_no; | ||
7930 | } | ||
7931 | |||
7932 | EAPI int | ||
7933 | evas_textblock_cursor_char_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
7934 | { | ||
7935 | return _evas_textblock_cursor_char_pen_geometry_common_get( | ||
7936 | cur->ENFN->font_char_coords_get, cur, cx, cy, cw, ch); | ||
7937 | } | ||
7938 | |||
7939 | EAPI int | ||
7940 | evas_textblock_cursor_pen_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
7941 | { | ||
7942 | return _evas_textblock_cursor_char_pen_geometry_common_get( | ||
7943 | cur->ENFN->font_pen_coords_get, cur, cx, cy, cw, ch); | ||
7944 | } | ||
7945 | |||
7946 | EAPI int | ||
7947 | evas_textblock_cursor_line_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
7948 | { | ||
7949 | Evas_Object_Textblock *o; | ||
7950 | Evas_Object_Textblock_Line *ln = NULL; | ||
7951 | Evas_Object_Textblock_Item *it = NULL; | ||
7952 | int x, y, w, h; | ||
7953 | |||
7954 | if (!cur) return -1; | ||
7955 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
7956 | if (!o->formatted.valid) _relayout(cur->obj); | ||
7957 | if (!cur->node) | ||
7958 | { | ||
7959 | ln = o->paragraphs->lines; | ||
7960 | } | ||
7961 | else | ||
7962 | { | ||
7963 | _find_layout_item_match(cur, &ln, &it); | ||
7964 | } | ||
7965 | if (!ln) return -1; | ||
7966 | x = ln->x; | ||
7967 | y = ln->par->y + ln->y; | ||
7968 | w = ln->w; | ||
7969 | h = ln->h; | ||
7970 | if (cx) *cx = x; | ||
7971 | if (cy) *cy = y; | ||
7972 | if (cw) *cw = w; | ||
7973 | if (ch) *ch = h; | ||
7974 | return ln->par->line_no + ln->line_no; | ||
7975 | } | ||
7976 | |||
7977 | EAPI Eina_Bool | ||
7978 | evas_textblock_cursor_visible_range_get(Evas_Textblock_Cursor *start, Evas_Textblock_Cursor *end) | ||
7979 | { | ||
7980 | Evas *e; | ||
7981 | Evas_Coord cy, ch; | ||
7982 | Evas_Object *obj = start->obj; | ||
7983 | TB_HEAD_RETURN(EINA_FALSE); | ||
7984 | e = evas_object_evas_get(obj); | ||
7985 | cy = 0 - obj->cur.geometry.y; | ||
7986 | ch = e->viewport.h; | ||
7987 | evas_textblock_cursor_line_coord_set(start, cy); | ||
7988 | evas_textblock_cursor_line_coord_set(end, cy + ch); | ||
7989 | evas_textblock_cursor_line_char_last(end); | ||
7990 | |||
7991 | return EINA_TRUE; | ||
7992 | } | ||
7993 | |||
7994 | EAPI Eina_Bool | ||
7995 | evas_textblock_cursor_char_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord x, Evas_Coord y) | ||
7996 | { | ||
7997 | Evas_Object_Textblock *o; | ||
7998 | Evas_Object_Textblock_Paragraph *found_par; | ||
7999 | Evas_Object_Textblock_Line *ln; | ||
8000 | Evas_Object_Textblock_Item *it = NULL; | ||
8001 | |||
8002 | if (!cur) return EINA_FALSE; | ||
8003 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
8004 | if (!o->formatted.valid) _relayout(cur->obj); | ||
8005 | x += o->style_pad.l; | ||
8006 | y += o->style_pad.t; | ||
8007 | |||
8008 | found_par = _layout_find_paragraph_by_y(o, y); | ||
8009 | if (found_par) | ||
8010 | { | ||
8011 | _layout_paragraph_render(o, found_par); | ||
8012 | EINA_INLIST_FOREACH(found_par->lines, ln) | ||
8013 | { | ||
8014 | if (ln->par->y + ln->y > y) break; | ||
8015 | if ((ln->par->y + ln->y <= y) && ((ln->par->y + ln->y + ln->h) > y)) | ||
8016 | { | ||
8017 | /* If before or after the line, go to start/end according | ||
8018 | * to paragraph direction. */ | ||
8019 | if (x < ln->x) | ||
8020 | { | ||
8021 | cur->pos = ln->items->text_pos; | ||
8022 | cur->node = found_par->text_node; | ||
8023 | if (found_par->direction == EVAS_BIDI_DIRECTION_RTL) | ||
8024 | { | ||
8025 | evas_textblock_cursor_line_char_last(cur); | ||
8026 | } | ||
8027 | else | ||
8028 | { | ||
8029 | evas_textblock_cursor_line_char_first(cur); | ||
8030 | } | ||
8031 | return EINA_TRUE; | ||
8032 | } | ||
8033 | else if (x >= ln->x + ln->w) | ||
8034 | { | ||
8035 | cur->pos = ln->items->text_pos; | ||
8036 | cur->node = found_par->text_node; | ||
8037 | if (found_par->direction == EVAS_BIDI_DIRECTION_RTL) | ||
8038 | { | ||
8039 | evas_textblock_cursor_line_char_first(cur); | ||
8040 | } | ||
8041 | else | ||
8042 | { | ||
8043 | evas_textblock_cursor_line_char_last(cur); | ||
8044 | } | ||
8045 | return EINA_TRUE; | ||
8046 | } | ||
8047 | |||
8048 | EINA_INLIST_FOREACH(ln->items, it) | ||
8049 | { | ||
8050 | if (((it->x + ln->x) <= x) && (((it->x + ln->x) + it->adv) > x)) | ||
8051 | { | ||
8052 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
8053 | { | ||
8054 | int pos; | ||
8055 | int cx, cy, cw, ch; | ||
8056 | Evas_Object_Textblock_Text_Item *ti; | ||
8057 | ti = _ITEM_TEXT(it); | ||
8058 | |||
8059 | pos = -1; | ||
8060 | if (ti->parent.format->font.font) | ||
8061 | pos = cur->ENFN->font_char_at_coords_get( | ||
8062 | cur->ENDT, | ||
8063 | ti->parent.format->font.font, | ||
8064 | &ti->text_props, | ||
8065 | x - it->x - ln->x, 0, | ||
8066 | &cx, &cy, &cw, &ch); | ||
8067 | if (pos < 0) | ||
8068 | return EINA_FALSE; | ||
8069 | cur->pos = pos + it->text_pos; | ||
8070 | cur->node = it->text_node; | ||
8071 | return EINA_TRUE; | ||
8072 | } | ||
8073 | else | ||
8074 | { | ||
8075 | Evas_Object_Textblock_Format_Item *fi; | ||
8076 | fi = _ITEM_FORMAT(it); | ||
8077 | cur->pos = fi->parent.text_pos; | ||
8078 | cur->node = found_par->text_node; | ||
8079 | return EINA_TRUE; | ||
8080 | } | ||
8081 | } | ||
8082 | } | ||
8083 | } | ||
8084 | } | ||
8085 | } | ||
8086 | else if (o->paragraphs && (y >= o->paragraphs->y + o->formatted.h)) | ||
8087 | { | ||
8088 | /* If we are after the last paragraph, use the last position in the | ||
8089 | * text. */ | ||
8090 | evas_textblock_cursor_paragraph_last(cur); | ||
8091 | return EINA_TRUE; | ||
8092 | } | ||
8093 | else if (o->paragraphs && (y < o->paragraphs->y)) | ||
8094 | { | ||
8095 | evas_textblock_cursor_paragraph_first(cur); | ||
8096 | return EINA_TRUE; | ||
8097 | } | ||
8098 | |||
8099 | return EINA_FALSE; | ||
8100 | } | ||
8101 | |||
8102 | EAPI int | ||
8103 | evas_textblock_cursor_line_coord_set(Evas_Textblock_Cursor *cur, Evas_Coord y) | ||
8104 | { | ||
8105 | Evas_Object_Textblock *o; | ||
8106 | Evas_Object_Textblock_Paragraph *found_par; | ||
8107 | Evas_Object_Textblock_Line *ln; | ||
8108 | |||
8109 | if (!cur) return -1; | ||
8110 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
8111 | if (!o->formatted.valid) _relayout(cur->obj); | ||
8112 | y += o->style_pad.t; | ||
8113 | |||
8114 | found_par = _layout_find_paragraph_by_y(o, y); | ||
8115 | |||
8116 | if (found_par) | ||
8117 | { | ||
8118 | _layout_paragraph_render(o, found_par); | ||
8119 | EINA_INLIST_FOREACH(found_par->lines, ln) | ||
8120 | { | ||
8121 | if (ln->par->y + ln->y > y) break; | ||
8122 | if ((ln->par->y + ln->y <= y) && ((ln->par->y + ln->y + ln->h) > y)) | ||
8123 | { | ||
8124 | evas_textblock_cursor_line_set(cur, ln->par->line_no + | ||
8125 | ln->line_no); | ||
8126 | return ln->par->line_no + ln->line_no; | ||
8127 | } | ||
8128 | } | ||
8129 | } | ||
8130 | else if (o->paragraphs && (y >= o->paragraphs->y + o->formatted.h)) | ||
8131 | { | ||
8132 | int line_no = 0; | ||
8133 | /* If we are after the last paragraph, use the last position in the | ||
8134 | * text. */ | ||
8135 | evas_textblock_cursor_paragraph_last(cur); | ||
8136 | if (cur->node && cur->node->par) | ||
8137 | { | ||
8138 | line_no = cur->node->par->line_no; | ||
8139 | if (cur->node->par->lines) | ||
8140 | { | ||
8141 | line_no += ((Evas_Object_Textblock_Line *) | ||
8142 | EINA_INLIST_GET(cur->node->par->lines)->last)->line_no; | ||
8143 | } | ||
8144 | } | ||
8145 | return line_no; | ||
8146 | } | ||
8147 | else if (o->paragraphs && (y < o->paragraphs->y)) | ||
8148 | { | ||
8149 | int line_no = 0; | ||
8150 | evas_textblock_cursor_paragraph_first(cur); | ||
8151 | if (cur->node && cur->node->par) | ||
8152 | { | ||
8153 | line_no = cur->node->par->line_no; | ||
8154 | } | ||
8155 | return line_no; | ||
8156 | } | ||
8157 | return -1; | ||
8158 | } | ||
8159 | |||
8160 | /** | ||
8161 | * @internal | ||
8162 | * Updates x and w according to the text direction, position in text and | ||
8163 | * if it's a special case switch | ||
8164 | * | ||
8165 | * @param ti the text item we are working on | ||
8166 | * @param x the current x (we get) and the x we return | ||
8167 | * @param w the current w (we get) and the w we return | ||
8168 | * @param start if this is the first item or not | ||
8169 | * @param switch_items toogles item switching (rtl cases) | ||
8170 | */ | ||
8171 | static void | ||
8172 | _evas_textblock_range_calc_x_w(const Evas_Object_Textblock_Item *it, | ||
8173 | Evas_Coord *x, Evas_Coord *w, Eina_Bool start, Eina_Bool switch_items) | ||
8174 | { | ||
8175 | if ((start && !switch_items) || (!start && switch_items)) | ||
8176 | { | ||
8177 | #ifdef BIDI_SUPPORT | ||
8178 | if (((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
8179 | _ITEM_TEXT(it)->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL) | ||
8180 | || | ||
8181 | ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
8182 | _ITEM_FORMAT(it)->bidi_dir == EVAS_BIDI_DIRECTION_RTL)) | ||
8183 | { | ||
8184 | *w = *x + *w; | ||
8185 | *x = 0; | ||
8186 | } | ||
8187 | else | ||
8188 | #endif | ||
8189 | { | ||
8190 | *w = it->adv - *x; | ||
8191 | } | ||
8192 | } | ||
8193 | else | ||
8194 | { | ||
8195 | #ifdef BIDI_SUPPORT | ||
8196 | if (((it->type == EVAS_TEXTBLOCK_ITEM_TEXT) && | ||
8197 | _ITEM_TEXT(it)->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL) | ||
8198 | || | ||
8199 | ((it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) && | ||
8200 | _ITEM_FORMAT(it)->bidi_dir == EVAS_BIDI_DIRECTION_RTL)) | ||
8201 | { | ||
8202 | *x = *x + *w; | ||
8203 | *w = it->adv - *x; | ||
8204 | } | ||
8205 | else | ||
8206 | #endif | ||
8207 | { | ||
8208 | *w = *x; | ||
8209 | *x = 0; | ||
8210 | } | ||
8211 | } | ||
8212 | |||
8213 | } | ||
8214 | |||
8215 | /** | ||
8216 | * @internal | ||
8217 | * Returns the geometry of the range in line ln. Cur1 is the start cursor, | ||
8218 | * cur2 is the end cursor, NULL means from the start or to the end accordingly. | ||
8219 | * Assumes that ln is valid, and that at least one of cur1 and cur2 is not NULL. | ||
8220 | * | ||
8221 | * @param ln the line to work on. | ||
8222 | * @param cur1 the start cursor | ||
8223 | * @param cur2 the end cursor | ||
8224 | * @return Returns the geometry of the range | ||
8225 | */ | ||
8226 | static Eina_List * | ||
8227 | _evas_textblock_cursor_range_in_line_geometry_get( | ||
8228 | const Evas_Object_Textblock_Line *ln, const Evas_Textblock_Cursor *cur1, | ||
8229 | const Evas_Textblock_Cursor *cur2) | ||
8230 | { | ||
8231 | Evas_Object_Textblock_Item *it; | ||
8232 | Evas_Object_Textblock_Item *it1, *it2; | ||
8233 | Eina_List *rects = NULL; | ||
8234 | Evas_Textblock_Rectangle *tr; | ||
8235 | size_t start, end; | ||
8236 | Eina_Bool switch_items; | ||
8237 | const Evas_Textblock_Cursor *cur; | ||
8238 | |||
8239 | cur = (cur1) ? cur1 : cur2; | ||
8240 | |||
8241 | if (!cur) return NULL; | ||
8242 | |||
8243 | /* Find the first and last items */ | ||
8244 | it1 = it2 = NULL; | ||
8245 | start = end = 0; | ||
8246 | EINA_INLIST_FOREACH(ln->items, it) | ||
8247 | { | ||
8248 | size_t item_len; | ||
8249 | item_len = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? | ||
8250 | _ITEM_TEXT(it)->text_props.text_len | ||
8251 | : 1; | ||
8252 | if ((!cur1 || (cur1->pos < it->text_pos + item_len)) && | ||
8253 | (!cur2 || (cur2->pos >= it->text_pos))) | ||
8254 | { | ||
8255 | if (!it1) | ||
8256 | { | ||
8257 | it1 = it; | ||
8258 | start = item_len; /* start stores the first item_len */ | ||
8259 | } | ||
8260 | it2 = it; | ||
8261 | end = item_len; /* end stores the last item_len */ | ||
8262 | } | ||
8263 | } | ||
8264 | |||
8265 | /* If we couldn't find even one item, return */ | ||
8266 | if (!it1) return NULL; | ||
8267 | |||
8268 | /* If the first item is logically before or equal the second item | ||
8269 | * we have to set start and end differently than in the other case */ | ||
8270 | if (it1->text_pos <= it2->text_pos) | ||
8271 | { | ||
8272 | start = (cur1) ? (cur1->pos - it1->text_pos) : 0; | ||
8273 | end = (cur2) ? (cur2->pos - it2->text_pos) : end; | ||
8274 | switch_items = EINA_FALSE; | ||
8275 | } | ||
8276 | else | ||
8277 | { | ||
8278 | start = (cur2) ? (cur2->pos - it1->text_pos) : start; | ||
8279 | end = (cur1) ? (cur1->pos - it2->text_pos) : 0; | ||
8280 | switch_items = EINA_TRUE; | ||
8281 | } | ||
8282 | |||
8283 | /* IMPORTANT: Don't use cur1/cur2 past this point (because they probably | ||
8284 | * don't make sense anymore. That's why there are start and end), | ||
8285 | * unless you know what you are doing */ | ||
8286 | |||
8287 | /* Special case when they share the same item and it's a text item */ | ||
8288 | if ((it1 == it2) && (it1->type == EVAS_TEXTBLOCK_ITEM_TEXT)) | ||
8289 | { | ||
8290 | Evas_Coord x1, w1, x2, w2; | ||
8291 | Evas_Coord x, w, y, h; | ||
8292 | Evas_Object_Textblock_Text_Item *ti; | ||
8293 | int ret = 0; | ||
8294 | |||
8295 | ti = _ITEM_TEXT(it1); | ||
8296 | if (ti->parent.format->font.font) | ||
8297 | { | ||
8298 | ret = cur->ENFN->font_pen_coords_get(cur->ENDT, | ||
8299 | ti->parent.format->font.font, | ||
8300 | &ti->text_props, | ||
8301 | start, | ||
8302 | &x1, &y, &w1, &h); | ||
8303 | } | ||
8304 | if (!ret) | ||
8305 | { | ||
8306 | return NULL; | ||
8307 | } | ||
8308 | ret = cur->ENFN->font_pen_coords_get(cur->ENDT, | ||
8309 | ti->parent.format->font.font, | ||
8310 | &ti->text_props, | ||
8311 | end, | ||
8312 | &x2, &y, &w2, &h); | ||
8313 | if (!ret) | ||
8314 | { | ||
8315 | return NULL; | ||
8316 | } | ||
8317 | |||
8318 | /* Make x2 the one on the right */ | ||
8319 | if (x2 < x1) | ||
8320 | { | ||
8321 | Evas_Coord tmp; | ||
8322 | tmp = x1; | ||
8323 | x1 = x2; | ||
8324 | x2 = tmp; | ||
8325 | |||
8326 | tmp = w1; | ||
8327 | w1 = w2; | ||
8328 | w2 = tmp; | ||
8329 | } | ||
8330 | |||
8331 | #ifdef BIDI_SUPPORT | ||
8332 | if (ti->text_props.bidi.dir == EVAS_BIDI_DIRECTION_RTL) | ||
8333 | { | ||
8334 | x = x1 + w1; | ||
8335 | w = x2 + w2 - x; | ||
8336 | } | ||
8337 | else | ||
8338 | #endif | ||
8339 | { | ||
8340 | x = x1; | ||
8341 | w = x2 - x1; | ||
8342 | } | ||
8343 | if (w > 0) | ||
8344 | { | ||
8345 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8346 | rects = eina_list_append(rects, tr); | ||
8347 | tr->x = ln->x + it1->x + x; | ||
8348 | tr->y = ln->par->y + ln->y; | ||
8349 | tr->h = ln->h; | ||
8350 | tr->w = w; | ||
8351 | } | ||
8352 | } | ||
8353 | else if (it1 != it2) | ||
8354 | { | ||
8355 | /* Get the middle items */ | ||
8356 | Evas_Coord min_x, max_x; | ||
8357 | Evas_Coord x, w; | ||
8358 | it = _ITEM(EINA_INLIST_GET(it1)->next); | ||
8359 | min_x = max_x = it->x; | ||
8360 | |||
8361 | if (it1->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
8362 | { | ||
8363 | Evas_Coord y, h; | ||
8364 | Evas_Object_Textblock_Text_Item *ti; | ||
8365 | int ret; | ||
8366 | ti = _ITEM_TEXT(it1); | ||
8367 | |||
8368 | ret = cur->ENFN->font_pen_coords_get(cur->ENDT, | ||
8369 | ti->parent.format->font.font, | ||
8370 | &ti->text_props, | ||
8371 | start, | ||
8372 | &x, &y, &w, &h); | ||
8373 | if (!ret) | ||
8374 | { | ||
8375 | /* BUG! Skip the first item */ | ||
8376 | x = w = 0; | ||
8377 | } | ||
8378 | else | ||
8379 | { | ||
8380 | _evas_textblock_range_calc_x_w(it1, &x, &w, EINA_TRUE, | ||
8381 | switch_items); | ||
8382 | } | ||
8383 | } | ||
8384 | else | ||
8385 | { | ||
8386 | x = 0; | ||
8387 | w = it1->w; | ||
8388 | _evas_textblock_range_calc_x_w(it1, &x, &w, EINA_TRUE, | ||
8389 | switch_items); | ||
8390 | } | ||
8391 | if (w > 0) | ||
8392 | { | ||
8393 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8394 | rects = eina_list_append(rects, tr); | ||
8395 | tr->x = ln->x + it1->x + x; | ||
8396 | tr->y = ln->par->y + ln->y; | ||
8397 | tr->h = ln->h; | ||
8398 | tr->w = w; | ||
8399 | } | ||
8400 | |||
8401 | while (it && (it != it2)) | ||
8402 | { | ||
8403 | max_x = it->x + it->adv; | ||
8404 | it = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(it)->next; | ||
8405 | } | ||
8406 | if (min_x != max_x) | ||
8407 | { | ||
8408 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8409 | rects = eina_list_append(rects, tr); | ||
8410 | tr->x = ln->x + min_x; | ||
8411 | tr->y = ln->par->y + ln->y; | ||
8412 | tr->h = ln->h; | ||
8413 | tr->w = max_x - min_x; | ||
8414 | } | ||
8415 | if (it2->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
8416 | { | ||
8417 | Evas_Coord y, h; | ||
8418 | Evas_Object_Textblock_Text_Item *ti; | ||
8419 | int ret; | ||
8420 | ti = _ITEM_TEXT(it2); | ||
8421 | |||
8422 | ret = cur->ENFN->font_pen_coords_get(cur->ENDT, | ||
8423 | ti->parent.format->font.font, | ||
8424 | &ti->text_props, | ||
8425 | end, | ||
8426 | &x, &y, &w, &h); | ||
8427 | if (!ret) | ||
8428 | { | ||
8429 | /* BUG! skip the last item */ | ||
8430 | x = w = 0; | ||
8431 | } | ||
8432 | else | ||
8433 | { | ||
8434 | _evas_textblock_range_calc_x_w(it2, &x, &w, EINA_FALSE, | ||
8435 | switch_items); | ||
8436 | } | ||
8437 | } | ||
8438 | else | ||
8439 | { | ||
8440 | x = 0; | ||
8441 | w = it2->w; | ||
8442 | _evas_textblock_range_calc_x_w(it2, &x, &w, EINA_FALSE, | ||
8443 | switch_items); | ||
8444 | } | ||
8445 | if (w > 0) | ||
8446 | { | ||
8447 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8448 | rects = eina_list_append(rects, tr); | ||
8449 | tr->x = ln->x + it2->x + x; | ||
8450 | tr->y = ln->par->y + ln->y; | ||
8451 | tr->h = ln->h; | ||
8452 | tr->w = w; | ||
8453 | } | ||
8454 | } | ||
8455 | return rects; | ||
8456 | } | ||
8457 | |||
8458 | EAPI Eina_List * | ||
8459 | evas_textblock_cursor_range_geometry_get(const Evas_Textblock_Cursor *cur1, const Evas_Textblock_Cursor *cur2) | ||
8460 | { | ||
8461 | Evas_Object_Textblock *o; | ||
8462 | Evas_Object_Textblock_Line *ln1, *ln2; | ||
8463 | Evas_Object_Textblock_Item *it1, *it2; | ||
8464 | Eina_List *rects = NULL; | ||
8465 | Evas_Textblock_Rectangle *tr; | ||
8466 | |||
8467 | if (!cur1 || !cur1->node) return NULL; | ||
8468 | if (!cur2 || !cur2->node) return NULL; | ||
8469 | if (cur1->obj != cur2->obj) return NULL; | ||
8470 | o = (Evas_Object_Textblock *)(cur1->obj->object_data); | ||
8471 | if (!o->formatted.valid) _relayout(cur1->obj); | ||
8472 | if (evas_textblock_cursor_compare(cur1, cur2) > 0) | ||
8473 | { | ||
8474 | const Evas_Textblock_Cursor *tc; | ||
8475 | |||
8476 | tc = cur1; | ||
8477 | cur1 = cur2; | ||
8478 | cur2 = tc; | ||
8479 | } | ||
8480 | |||
8481 | ln1 = ln2 = NULL; | ||
8482 | it1 = it2 = NULL; | ||
8483 | _find_layout_item_match(cur1, &ln1, &it1); | ||
8484 | if (!ln1 || !it1) return NULL; | ||
8485 | _find_layout_item_match(cur2, &ln2, &it2); | ||
8486 | if (!ln2 || !it2) return NULL; | ||
8487 | |||
8488 | if (ln1 == ln2) | ||
8489 | { | ||
8490 | rects = _evas_textblock_cursor_range_in_line_geometry_get(ln1, | ||
8491 | cur1, cur2); | ||
8492 | } | ||
8493 | else | ||
8494 | { | ||
8495 | Evas_Object_Textblock_Line *plni, *lni; | ||
8496 | Eina_List *rects2 = NULL; | ||
8497 | /* Handle the first line */ | ||
8498 | rects = _evas_textblock_cursor_range_in_line_geometry_get(ln1, | ||
8499 | cur1, NULL); | ||
8500 | |||
8501 | /* Handle the lines between the first and the last line */ | ||
8502 | lni = (Evas_Object_Textblock_Line *) EINA_INLIST_GET(ln1)->next; | ||
8503 | if (!lni && (ln1->par != ln2->par)) | ||
8504 | { | ||
8505 | lni = ((Evas_Object_Textblock_Paragraph *) | ||
8506 | EINA_INLIST_GET(ln1->par)->next)->lines; | ||
8507 | } | ||
8508 | while (lni && (lni != ln2)) | ||
8509 | { | ||
8510 | tr = calloc(1, sizeof(Evas_Textblock_Rectangle)); | ||
8511 | rects = eina_list_append(rects, tr); | ||
8512 | tr->x = lni->x; | ||
8513 | tr->y = lni->par->y + lni->y; | ||
8514 | tr->h = lni->h; | ||
8515 | tr->w = lni->w; | ||
8516 | plni = lni; | ||
8517 | lni = (Evas_Object_Textblock_Line *) EINA_INLIST_GET(lni)->next; | ||
8518 | if (!lni && (plni->par != ln2->par)) | ||
8519 | { | ||
8520 | lni = ((Evas_Object_Textblock_Paragraph *) | ||
8521 | EINA_INLIST_GET(plni->par)->next)->lines; | ||
8522 | } | ||
8523 | } | ||
8524 | rects2 = _evas_textblock_cursor_range_in_line_geometry_get(ln2, | ||
8525 | NULL, cur2); | ||
8526 | rects = eina_list_merge(rects, rects2); | ||
8527 | } | ||
8528 | return rects; | ||
8529 | } | ||
8530 | |||
8531 | EAPI Eina_Bool | ||
8532 | evas_textblock_cursor_format_item_geometry_get(const Evas_Textblock_Cursor *cur, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
8533 | { | ||
8534 | Evas_Object_Textblock *o; | ||
8535 | Evas_Object_Textblock_Line *ln = NULL; | ||
8536 | Evas_Object_Textblock_Format_Item *fi; | ||
8537 | Evas_Object_Textblock_Item *it = NULL; | ||
8538 | Evas_Coord x, y, w, h; | ||
8539 | |||
8540 | if (!cur || !evas_textblock_cursor_format_is_visible_get(cur)) return EINA_FALSE; | ||
8541 | o = (Evas_Object_Textblock *)(cur->obj->object_data); | ||
8542 | if (!o->formatted.valid) _relayout(cur->obj); | ||
8543 | if (!evas_textblock_cursor_format_is_visible_get(cur)) return EINA_FALSE; | ||
8544 | _find_layout_item_line_match(cur->obj, cur->node, cur->pos, &ln, &it); | ||
8545 | fi = _ITEM_FORMAT(it); | ||
8546 | if ((!ln) || (!fi)) return EINA_FALSE; | ||
8547 | x = ln->x + fi->parent.x; | ||
8548 | y = ln->par->y + ln->y + ln->baseline + fi->y; | ||
8549 | w = fi->parent.w; | ||
8550 | h = fi->parent.h; | ||
8551 | if (cx) *cx = x; | ||
8552 | if (cy) *cy = y; | ||
8553 | if (cw) *cw = w; | ||
8554 | if (ch) *ch = h; | ||
8555 | return EINA_TRUE; | ||
8556 | } | ||
8557 | |||
8558 | EAPI Eina_Bool | ||
8559 | evas_textblock_cursor_eol_get(const Evas_Textblock_Cursor *cur) | ||
8560 | { | ||
8561 | Eina_Bool ret = EINA_FALSE; | ||
8562 | Evas_Textblock_Cursor cur2; | ||
8563 | if (!cur) return EINA_FALSE; | ||
8564 | |||
8565 | cur2.obj = cur->obj; | ||
8566 | evas_textblock_cursor_copy(cur, &cur2); | ||
8567 | evas_textblock_cursor_line_char_last(&cur2); | ||
8568 | if (cur2.pos == cur->pos) | ||
8569 | { | ||
8570 | ret = EINA_TRUE; | ||
8571 | } | ||
8572 | return ret; | ||
8573 | } | ||
8574 | |||
8575 | /* general controls */ | ||
8576 | EAPI Eina_Bool | ||
8577 | evas_object_textblock_line_number_geometry_get(const Evas_Object *obj, int line, Evas_Coord *cx, Evas_Coord *cy, Evas_Coord *cw, Evas_Coord *ch) | ||
8578 | { | ||
8579 | Evas_Object_Textblock_Line *ln; | ||
8580 | |||
8581 | TB_HEAD_RETURN(0); | ||
8582 | ln = _find_layout_line_num(obj, line); | ||
8583 | if (!ln) return EINA_FALSE; | ||
8584 | if (cx) *cx = ln->x; | ||
8585 | if (cy) *cy = ln->par->y + ln->y; | ||
8586 | if (cw) *cw = ln->w; | ||
8587 | if (ch) *ch = ln->h; | ||
8588 | return EINA_TRUE; | ||
8589 | } | ||
8590 | |||
8591 | EAPI void | ||
8592 | evas_object_textblock_clear(Evas_Object *obj) | ||
8593 | { | ||
8594 | Eina_List *l; | ||
8595 | Evas_Textblock_Cursor *cur; | ||
8596 | |||
8597 | TB_HEAD(); | ||
8598 | if (o->paragraphs) | ||
8599 | { | ||
8600 | _paragraphs_free(obj, o->paragraphs); | ||
8601 | o->paragraphs = NULL; | ||
8602 | } | ||
8603 | |||
8604 | _nodes_clear(obj); | ||
8605 | o->cursor->node = NULL; | ||
8606 | o->cursor->pos = 0; | ||
8607 | EINA_LIST_FOREACH(o->cursors, l, cur) | ||
8608 | { | ||
8609 | cur->node = NULL; | ||
8610 | cur->pos = 0; | ||
8611 | |||
8612 | } | ||
8613 | _evas_textblock_changed(o, obj); | ||
8614 | } | ||
8615 | |||
8616 | EAPI void | ||
8617 | evas_object_textblock_size_formatted_get(const Evas_Object *obj, Evas_Coord *w, Evas_Coord *h) | ||
8618 | { | ||
8619 | TB_HEAD(); | ||
8620 | if (!o->formatted.valid) _relayout(obj); | ||
8621 | if (w) *w = o->formatted.w; | ||
8622 | if (h) *h = o->formatted.h; | ||
8623 | } | ||
8624 | |||
8625 | static void | ||
8626 | _size_native_calc_line_finalize(const Evas_Object *obj, Eina_List *items, | ||
8627 | Evas_Coord *ascent, Evas_Coord *descent, Evas_Coord *w) | ||
8628 | { | ||
8629 | Evas_Object_Textblock_Item *it; | ||
8630 | Eina_List *i; | ||
8631 | |||
8632 | it = eina_list_data_get(items); | ||
8633 | /* If there are no text items yet, calc ascent/descent | ||
8634 | * according to the current format. */ | ||
8635 | if (it && (*ascent + *descent == 0)) | ||
8636 | _layout_format_ascent_descent_adjust(obj, ascent, descent, it->format); | ||
8637 | |||
8638 | *w = 0; | ||
8639 | /* Adjust all the item sizes according to the final line size, | ||
8640 | * and update the x positions of all the items of the line. */ | ||
8641 | EINA_LIST_FOREACH(items, i, it) | ||
8642 | { | ||
8643 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
8644 | { | ||
8645 | Evas_Coord fw, fh, fy; | ||
8646 | |||
8647 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
8648 | if (!fi->formatme) goto loop_advance; | ||
8649 | _layout_calculate_format_item_size(obj, fi, ascent, | ||
8650 | descent, &fy, &fw, &fh); | ||
8651 | } | ||
8652 | |||
8653 | loop_advance: | ||
8654 | *w += it->adv; | ||
8655 | } | ||
8656 | } | ||
8657 | |||
8658 | /* FIXME: doc */ | ||
8659 | static void | ||
8660 | _size_native_calc_paragraph_size(const Evas_Object *obj, | ||
8661 | const Evas_Object_Textblock *o, | ||
8662 | const Evas_Object_Textblock_Paragraph *par, | ||
8663 | Evas_Coord *_w, Evas_Coord *_h) | ||
8664 | { | ||
8665 | Eina_List *i; | ||
8666 | Evas_Object_Textblock_Item *it; | ||
8667 | Eina_List *line_items = NULL; | ||
8668 | Evas_Coord w = 0, y = 0, wmax = 0, h = 0, ascent = 0, descent = 0; | ||
8669 | |||
8670 | EINA_LIST_FOREACH(par->logical_items, i, it) | ||
8671 | { | ||
8672 | line_items = eina_list_append(line_items, it); | ||
8673 | if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT) | ||
8674 | { | ||
8675 | Evas_Object_Textblock_Format_Item *fi = _ITEM_FORMAT(it); | ||
8676 | if (fi->item && (_IS_LINE_SEPARATOR(fi->item) || | ||
8677 | _IS_PARAGRAPH_SEPARATOR(o, fi->item))) | ||
8678 | { | ||
8679 | _size_native_calc_line_finalize(obj, line_items, &ascent, | ||
8680 | &descent, &w); | ||
8681 | |||
8682 | if (ascent + descent > h) | ||
8683 | h = ascent + descent; | ||
8684 | |||
8685 | y += h; | ||
8686 | if (w > wmax) | ||
8687 | wmax = w; | ||
8688 | h = 0; | ||
8689 | ascent = descent = 0; | ||
8690 | line_items = eina_list_free(line_items); | ||
8691 | } | ||
8692 | else | ||
8693 | { | ||
8694 | Evas_Coord fw, fh, fy; | ||
8695 | /* If there are no text items yet, calc ascent/descent | ||
8696 | * according to the current format. */ | ||
8697 | if (it && (ascent + descent == 0)) | ||
8698 | _layout_format_ascent_descent_adjust(obj, &ascent, | ||
8699 | &descent, it->format); | ||
8700 | |||
8701 | _layout_calculate_format_item_size(obj, fi, &ascent, | ||
8702 | &descent, &fy, &fw, &fh); | ||
8703 | } | ||
8704 | } | ||
8705 | else | ||
8706 | { | ||
8707 | _layout_format_ascent_descent_adjust(obj, &ascent, | ||
8708 | &descent, it->format); | ||
8709 | } | ||
8710 | } | ||
8711 | |||
8712 | _size_native_calc_line_finalize(obj, line_items, &ascent, &descent, &w); | ||
8713 | |||
8714 | line_items = eina_list_free(line_items); | ||
8715 | |||
8716 | /* Do the last addition */ | ||
8717 | if (ascent + descent > h) | ||
8718 | h = ascent + descent; | ||
8719 | |||
8720 | if (w > wmax) | ||
8721 | wmax = w; | ||
8722 | |||
8723 | *_h = y + h; | ||
8724 | *_w = wmax; | ||
8725 | } | ||
8726 | |||
8727 | EAPI void | ||
8728 | evas_object_textblock_size_native_get(const Evas_Object *obj, Evas_Coord *w, Evas_Coord *h) | ||
8729 | { | ||
8730 | TB_HEAD(); | ||
8731 | if (!o->native.valid) | ||
8732 | { | ||
8733 | Evas_Coord wmax = 0, hmax = 0; | ||
8734 | Evas_Object_Textblock_Paragraph *par; | ||
8735 | /* We just want the layout objects to update, should probably | ||
8736 | * split that. */ | ||
8737 | if (!o->formatted.valid) _relayout(obj); | ||
8738 | EINA_INLIST_FOREACH(o->paragraphs, par) | ||
8739 | { | ||
8740 | Evas_Coord tw, th; | ||
8741 | _size_native_calc_paragraph_size(obj, o, par, &tw, &th); | ||
8742 | if (tw > wmax) | ||
8743 | wmax = tw; | ||
8744 | hmax += th; | ||
8745 | } | ||
8746 | |||
8747 | o->native.w = wmax; | ||
8748 | o->native.h = hmax; | ||
8749 | |||
8750 | o->native.valid = 1; | ||
8751 | o->content_changed = 0; | ||
8752 | o->format_changed = EINA_FALSE; | ||
8753 | } | ||
8754 | if (w) *w = o->native.w; | ||
8755 | if (h) *h = o->native.h; | ||
8756 | } | ||
8757 | |||
8758 | EAPI void | ||
8759 | evas_object_textblock_style_insets_get(const Evas_Object *obj, Evas_Coord *l, Evas_Coord *r, Evas_Coord *t, Evas_Coord *b) | ||
8760 | { | ||
8761 | TB_HEAD(); | ||
8762 | if (!o->formatted.valid) _relayout(obj); | ||
8763 | if (l) *l = o->style_pad.l; | ||
8764 | if (r) *r = o->style_pad.r; | ||
8765 | if (t) *t = o->style_pad.t; | ||
8766 | if (b) *b = o->style_pad.b; | ||
8767 | } | ||
8768 | |||
8769 | /** @internal | ||
8770 | * FIXME: DELETE ME! DELETE ME! | ||
8771 | * This is an ugly workaround to get around the fact that | ||
8772 | * evas_object_textblock_coords_recalc isn't really called when it's supposed | ||
8773 | * to. When that bug is fixed please remove this. */ | ||
8774 | static void | ||
8775 | _workaround_object_coords_recalc(void *data __UNUSED__, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__) | ||
8776 | { | ||
8777 | evas_object_textblock_coords_recalc(obj); | ||
8778 | } | ||
8779 | |||
8780 | /* all nice and private */ | ||
8781 | static void | ||
8782 | evas_object_textblock_init(Evas_Object *obj) | ||
8783 | { | ||
8784 | Evas_Object_Textblock *o; | ||
8785 | #ifdef HAVE_LINEBREAK | ||
8786 | static Eina_Bool linebreak_init = EINA_FALSE; | ||
8787 | if (!linebreak_init) | ||
8788 | { | ||
8789 | linebreak_init = EINA_TRUE; | ||
8790 | init_linebreak(); | ||
8791 | } | ||
8792 | #endif | ||
8793 | |||
8794 | /* alloc image ob, setup methods and default values */ | ||
8795 | obj->object_data = evas_object_textblock_new(); | ||
8796 | /* set up default settings for this kind of object */ | ||
8797 | obj->cur.color.r = 255; | ||
8798 | obj->cur.color.g = 255; | ||
8799 | obj->cur.color.b = 255; | ||
8800 | obj->cur.color.a = 255; | ||
8801 | obj->cur.geometry.x = 0.0; | ||
8802 | obj->cur.geometry.y = 0.0; | ||
8803 | obj->cur.geometry.w = 0.0; | ||
8804 | obj->cur.geometry.h = 0.0; | ||
8805 | obj->cur.layer = 0; | ||
8806 | /* set up object-specific settings */ | ||
8807 | obj->prev = obj->cur; | ||
8808 | /* set up methods (compulsory) */ | ||
8809 | obj->func = &object_func; | ||
8810 | obj->type = o_type; | ||
8811 | |||
8812 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
8813 | o->cursor->obj = obj; | ||
8814 | o->legacy_newline = EINA_TRUE; | ||
8815 | evas_object_event_callback_priority_add(obj, EVAS_CALLBACK_RESIZE, -1000, | ||
8816 | _workaround_object_coords_recalc, NULL); | ||
8817 | } | ||
8818 | |||
8819 | static void * | ||
8820 | evas_object_textblock_new(void) | ||
8821 | { | ||
8822 | Evas_Object_Textblock *o; | ||
8823 | |||
8824 | /* alloc obj private data */ | ||
8825 | EVAS_MEMPOOL_INIT(_mp_obj, "evas_object_textblock", Evas_Object_Textblock, 64, NULL); | ||
8826 | o = EVAS_MEMPOOL_ALLOC(_mp_obj, Evas_Object_Textblock); | ||
8827 | if (!o) return NULL; | ||
8828 | EVAS_MEMPOOL_PREP(_mp_obj, o, Evas_Object_Textblock); | ||
8829 | o->magic = MAGIC_OBJ_TEXTBLOCK; | ||
8830 | o->cursor = calloc(1, sizeof(Evas_Textblock_Cursor)); | ||
8831 | _format_command_init(); | ||
8832 | return o; | ||
8833 | } | ||
8834 | |||
8835 | static void | ||
8836 | evas_object_textblock_free(Evas_Object *obj) | ||
8837 | { | ||
8838 | Evas_Object_Textblock *o; | ||
8839 | |||
8840 | evas_object_textblock_clear(obj); | ||
8841 | evas_object_textblock_style_set(obj, NULL); | ||
8842 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
8843 | free(o->cursor); | ||
8844 | while (o->cursors) | ||
8845 | { | ||
8846 | Evas_Textblock_Cursor *cur; | ||
8847 | |||
8848 | cur = (Evas_Textblock_Cursor *)o->cursors->data; | ||
8849 | o->cursors = eina_list_remove_list(o->cursors, o->cursors); | ||
8850 | free(cur); | ||
8851 | } | ||
8852 | if (o->repch) eina_stringshare_del(o->repch); | ||
8853 | if (o->ellip_ti) _item_free(obj, NULL, _ITEM(o->ellip_ti)); | ||
8854 | o->magic = 0; | ||
8855 | EVAS_MEMPOOL_FREE(_mp_obj, o); | ||
8856 | _format_command_shutdown(); | ||
8857 | } | ||
8858 | |||
8859 | |||
8860 | static void | ||
8861 | evas_object_textblock_render(Evas_Object *obj, void *output, void *context, void *surface, int x, int y) | ||
8862 | { | ||
8863 | Evas_Object_Textblock_Paragraph *par, *start = NULL; | ||
8864 | Evas_Object_Textblock_Line *ln; | ||
8865 | Evas_Object_Textblock *o; | ||
8866 | int i, j; | ||
8867 | int cx, cy, cw, ch, clip; | ||
8868 | const char vals[5][5] = | ||
8869 | { | ||
8870 | {0, 1, 2, 1, 0}, | ||
8871 | {1, 3, 4, 3, 1}, | ||
8872 | {2, 4, 5, 4, 2}, | ||
8873 | {1, 3, 4, 3, 1}, | ||
8874 | {0, 1, 2, 1, 0} | ||
8875 | }; | ||
8876 | |||
8877 | /* render object to surface with context, and offxet by x,y */ | ||
8878 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
8879 | obj->layer->evas->engine.func->context_multiplier_unset(output, | ||
8880 | context); | ||
8881 | /* FIXME: This clipping is just until we fix inset handling correctly. */ | ||
8882 | ENFN->context_clip_clip(output, context, | ||
8883 | obj->cur.geometry.x + x, | ||
8884 | obj->cur.geometry.y + y, | ||
8885 | obj->cur.geometry.w, | ||
8886 | obj->cur.geometry.h); | ||
8887 | clip = ENFN->context_clip_get(output, context, &cx, &cy, &cw, &ch); | ||
8888 | /* If there are no paragraphs and thus there are no lines, | ||
8889 | * there's nothing left to do. */ | ||
8890 | if (!o->paragraphs) return; | ||
8891 | |||
8892 | #define ITEM_WALK() \ | ||
8893 | EINA_INLIST_FOREACH(start, par) \ | ||
8894 | { \ | ||
8895 | if (!par->visible) continue; \ | ||
8896 | if (clip) \ | ||
8897 | { \ | ||
8898 | if ((obj->cur.geometry.y + y + par->y + par->h) < (cy - 20)) \ | ||
8899 | continue; \ | ||
8900 | if ((obj->cur.geometry.y + y + par->y) > (cy + ch + 20)) \ | ||
8901 | break; \ | ||
8902 | } \ | ||
8903 | _layout_paragraph_render(o, par); \ | ||
8904 | EINA_INLIST_FOREACH(par->lines, ln) \ | ||
8905 | { \ | ||
8906 | Evas_Object_Textblock_Item *itr; \ | ||
8907 | \ | ||
8908 | if (clip) \ | ||
8909 | { \ | ||
8910 | if ((obj->cur.geometry.y + y + par->y + ln->y + ln->h) < (cy - 20)) \ | ||
8911 | continue; \ | ||
8912 | if ((obj->cur.geometry.y + y + par->y + ln->y) > (cy + ch + 20)) \ | ||
8913 | break; \ | ||
8914 | } \ | ||
8915 | EINA_INLIST_FOREACH(ln->items, itr) \ | ||
8916 | { \ | ||
8917 | Evas_Coord yoff; \ | ||
8918 | yoff = ln->baseline; \ | ||
8919 | if (itr->format->valign != -1.0) \ | ||
8920 | { \ | ||
8921 | yoff += itr->format->valign * (ln->h - itr->h); \ | ||
8922 | } \ | ||
8923 | if (clip) \ | ||
8924 | { \ | ||
8925 | if ((obj->cur.geometry.x + x + ln->x + itr->x + itr->w) < (cx - 20)) \ | ||
8926 | continue; \ | ||
8927 | if ((obj->cur.geometry.x + x + ln->x + itr->x) > (cx + cw + 20)) \ | ||
8928 | break; \ | ||
8929 | } \ | ||
8930 | if ((ln->x + itr->x + itr->w) <= 0) continue; \ | ||
8931 | if (ln->x + itr->x > obj->cur.geometry.w) break; \ | ||
8932 | do | ||
8933 | |||
8934 | #define ITEM_WALK_END() \ | ||
8935 | while (0); \ | ||
8936 | } \ | ||
8937 | } \ | ||
8938 | } \ | ||
8939 | do {} while(0) | ||
8940 | #define COLOR_SET(col) \ | ||
8941 | ENFN->context_color_set(output, context, \ | ||
8942 | (obj->cur.cache.clip.r * ti->parent.format->color.col.r) / 255, \ | ||
8943 | (obj->cur.cache.clip.g * ti->parent.format->color.col.g) / 255, \ | ||
8944 | (obj->cur.cache.clip.b * ti->parent.format->color.col.b) / 255, \ | ||
8945 | (obj->cur.cache.clip.a * ti->parent.format->color.col.a) / 255); | ||
8946 | #define COLOR_SET_AMUL(col, amul) \ | ||
8947 | ENFN->context_color_set(output, context, \ | ||
8948 | (obj->cur.cache.clip.r * ti->parent.format->color.col.r * (amul)) / 65025, \ | ||
8949 | (obj->cur.cache.clip.g * ti->parent.format->color.col.g * (amul)) / 65025, \ | ||
8950 | (obj->cur.cache.clip.b * ti->parent.format->color.col.b * (amul)) / 65025, \ | ||
8951 | (obj->cur.cache.clip.a * ti->parent.format->color.col.a * (amul)) / 65025); | ||
8952 | #define DRAW_TEXT(ox, oy) \ | ||
8953 | if (ti->parent.format->font.font) ENFN->font_draw(output, context, surface, ti->parent.format->font.font, \ | ||
8954 | obj->cur.geometry.x + ln->x + ti->parent.x + x + (ox), \ | ||
8955 | obj->cur.geometry.y + ln->par->y + ln->y + yoff + y + (oy), \ | ||
8956 | ti->parent.w, ti->parent.h, ti->parent.w, ti->parent.h, \ | ||
8957 | &ti->text_props); | ||
8958 | |||
8959 | /* backing */ | ||
8960 | #define DRAW_RECT(ox, oy, ow, oh, or, og, ob, oa) \ | ||
8961 | do \ | ||
8962 | { \ | ||
8963 | ENFN->context_color_set(output, \ | ||
8964 | context, \ | ||
8965 | (obj->cur.cache.clip.r * or) / 255, \ | ||
8966 | (obj->cur.cache.clip.g * og) / 255, \ | ||
8967 | (obj->cur.cache.clip.b * ob) / 255, \ | ||
8968 | (obj->cur.cache.clip.a * oa) / 255); \ | ||
8969 | ENFN->rectangle_draw(output, \ | ||
8970 | context, \ | ||
8971 | surface, \ | ||
8972 | obj->cur.geometry.x + ln->x + x + (ox), \ | ||
8973 | obj->cur.geometry.y + ln->par->y + ln->y + y + (oy), \ | ||
8974 | (ow), \ | ||
8975 | (oh)); \ | ||
8976 | } \ | ||
8977 | while (0) | ||
8978 | |||
8979 | #define DRAW_FORMAT_DASHED(oname, oy, oh, dw, dp) \ | ||
8980 | do \ | ||
8981 | { \ | ||
8982 | if (itr->format->oname) \ | ||
8983 | { \ | ||
8984 | unsigned char _or, _og, _ob, _oa; \ | ||
8985 | int _ind, _dx = 0, _dn, _dr; \ | ||
8986 | _or = itr->format->color.oname.r; \ | ||
8987 | _og = itr->format->color.oname.g; \ | ||
8988 | _ob = itr->format->color.oname.b; \ | ||
8989 | _oa = itr->format->color.oname.a; \ | ||
8990 | if (!EINA_INLIST_GET(itr)->next) \ | ||
8991 | { \ | ||
8992 | _dn = itr->w / (dw + dp); \ | ||
8993 | _dr = itr->w % (dw + dp); \ | ||
8994 | } \ | ||
8995 | else \ | ||
8996 | { \ | ||
8997 | _dn = itr->adv / (dw + dp); \ | ||
8998 | _dr = itr->adv % (dw + dp); \ | ||
8999 | } \ | ||
9000 | if (_dr > dw) _dr = dw; \ | ||
9001 | for (_ind = 0 ; _ind < _dn ; _ind++) \ | ||
9002 | { \ | ||
9003 | DRAW_RECT(itr->x + _dx, oy, dw, oh, _or, _og, _ob, _oa); \ | ||
9004 | _dx += dw + dp; \ | ||
9005 | } \ | ||
9006 | DRAW_RECT(itr->x + _dx, oy, _dr, oh, _or, _og, _ob, _oa); \ | ||
9007 | } \ | ||
9008 | } \ | ||
9009 | while (0) | ||
9010 | |||
9011 | #define DRAW_FORMAT(oname, oy, oh) \ | ||
9012 | do \ | ||
9013 | { \ | ||
9014 | if (itr->format->oname) \ | ||
9015 | { \ | ||
9016 | unsigned char _or, _og, _ob, _oa; \ | ||
9017 | _or = itr->format->color.oname.r; \ | ||
9018 | _og = itr->format->color.oname.g; \ | ||
9019 | _ob = itr->format->color.oname.b; \ | ||
9020 | _oa = itr->format->color.oname.a; \ | ||
9021 | if (!EINA_INLIST_GET(itr)->next) \ | ||
9022 | { \ | ||
9023 | DRAW_RECT(itr->x, oy, itr->w, oh, _or, _og, _ob, _oa); \ | ||
9024 | } \ | ||
9025 | else \ | ||
9026 | { \ | ||
9027 | DRAW_RECT(itr->x, oy, itr->adv, oh, _or, _og, _ob, _oa); \ | ||
9028 | } \ | ||
9029 | } \ | ||
9030 | } \ | ||
9031 | while (0) | ||
9032 | |||
9033 | { | ||
9034 | Evas_Coord look_for_y = 0 - (obj->cur.geometry.y + y); | ||
9035 | if (clip) | ||
9036 | { | ||
9037 | Evas_Coord tmp_lfy = cy - (obj->cur.geometry.y + y); | ||
9038 | if (tmp_lfy > look_for_y) | ||
9039 | look_for_y = tmp_lfy; | ||
9040 | } | ||
9041 | |||
9042 | if (look_for_y >= 0) | ||
9043 | start = _layout_find_paragraph_by_y(o, look_for_y); | ||
9044 | |||
9045 | if (!start) | ||
9046 | start = o->paragraphs; | ||
9047 | } | ||
9048 | |||
9049 | ITEM_WALK() | ||
9050 | { | ||
9051 | DRAW_FORMAT(backing, 0, ln->h); | ||
9052 | } | ||
9053 | ITEM_WALK_END(); | ||
9054 | |||
9055 | /* There are size adjustments that depend on the styles drawn here back | ||
9056 | * in "_text_item_update_sizes" should not modify one without the other. */ | ||
9057 | |||
9058 | /* prepare everything for text draw */ | ||
9059 | |||
9060 | /* shadows */ | ||
9061 | ITEM_WALK() | ||
9062 | { | ||
9063 | int shad_dst, shad_sz, dx, dy, haveshad; | ||
9064 | Evas_Object_Textblock_Text_Item *ti; | ||
9065 | ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL; | ||
9066 | if (!ti) continue; | ||
9067 | |||
9068 | shad_dst = shad_sz = dx = dy = haveshad = 0; | ||
9069 | switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_BASIC) | ||
9070 | { | ||
9071 | case EVAS_TEXT_STYLE_SHADOW: | ||
9072 | case EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW: | ||
9073 | shad_dst = 1; | ||
9074 | haveshad = 1; | ||
9075 | break; | ||
9076 | case EVAS_TEXT_STYLE_OUTLINE_SHADOW: | ||
9077 | case EVAS_TEXT_STYLE_FAR_SHADOW: | ||
9078 | shad_dst = 2; | ||
9079 | haveshad = 1; | ||
9080 | break; | ||
9081 | case EVAS_TEXT_STYLE_FAR_SOFT_SHADOW: | ||
9082 | shad_dst = 2; | ||
9083 | shad_sz = 2; | ||
9084 | haveshad = 1; | ||
9085 | break; | ||
9086 | case EVAS_TEXT_STYLE_SOFT_SHADOW: | ||
9087 | shad_dst = 1; | ||
9088 | shad_sz = 2; | ||
9089 | haveshad = 1; | ||
9090 | break; | ||
9091 | default: | ||
9092 | break; | ||
9093 | } | ||
9094 | if (haveshad) | ||
9095 | { | ||
9096 | if (shad_dst > 0) | ||
9097 | { | ||
9098 | switch (ti->parent.format->style & EVAS_TEXT_STYLE_MASK_SHADOW_DIRECTION) | ||
9099 | { | ||
9100 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_RIGHT: | ||
9101 | dx = 1; | ||
9102 | dy = 1; | ||
9103 | break; | ||
9104 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM: | ||
9105 | dx = 0; | ||
9106 | dy = 1; | ||
9107 | break; | ||
9108 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_BOTTOM_LEFT: | ||
9109 | dx = -1; | ||
9110 | dy = 1; | ||
9111 | break; | ||
9112 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_LEFT: | ||
9113 | dx = -1; | ||
9114 | dy = 0; | ||
9115 | break; | ||
9116 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_LEFT: | ||
9117 | dx = -1; | ||
9118 | dy = -1; | ||
9119 | break; | ||
9120 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP: | ||
9121 | dx = 0; | ||
9122 | dy = -1; | ||
9123 | break; | ||
9124 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_TOP_RIGHT: | ||
9125 | dx = 1; | ||
9126 | dy = -1; | ||
9127 | break; | ||
9128 | case EVAS_TEXT_STYLE_SHADOW_DIRECTION_RIGHT: | ||
9129 | dx = 1; | ||
9130 | dy = 0; | ||
9131 | default: | ||
9132 | break; | ||
9133 | } | ||
9134 | dx *= shad_dst; | ||
9135 | dy *= shad_dst; | ||
9136 | } | ||
9137 | switch (shad_sz) | ||
9138 | { | ||
9139 | case 0: | ||
9140 | COLOR_SET(shadow); | ||
9141 | DRAW_TEXT(dx, dy); | ||
9142 | break; | ||
9143 | case 2: | ||
9144 | for (j = 0; j < 5; j++) | ||
9145 | { | ||
9146 | for (i = 0; i < 5; i++) | ||
9147 | { | ||
9148 | if (vals[i][j] != 0) | ||
9149 | { | ||
9150 | COLOR_SET_AMUL(shadow, vals[i][j] * 50); | ||
9151 | DRAW_TEXT(i - 2 + dx, j - 2 + dy); | ||
9152 | } | ||
9153 | } | ||
9154 | } | ||
9155 | break; | ||
9156 | default: | ||
9157 | break; | ||
9158 | } | ||
9159 | } | ||
9160 | } | ||
9161 | ITEM_WALK_END(); | ||
9162 | |||
9163 | /* glows */ | ||
9164 | ITEM_WALK() | ||
9165 | { | ||
9166 | Evas_Object_Textblock_Text_Item *ti; | ||
9167 | ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL; | ||
9168 | if (!ti) continue; | ||
9169 | |||
9170 | if (ti->parent.format->style == EVAS_TEXT_STYLE_GLOW) | ||
9171 | { | ||
9172 | for (j = 0; j < 5; j++) | ||
9173 | { | ||
9174 | for (i = 0; i < 5; i++) | ||
9175 | { | ||
9176 | if (vals[i][j] != 0) | ||
9177 | { | ||
9178 | COLOR_SET_AMUL(glow, vals[i][j] * 50); | ||
9179 | DRAW_TEXT(i - 2, j - 2); | ||
9180 | } | ||
9181 | } | ||
9182 | } | ||
9183 | COLOR_SET(glow2); | ||
9184 | DRAW_TEXT(-1, 0); | ||
9185 | DRAW_TEXT(1, 0); | ||
9186 | DRAW_TEXT(0, -1); | ||
9187 | DRAW_TEXT(0, 1); | ||
9188 | } | ||
9189 | } | ||
9190 | ITEM_WALK_END(); | ||
9191 | |||
9192 | /* outlines */ | ||
9193 | ITEM_WALK() | ||
9194 | { | ||
9195 | Evas_Object_Textblock_Text_Item *ti; | ||
9196 | ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL; | ||
9197 | if (!ti) continue; | ||
9198 | |||
9199 | if ((ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE) || | ||
9200 | (ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE_SHADOW) || | ||
9201 | (ti->parent.format->style == EVAS_TEXT_STYLE_OUTLINE_SOFT_SHADOW)) | ||
9202 | { | ||
9203 | COLOR_SET(outline); | ||
9204 | DRAW_TEXT(-1, 0); | ||
9205 | DRAW_TEXT(1, 0); | ||
9206 | DRAW_TEXT(0, -1); | ||
9207 | DRAW_TEXT(0, 1); | ||
9208 | } | ||
9209 | else if (ti->parent.format->style == EVAS_TEXT_STYLE_SOFT_OUTLINE) | ||
9210 | { | ||
9211 | for (j = 0; j < 5; j++) | ||
9212 | { | ||
9213 | for (i = 0; i < 5; i++) | ||
9214 | { | ||
9215 | if (((i != 2) || (j != 2)) && (vals[i][j] != 0)) | ||
9216 | { | ||
9217 | COLOR_SET_AMUL(outline, vals[i][j] * 50); | ||
9218 | DRAW_TEXT(i - 2, j - 2); | ||
9219 | } | ||
9220 | } | ||
9221 | } | ||
9222 | } | ||
9223 | } | ||
9224 | ITEM_WALK_END(); | ||
9225 | |||
9226 | /* normal text and lines */ | ||
9227 | ITEM_WALK() | ||
9228 | { | ||
9229 | Evas_Object_Textblock_Text_Item *ti; | ||
9230 | ti = (itr->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? _ITEM_TEXT(itr) : NULL; | ||
9231 | /* NORMAL TEXT */ | ||
9232 | if (ti) | ||
9233 | { | ||
9234 | COLOR_SET(normal); | ||
9235 | DRAW_TEXT(0, 0); | ||
9236 | } | ||
9237 | |||
9238 | /* STRIKETHROUGH */ | ||
9239 | DRAW_FORMAT(strikethrough, (ln->h / 2), 1); | ||
9240 | |||
9241 | /* UNDERLINE */ | ||
9242 | DRAW_FORMAT(underline, ln->baseline + 1, 1); | ||
9243 | |||
9244 | /* UNDERLINE DASHED */ | ||
9245 | DRAW_FORMAT_DASHED(underline_dash, ln->baseline + 1, 1, | ||
9246 | ti->parent.format->underline_dash_width, | ||
9247 | ti->parent.format->underline_dash_gap); | ||
9248 | |||
9249 | /* UNDERLINE2 */ | ||
9250 | DRAW_FORMAT(underline2, ln->baseline + 3, 1); | ||
9251 | } | ||
9252 | ITEM_WALK_END(); | ||
9253 | } | ||
9254 | |||
9255 | static void | ||
9256 | evas_object_textblock_render_pre(Evas_Object *obj) | ||
9257 | { | ||
9258 | Evas_Object_Textblock *o; | ||
9259 | int is_v, was_v; | ||
9260 | |||
9261 | /* dont pre-render the obj twice! */ | ||
9262 | if (obj->pre_render_done) return; | ||
9263 | obj->pre_render_done = 1; | ||
9264 | /* pre-render phase. this does anything an object needs to do just before */ | ||
9265 | /* rendering. this could mean loading the image data, retrieving it from */ | ||
9266 | /* elsewhere, decoding video etc. */ | ||
9267 | /* then when this is done the object needs to figure if it changed and */ | ||
9268 | /* if so what and where and add the appropriate redraw textblocks */ | ||
9269 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9270 | if ((o->changed) || (o->content_changed) || (o->format_changed) || | ||
9271 | ((obj->cur.geometry.w != o->last_w) || | ||
9272 | (((o->valign != 0.0) || (o->have_ellipsis)) && | ||
9273 | (obj->cur.geometry.h != o->last_h)))) | ||
9274 | { | ||
9275 | _relayout(obj); | ||
9276 | o->redraw = 0; | ||
9277 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9278 | is_v = evas_object_is_visible(obj); | ||
9279 | was_v = evas_object_was_visible(obj); | ||
9280 | goto done; | ||
9281 | } | ||
9282 | if (o->redraw) | ||
9283 | { | ||
9284 | o->redraw = 0; | ||
9285 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9286 | is_v = evas_object_is_visible(obj); | ||
9287 | was_v = evas_object_was_visible(obj); | ||
9288 | goto done; | ||
9289 | } | ||
9290 | /* if someone is clipping this obj - go calculate the clipper */ | ||
9291 | if (obj->cur.clipper) | ||
9292 | { | ||
9293 | if (obj->cur.cache.clip.dirty) | ||
9294 | evas_object_clip_recalc(obj->cur.clipper); | ||
9295 | obj->cur.clipper->func->render_pre(obj->cur.clipper); | ||
9296 | } | ||
9297 | /* now figure what changed and add draw rects */ | ||
9298 | /* if it just became visible or invisible */ | ||
9299 | is_v = evas_object_is_visible(obj); | ||
9300 | was_v = evas_object_was_visible(obj); | ||
9301 | if (is_v != was_v) | ||
9302 | { | ||
9303 | evas_object_render_pre_visible_change(&obj->layer->evas->clip_changes, obj, is_v, was_v); | ||
9304 | goto done; | ||
9305 | } | ||
9306 | if ((obj->cur.map != obj->prev.map) || | ||
9307 | (obj->cur.usemap != obj->prev.usemap)) | ||
9308 | { | ||
9309 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9310 | goto done; | ||
9311 | } | ||
9312 | /* it's not visible - we accounted for it appearing or not so just abort */ | ||
9313 | if (!is_v) goto done; | ||
9314 | /* clipper changed this is in addition to anything else for obj */ | ||
9315 | evas_object_render_pre_clipper_change(&obj->layer->evas->clip_changes, obj); | ||
9316 | /* if we restacked (layer or just within a layer) and don't clip anyone */ | ||
9317 | if (obj->restack) | ||
9318 | { | ||
9319 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9320 | goto done; | ||
9321 | } | ||
9322 | /* if it changed color */ | ||
9323 | if ((obj->cur.color.r != obj->prev.color.r) || | ||
9324 | (obj->cur.color.g != obj->prev.color.g) || | ||
9325 | (obj->cur.color.b != obj->prev.color.b) || | ||
9326 | (obj->cur.color.a != obj->prev.color.a)) | ||
9327 | { | ||
9328 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9329 | goto done; | ||
9330 | } | ||
9331 | /* if it changed geometry - and obviously not visibility or color */ | ||
9332 | /* calculate differences since we have a constant color fill */ | ||
9333 | /* we really only need to update the differences */ | ||
9334 | if ((obj->cur.geometry.x != obj->prev.geometry.x) || | ||
9335 | (obj->cur.geometry.y != obj->prev.geometry.y) || | ||
9336 | (obj->cur.geometry.w != obj->prev.geometry.w) || | ||
9337 | (obj->cur.geometry.h != obj->prev.geometry.h)) | ||
9338 | { | ||
9339 | evas_object_render_pre_prev_cur_add(&obj->layer->evas->clip_changes, obj); | ||
9340 | goto done; | ||
9341 | } | ||
9342 | done: | ||
9343 | evas_object_render_pre_effect_updates(&obj->layer->evas->clip_changes, obj, is_v, was_v); | ||
9344 | } | ||
9345 | |||
9346 | static void | ||
9347 | evas_object_textblock_render_post(Evas_Object *obj) | ||
9348 | { | ||
9349 | /* Evas_Object_Textblock *o; */ | ||
9350 | |||
9351 | /* this moves the current data to the previous state parts of the object */ | ||
9352 | /* in whatever way is safest for the object. also if we don't need object */ | ||
9353 | /* data anymore we can free it if the object deems this is a good idea */ | ||
9354 | /* o = (Evas_Object_Textblock *)(obj->object_data); */ | ||
9355 | /* remove those pesky changes */ | ||
9356 | evas_object_clip_changes_clean(obj); | ||
9357 | /* move cur to prev safely for object data */ | ||
9358 | obj->prev = obj->cur; | ||
9359 | /* o->prev = o->cur; */ | ||
9360 | /* o->changed = 0; */ | ||
9361 | } | ||
9362 | |||
9363 | static unsigned int evas_object_textblock_id_get(Evas_Object *obj) | ||
9364 | { | ||
9365 | Evas_Object_Textblock *o; | ||
9366 | |||
9367 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9368 | if (!o) return 0; | ||
9369 | return MAGIC_OBJ_TEXTBLOCK; | ||
9370 | } | ||
9371 | |||
9372 | static unsigned int evas_object_textblock_visual_id_get(Evas_Object *obj) | ||
9373 | { | ||
9374 | Evas_Object_Textblock *o; | ||
9375 | |||
9376 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9377 | if (!o) return 0; | ||
9378 | return MAGIC_OBJ_CUSTOM; | ||
9379 | } | ||
9380 | |||
9381 | static void *evas_object_textblock_engine_data_get(Evas_Object *obj) | ||
9382 | { | ||
9383 | Evas_Object_Textblock *o; | ||
9384 | |||
9385 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9386 | if (!o) return NULL; | ||
9387 | return o->engine_data; | ||
9388 | } | ||
9389 | |||
9390 | static int | ||
9391 | evas_object_textblock_is_opaque(Evas_Object *obj __UNUSED__) | ||
9392 | { | ||
9393 | /* this returns 1 if the internal object data implies that the object is */ | ||
9394 | /* currently fulyl opque over the entire gradient it occupies */ | ||
9395 | return 0; | ||
9396 | } | ||
9397 | |||
9398 | static int | ||
9399 | evas_object_textblock_was_opaque(Evas_Object *obj __UNUSED__) | ||
9400 | { | ||
9401 | /* this returns 1 if the internal object data implies that the object was */ | ||
9402 | /* currently fulyl opque over the entire gradient it occupies */ | ||
9403 | return 0; | ||
9404 | } | ||
9405 | |||
9406 | static void | ||
9407 | evas_object_textblock_coords_recalc(Evas_Object *obj) | ||
9408 | { | ||
9409 | Evas_Object_Textblock *o; | ||
9410 | |||
9411 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9412 | if ((obj->cur.geometry.w != o->last_w) || | ||
9413 | (((o->valign != 0.0) || (o->have_ellipsis)) && | ||
9414 | (obj->cur.geometry.h != o->last_h))) | ||
9415 | { | ||
9416 | o->formatted.valid = 0; | ||
9417 | o->changed = 1; | ||
9418 | } | ||
9419 | } | ||
9420 | |||
9421 | static void | ||
9422 | evas_object_textblock_scale_update(Evas_Object *obj) | ||
9423 | { | ||
9424 | Evas_Object_Textblock *o; | ||
9425 | |||
9426 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9427 | _evas_textblock_invalidate_all(o); | ||
9428 | _evas_textblock_changed(o, obj); | ||
9429 | } | ||
9430 | |||
9431 | void | ||
9432 | _evas_object_textblock_rehint(Evas_Object *obj) | ||
9433 | { | ||
9434 | Evas_Object_Textblock *o; | ||
9435 | Evas_Object_Textblock_Paragraph *par; | ||
9436 | Evas_Object_Textblock_Line *ln; | ||
9437 | |||
9438 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9439 | EINA_INLIST_FOREACH(o->paragraphs, par) | ||
9440 | { | ||
9441 | EINA_INLIST_FOREACH(par->lines, ln) | ||
9442 | { | ||
9443 | Evas_Object_Textblock_Item *it; | ||
9444 | |||
9445 | EINA_INLIST_FOREACH(ln->items, it) | ||
9446 | { | ||
9447 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
9448 | { | ||
9449 | Evas_Object_Textblock_Text_Item *ti = _ITEM_TEXT(it); | ||
9450 | if (ti->parent.format->font.font) | ||
9451 | { | ||
9452 | #ifdef EVAS_FRAME_QUEUING | ||
9453 | evas_common_pipe_op_text_flush((RGBA_Font *) ti->parent.format->font.font); | ||
9454 | #endif | ||
9455 | evas_font_load_hinting_set(obj->layer->evas, | ||
9456 | ti->parent.format->font.font, | ||
9457 | obj->layer->evas->hinting); | ||
9458 | } | ||
9459 | } | ||
9460 | } | ||
9461 | } | ||
9462 | } | ||
9463 | _evas_textblock_invalidate_all(o); | ||
9464 | _evas_textblock_changed(o, obj); | ||
9465 | } | ||
9466 | |||
9467 | /** | ||
9468 | * @} | ||
9469 | */ | ||
9470 | |||
9471 | #ifdef HAVE_TESTS | ||
9472 | /* return EINA_FALSE on error, used in unit_testing */ | ||
9473 | EAPI Eina_Bool | ||
9474 | _evas_textblock_check_item_node_link(Evas_Object *obj) | ||
9475 | { | ||
9476 | Evas_Object_Textblock *o; | ||
9477 | Evas_Object_Textblock_Paragraph *par; | ||
9478 | Evas_Object_Textblock_Line *ln; | ||
9479 | Evas_Object_Textblock_Item *it; | ||
9480 | |||
9481 | o = (Evas_Object_Textblock *)(obj->object_data); | ||
9482 | if (!o) return EINA_FALSE; | ||
9483 | |||
9484 | if (!o->formatted.valid) _relayout(obj); | ||
9485 | |||
9486 | EINA_INLIST_FOREACH(o->paragraphs, par) | ||
9487 | { | ||
9488 | EINA_INLIST_FOREACH(par->lines, ln) | ||
9489 | { | ||
9490 | EINA_INLIST_FOREACH(ln->items, it) | ||
9491 | { | ||
9492 | if (it->text_node != par->text_node) | ||
9493 | return EINA_FALSE; | ||
9494 | } | ||
9495 | } | ||
9496 | } | ||
9497 | return EINA_TRUE; | ||
9498 | } | ||
9499 | |||
9500 | EAPI int | ||
9501 | _evas_textblock_format_offset_get(const Evas_Object_Textblock_Node_Format *n) | ||
9502 | { | ||
9503 | return n->offset; | ||
9504 | } | ||
9505 | #endif | ||
9506 | |||
9507 | #if 0 | ||
9508 | /* Good for debugging */ | ||
9509 | void | ||
9510 | pfnode(Evas_Object_Textblock_Node_Format *n) | ||
9511 | { | ||
9512 | printf("Format Node: %p\n", n); | ||
9513 | printf("next = %p, prev = %p, last = %p\n", EINA_INLIST_GET(n)->next, EINA_INLIST_GET(n)->prev, EINA_INLIST_GET(n)->last); | ||
9514 | printf("text_node = %p, offset = %u, visible = %d\n", n->text_node, n->offset, n->visible); | ||
9515 | printf("'%s'\n", eina_strbuf_string_get(n->format)); | ||
9516 | } | ||
9517 | |||
9518 | void | ||
9519 | ptnode(Evas_Object_Textblock_Node_Text *n) | ||
9520 | { | ||
9521 | printf("Text Node: %p\n", n); | ||
9522 | printf("next = %p, prev = %p, last = %p\n", EINA_INLIST_GET(n)->next, EINA_INLIST_GET(n)->prev, EINA_INLIST_GET(n)->last); | ||
9523 | printf("format_node = %p\n", n->format_node); | ||
9524 | printf("'%ls'\n", eina_ustrbuf_string_get(n->unicode)); | ||
9525 | } | ||
9526 | |||
9527 | void | ||
9528 | pitem(Evas_Object_Textblock_Item *it) | ||
9529 | { | ||
9530 | Evas_Object_Textblock_Text_Item *ti; | ||
9531 | Evas_Object_Textblock_Format_Item *fi; | ||
9532 | printf("Item: %p\n", it); | ||
9533 | printf("Type: %s (%d)\n", (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ? | ||
9534 | "TEXT" : "FORMAT", it->type); | ||
9535 | printf("Text pos: %d Visual pos: %d\n", it->text_pos, | ||
9536 | #ifdef BIDI_SUPPORT | ||
9537 | it->visual_pos | ||
9538 | #else | ||
9539 | it->text_pos | ||
9540 | #endif | ||
9541 | ); | ||
9542 | printf("Coords: x = %d w = %d adv = %d\n", (int) it->x, (int) it->w, | ||
9543 | (int) it->adv); | ||
9544 | if (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) | ||
9545 | { | ||
9546 | ti = _ITEM_TEXT(it); | ||
9547 | printf("Text: '%*ls'\n", ti->text_props.text_len, GET_ITEM_TEXT(ti)); | ||
9548 | } | ||
9549 | else | ||
9550 | { | ||
9551 | fi = _ITEM_FORMAT(it); | ||
9552 | printf("Format: '%s'\n", fi->item); | ||
9553 | } | ||
9554 | } | ||
9555 | |||
9556 | void | ||
9557 | ppar(Evas_Object_Textblock_Paragraph *par) | ||
9558 | { | ||
9559 | Evas_Object_Textblock_Item *it; | ||
9560 | Eina_List *i; | ||
9561 | EINA_LIST_FOREACH(par->logical_items, i, it) | ||
9562 | { | ||
9563 | printf("***********************\n"); | ||
9564 | pitem(it); | ||
9565 | } | ||
9566 | } | ||
9567 | |||
9568 | #endif | ||
9569 | |||