1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* This file is part of the GtkHTML library.
3  *
4  * Copyright (C) 1997 Martin Jones (mjones@kde.org)
5  * Copyright (C) 1997 Torben Weis (weis@kde.org)
6  * Copyright (C) 1999 Helix Code, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22 */
23 
24 #include <config.h>
25 #include <stdio.h>
26 #include <string.h>
27 
28 #include "htmltextslave.h"
29 #include "htmlclue.h"
30 #include "htmlclueflow.h"
31 #include "htmlcursor.h"
32 #include "htmlcolor.h"
33 #include "htmlcolorset.h"
34 #include "htmlpainter.h"
35 #include "htmlobject.h"
36 #include "htmlprinter.h"
37 #include "htmlplainpainter.h"
38 #include "htmlgdkpainter.h"
39 #include "htmlsettings.h"
40 #include "gtkhtml.h"
41 
42 
43 /* #define HTML_TEXT_SLAVE_DEBUG */
44 
45 HTMLTextSlaveClass html_text_slave_class;
46 static HTMLObjectClass *parent_class = NULL;
47 
48 static void    clear_glyph_items (HTMLTextSlave *slave);
49 
50 gchar *
html_text_slave_get_text(HTMLTextSlave * slave)51 html_text_slave_get_text (HTMLTextSlave *slave)
52 {
53 	if (!slave->charStart)
54 		slave->charStart = html_text_get_text (slave->owner, slave->posStart);
55 
56 	return slave->charStart;
57 }
58 
59 /* Split this TextSlave at the specified offset.  */
60 static void
split(HTMLTextSlave * slave,guint offset,gint skip,gchar * start_pointer)61 split (HTMLTextSlave *slave,
62        guint offset,
63        gint skip,
64        gchar *start_pointer)
65 {
66 	HTMLObject *obj;
67 	HTMLObject *new;
68 
69 	g_return_if_fail (offset < slave->posLen);
70 
71 	obj = HTML_OBJECT (slave);
72 
73 	new = html_text_slave_new (slave->owner,
74 				   slave->posStart + offset + skip,
75 				   slave->posLen - (offset + skip));
76 
77 	HTML_TEXT_SLAVE (new)->charStart = start_pointer;
78 
79 	html_clue_append_after (HTML_CLUE (obj->parent), new, obj);
80 
81 	slave->posLen = offset;
82 }
83 
84 
85 /* HTMLObject methods.  */
86 
87 static void
copy(HTMLObject * self,HTMLObject * dest)88 copy (HTMLObject *self,
89       HTMLObject *dest)
90 {
91 	(* HTML_OBJECT_CLASS (parent_class)->copy) (self, dest);
92 
93 	/* FIXME it does not make much sense to me to share the owner.  */
94 	HTML_TEXT_SLAVE (dest)->owner = HTML_TEXT_SLAVE (self)->owner;
95 	HTML_TEXT_SLAVE (dest)->posStart = HTML_TEXT_SLAVE (self)->posStart;
96 	HTML_TEXT_SLAVE (dest)->posLen = HTML_TEXT_SLAVE (self)->posLen;
97 	HTML_TEXT_SLAVE (dest)->glyph_items = NULL;
98 }
99 
100 static inline gint
html_text_slave_get_start_byte_offset(HTMLTextSlave * slave)101 html_text_slave_get_start_byte_offset (HTMLTextSlave *slave)
102 {
103 	return html_text_slave_get_text (slave) - slave->owner->text;
104 }
105 
106 static void
hts_update_asc_dsc(HTMLPainter * painter,PangoItem * item,gint * asc,gint * dsc)107 hts_update_asc_dsc (HTMLPainter *painter,
108                     PangoItem *item,
109                     gint *asc,
110                     gint *dsc)
111 {
112 	PangoFontMetrics *pfm;
113 
114 	pfm = pango_font_get_metrics (item->analysis.font, item->analysis.language);
115 	if (asc)
116 		*asc = MAX (*asc, pango_font_metrics_get_ascent (pfm));
117 	if (dsc)
118 		*dsc = MAX (*dsc, pango_font_metrics_get_descent (pfm));
119 	pango_font_metrics_unref (pfm);
120 }
121 
122 static gint
hts_calc_width(HTMLTextSlave * slave,HTMLPainter * painter,gint * asc,gint * dsc)123 hts_calc_width (HTMLTextSlave *slave,
124                 HTMLPainter *painter,
125                 gint *asc,
126                 gint *dsc)
127 {
128 	/*HTMLText *text = slave->owner;
129 	  gint line_offset, tabs = 0, width = 0;
130  *
131 	line_offset = html_text_slave_get_line_offset (slave, 0, painter);
132 	if (line_offset != -1)
133 		width += (html_text_text_line_length (html_text_slave_get_text (slave), &line_offset, slave->posLen, &tabs) - slave->posLen)*
134 			html_painter_get_space_width (painter, html_text_get_font_style (text), text->face);
135  *
136 			width += html_text_calc_part_width (text, painter, html_text_slave_get_text (slave), slave->posStart, slave->posLen, asc, dsc); */
137 
138 	GSList *cur, *gilist = html_text_slave_get_glyph_items (slave, painter);
139 	PangoLanguage *language = NULL;
140 	PangoFont *font = NULL;
141 	gint width = 0;
142 
143 	if (asc)
144 		*asc = html_painter_engine_to_pango (painter,
145 						     html_painter_get_space_asc (painter, html_text_get_font_style (slave->owner), slave->owner->face));
146 	if (dsc)
147 		*dsc = html_painter_engine_to_pango (painter,
148 						     html_painter_get_space_dsc (painter, html_text_get_font_style (slave->owner), slave->owner->face));
149 
150 	for (cur = gilist; cur; cur = cur->next) {
151 		HTMLTextSlaveGlyphItem *sgi = (HTMLTextSlaveGlyphItem *) cur->data;
152 		PangoRectangle log_rect;
153 		PangoItem *item = sgi->glyph_item.item;
154 
155 		pango_glyph_string_extents (sgi->glyph_item.glyphs, sgi->glyph_item.item->analysis.font, NULL, &log_rect);
156 		width += log_rect.width;
157 
158 		if (item->analysis.font != font || item->analysis.language != language)
159 			hts_update_asc_dsc (painter, item, asc, dsc);
160 	}
161 
162 	if (asc)
163 		*asc = html_painter_pango_to_engine (painter, *asc);
164 	if (dsc)
165 		*dsc = html_painter_pango_to_engine (painter, *dsc);
166 
167 	width = html_painter_pango_to_engine (painter, width);
168 
169 	/* printf ("hts width: %d\n", width); */
170 
171 	return width;
172 }
173 
174 inline static void
glyphs_destroy(GList * glyphs)175 glyphs_destroy (GList *glyphs)
176 {
177 	GList *l;
178 
179 	for (l = glyphs; l; l = l->next->next)
180 		pango_glyph_string_free ((PangoGlyphString *) l->data);
181 	g_list_free (glyphs);
182 }
183 
184 inline static void
glyph_items_destroy(GSList * glyph_items)185 glyph_items_destroy (GSList *glyph_items)
186 {
187 	GSList *l;
188 
189 	for (l = glyph_items; l; l = l->next) {
190 		HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) l->data;
191 
192 		if (gi->type == HTML_TEXT_SLAVE_GLYPH_ITEM_CREATED) {
193 			if (gi->glyph_item.item)
194 				pango_item_free (gi->glyph_item.item);
195 			if (gi->glyph_item.glyphs)
196 				pango_glyph_string_free (gi->glyph_item.glyphs);
197 			g_free (gi->widths);
198 		}
199 
200 		g_free (gi);
201 	}
202 	g_slist_free (glyph_items);
203 }
204 
205 static gboolean
html_text_slave_real_calc_size(HTMLObject * self,HTMLPainter * painter,GList ** changed_objs)206 html_text_slave_real_calc_size (HTMLObject *self,
207                                 HTMLPainter *painter,
208                                 GList **changed_objs)
209 {
210 	HTMLText *owner;
211 	HTMLTextSlave *slave;
212 	GtkHTMLFontStyle font_style;
213 	gint new_ascent, new_descent, new_width;
214 	gboolean changed;
215 
216 	slave = HTML_TEXT_SLAVE (self);
217 	owner = HTML_TEXT (slave->owner);
218 	font_style = html_text_get_font_style (owner);
219 
220 	new_width = MAX (1, hts_calc_width (slave, painter, &new_ascent, &new_descent));
221 
222 	/* handle sub & super script */
223 	if (font_style & GTK_HTML_FONT_STYLE_SUBSCRIPT || font_style & GTK_HTML_FONT_STYLE_SUPERSCRIPT) {
224 		gint shift = (new_ascent + new_descent) >> 1;
225 
226 		if (font_style & GTK_HTML_FONT_STYLE_SUBSCRIPT) {
227 			new_descent += shift;
228 			new_ascent  -= shift;
229 		} else {
230 			new_descent -= shift;
231 			new_ascent  += shift;
232 		}
233 	}
234 
235 	changed = FALSE;
236 
237 	if (new_ascent != self->ascent) {
238 		self->ascent = new_ascent;
239 		changed = TRUE;
240 	}
241 
242 	if (new_descent != self->descent) {
243 		self->descent = new_descent;
244 		changed = TRUE;
245 	}
246 
247 	if (new_width != self->width) {
248 		self->width = new_width;
249 		changed = TRUE;
250 	}
251 
252 	return changed;
253 }
254 
255 #ifdef HTML_TEXT_SLAVE_DEBUG
256 static void
debug_print(HTMLFitType rv,gchar * text,gint len)257 debug_print (HTMLFitType rv,
258              gchar *text,
259              gint len)
260 {
261 	gint i;
262 
263 	printf ("Split text");
264 	switch (rv) {
265 	case HTML_FIT_PARTIAL:
266 		printf (" (Partial): `");
267 		break;
268 	case HTML_FIT_NONE:
269 		printf (" (NoFit): `");
270 		break;
271 	case HTML_FIT_COMPLETE:
272 		printf (" (Complete): `");
273 		break;
274 	}
275 
276 	for (i = 0; i < len; i++)
277 		putchar (text[i]);
278 
279 	printf ("'\n");
280 }
281 #endif
282 
283 gint
html_text_slave_get_line_offset(HTMLTextSlave * slave,gint offset,HTMLPainter * p)284 html_text_slave_get_line_offset (HTMLTextSlave *slave,
285                                  gint offset,
286                                  HTMLPainter *p)
287 {
288 	HTMLObject *head = HTML_OBJECT (slave->owner)->next;
289 
290 	g_assert (HTML_IS_TEXT_SLAVE (head));
291 
292 	if (!html_clueflow_tabs (HTML_CLUEFLOW (HTML_OBJECT (slave)->parent), p))
293 		return -1;
294 
295 	if (head->y + head->descent - 1 < HTML_OBJECT (slave)->y - HTML_OBJECT (slave)->ascent) {
296 		HTMLObject *prev;
297 		HTMLTextSlave *bol;
298 		gint line_offset = 0;
299 
300 		prev = html_object_prev (HTML_OBJECT (slave)->parent, HTML_OBJECT (slave));
301 		while (prev->y + prev->descent - 1 >= HTML_OBJECT (slave)->y - HTML_OBJECT (slave)->ascent)
302 			prev = html_object_prev (HTML_OBJECT (slave)->parent, HTML_OBJECT (prev));
303 
304 		bol = HTML_TEXT_SLAVE (prev->next);
305 		return html_text_text_line_length (html_text_slave_get_text (bol),
306 						   &line_offset, slave->posStart + offset - bol->posStart, NULL);
307 	} else
308 		return html_text_get_line_offset (slave->owner, p, slave->posStart + offset);
309 }
310 
311 static gboolean
could_remove_leading_space(HTMLTextSlave * slave,gboolean lineBegin)312 could_remove_leading_space (HTMLTextSlave *slave,
313                             gboolean lineBegin)
314 {
315 	HTMLObject *o = HTML_OBJECT (slave->owner);
316 
317 	if (lineBegin && (HTML_OBJECT (slave)->prev != o || o->prev))
318 		return TRUE;
319 
320 	if (!o->prev)
321 		return FALSE;
322 
323 	while (o->prev && HTML_OBJECT_TYPE (o->prev) == HTML_TYPE_CLUEALIGNED)
324 		o = o->prev;
325 
326 	return o->prev ? FALSE : TRUE;
327 }
328 
329 gchar *
html_text_slave_remove_leading_space(HTMLTextSlave * slave,HTMLPainter * painter,gboolean lineBegin)330 html_text_slave_remove_leading_space (HTMLTextSlave *slave,
331                                       HTMLPainter *painter,
332                                       gboolean lineBegin)
333 {
334 	gchar *begin;
335 
336 	begin = html_text_slave_get_text (slave);
337 	if (*begin == ' ' && could_remove_leading_space (slave, lineBegin)) {
338 		begin = g_utf8_next_char (begin);
339 		slave->charStart = begin;
340 		slave->posStart++;
341 		slave->posLen--;
342 	}
343 
344 	return begin;
345 }
346 
347 gint
html_text_slave_get_nb_width(HTMLTextSlave * slave,HTMLPainter * painter,gboolean lineBegin)348 html_text_slave_get_nb_width (HTMLTextSlave *slave,
349                               HTMLPainter *painter,
350                               gboolean lineBegin)
351 {
352 	html_text_slave_remove_leading_space (slave, painter, lineBegin);
353 
354 	return html_object_calc_min_width (HTML_OBJECT (slave), painter);
355 }
356 
357 /*
358  * offset: current position - gchar offset
359  * s: current position - string pointer
360  * ii: current position - index of item
361  * io: current position - offset within item
362  * line_offset:
363  * w: width for current position
364  * lwl: last-whitespacing-length (in characters)
365  * lbw: last-break-width
366  * lbo: last-break-offset (into text)
367  * lbsp: last-break-string-pointer
368  *
369  * Checks to see if breaking the text at the given position would result in it fitting
370  * into the remaining width. If so, updates the last-break information (lwl,lbw,lbo,lbsp).
371  * We'll actually break the item at the last break position that still fits.
372  */
373 static gboolean
update_lb(HTMLTextSlave * slave,HTMLPainter * painter,gint widthLeft,gint offset,gchar * s,gint ii,gint io,gint line_offset,gint w,gint * lwl,gint * lbw,gint * lbo,gchar ** lbsp,gboolean * force_fit)374 update_lb (HTMLTextSlave *slave,
375            HTMLPainter *painter,
376            gint widthLeft,
377            gint offset,
378            gchar *s,
379            gint ii,
380            gint io,
381            gint line_offset,
382            gint w,
383            gint *lwl,
384            gint *lbw,
385            gint *lbo,
386            gchar **lbsp,
387            gboolean *force_fit)
388 {
389 	gint new_ltw, new_lwl, aw;
390 
391 	new_ltw = html_text_tail_white_space (slave->owner, painter, offset, ii, io, &new_lwl, line_offset, s);
392 	aw = w - new_ltw;
393 
394 	if (aw <= widthLeft || *force_fit) {
395 		*lwl = new_lwl;
396 		*lbw = aw;
397 		*lbo = offset;
398 		*lbsp = s;
399 		if (*force_fit && *lbw >= widthLeft)
400 			return TRUE;
401 		*force_fit = FALSE;
402 	} else
403 		return TRUE;
404 
405 	return FALSE;
406 }
407 
408 static HTMLFitType
hts_fit_line(HTMLObject * o,HTMLPainter * painter,gboolean lineBegin,gboolean firstRun,gboolean next_to_floating,gint widthLeft)409 hts_fit_line (HTMLObject *o,
410               HTMLPainter *painter,
411               gboolean lineBegin,
412               gboolean firstRun,
413               gboolean next_to_floating,
414               gint widthLeft)
415 {
416 	HTMLTextSlave *slave = HTML_TEXT_SLAVE (o);
417 	gint lbw, w, lbo, lwl, offset;
418 	gint ii, io, line_offset;
419 	gchar *s, *lbsp;
420 	HTMLFitType rv = HTML_FIT_NONE;
421 	HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter);
422 	gboolean force_fit = lineBegin;
423 
424 	if (slave->posLen == 0)
425 		return HTML_FIT_COMPLETE;
426 
427 	widthLeft = html_painter_engine_to_pango (painter, widthLeft);
428 
429 	lbw = lwl = w = 0;
430 	offset = lbo = slave->posStart;
431 	ii = html_text_get_item_index (slave->owner, painter, offset, &io);
432 
433 	line_offset = html_text_get_line_offset (slave->owner, painter, offset);
434 	lbsp = s = html_text_slave_get_text (slave);
435 
436 	while ((force_fit || widthLeft > lbw) && offset < slave->posStart + slave->posLen) {
437 		if (offset > slave->posStart && offset > lbo && html_text_is_line_break (pi->attrs[offset]))
438 			if (update_lb (slave, painter, widthLeft, offset, s, ii, io, line_offset, w, &lwl, &lbw, &lbo, &lbsp, &force_fit))
439 				break;
440 
441 		if (io == 0 && slave->owner->text[pi->entries[ii].glyph_item.item->offset]  == '\t') {
442 			GtkHTMLFontStyle font_style;
443 			gchar *face;
444 			gint skip = 8 - (line_offset % 8);
445 
446 			if (HTML_IS_PLAIN_PAINTER (painter)) {
447 				font_style = GTK_HTML_FONT_STYLE_FIXED;
448 				face = NULL;
449 			} else {
450 				font_style = html_text_get_font_style (slave->owner);
451 				face = slave->owner->face;
452 			}
453 
454 			pi->entries[ii].glyph_item.glyphs->glyphs[0].geometry.width = pi->entries[ii].widths[io]
455 				= skip * html_painter_get_space_width (painter, font_style, face) * PANGO_SCALE;
456 			line_offset += skip;
457 		} else {
458 			line_offset++;
459 		}
460 		w += pi->entries[ii].widths[io];
461 
462 		html_text_pi_forward (pi, &ii, &io);
463 		s = g_utf8_next_char (s);
464 		offset++;
465 	}
466 
467 	if (offset == slave->posStart + slave->posLen && (widthLeft >= w || force_fit)) {
468 		rv = HTML_FIT_COMPLETE;
469 		if (slave->posLen)
470 			o->width = html_painter_pango_to_engine (painter, w);
471 	} else if (lbo > slave->posStart) {
472 		split (slave, lbo - slave->posStart - lwl, lwl, lbsp);
473 		rv = HTML_FIT_PARTIAL;
474 		o->width = html_painter_pango_to_engine (painter, lbw);
475 		o->change |= HTML_CHANGE_RECALC_PI;
476 	}
477 
478 	return rv;
479 }
480 
481 static gboolean
select_range(HTMLObject * self,HTMLEngine * engine,guint start,gint length,gboolean queue_draw)482 select_range (HTMLObject *self,
483               HTMLEngine *engine,
484               guint start,
485               gint length,
486               gboolean queue_draw)
487 {
488 	return FALSE;
489 }
490 
491 static guint
get_length(HTMLObject * self)492 get_length (HTMLObject *self)
493 {
494 	return 0;
495 }
496 
497 
498 /* HTMLObject::draw() implementation.  */
499 
500 static gint
get_ys(HTMLText * text,HTMLPainter * p)501 get_ys (HTMLText *text,
502         HTMLPainter *p)
503 {
504 	if (text->font_style & GTK_HTML_FONT_STYLE_SUBSCRIPT || text->font_style & GTK_HTML_FONT_STYLE_SUPERSCRIPT) {
505 		gint height2;
506 
507 		height2 = (HTML_OBJECT (text)->ascent + HTML_OBJECT (text)->descent) / 2;
508 		/* FIX2? (html_painter_calc_ascent (p, text->font_style, text->face)
509 		 * + html_painter_calc_descent (p, text->font_style, text->face)) >> 1; */
510 		return (text->font_style & GTK_HTML_FONT_STYLE_SUBSCRIPT) ? height2 : -height2;
511 
512 	} else
513 		return 0;
514 }
515 
516 static inline GList *
get_glyphs_base_text(GList * glyphs,PangoItem * item,gint ii,const gchar * text,gint bytes)517 get_glyphs_base_text (GList *glyphs,
518                       PangoItem *item,
519                       gint ii,
520                       const gchar *text,
521                       gint bytes)
522 {
523 	PangoGlyphString *str;
524 
525 	str = pango_glyph_string_new ();
526 	pango_shape (text, bytes, &item->analysis, str);
527 	glyphs = g_list_prepend (glyphs, str);
528 	glyphs = g_list_prepend (glyphs, GINT_TO_POINTER (ii));
529 
530 	return glyphs;
531 }
532 
533 GList *
html_get_glyphs_non_tab(GList * glyphs,PangoItem * item,gint ii,const gchar * text,gint bytes,gint len)534 html_get_glyphs_non_tab (GList *glyphs,
535                          PangoItem *item,
536                          gint ii,
537                          const gchar *text,
538                          gint bytes,
539                          gint len)
540 {
541 	gchar *tab;
542 
543 	while ((tab = memchr (text, (guchar) '\t', bytes))) {
544 		gint c_bytes = tab - text;
545 		if (c_bytes > 0)
546 			glyphs = get_glyphs_base_text (glyphs, item, ii, text, c_bytes);
547 		text += c_bytes + 1;
548 		bytes -= c_bytes + 1;
549 	}
550 
551 	if (bytes > 0)
552 		glyphs = get_glyphs_base_text (glyphs, item, ii, text, bytes);
553 
554 	return glyphs;
555 }
556 
557 /*
558  * NB: This implement the exact same algorithm as
559  *     reorder-items.c:pango_reorder_items().
560  */
561 
562 static GSList *
reorder_glyph_items(GSList * glyph_items,gint n_items)563 reorder_glyph_items (GSList *glyph_items,
564                      gint n_items)
565 {
566 	GSList *tmp_list, *level_start_node;
567 	gint i, level_start_i;
568 	gint min_level = G_MAXINT;
569 	GSList *result = NULL;
570 
571 	if (n_items == 0)
572 		return NULL;
573 
574 	tmp_list = glyph_items;
575 	for (i = 0; i < n_items; i++) {
576 		HTMLTextSlaveGlyphItem *gi = tmp_list->data;
577 
578 		min_level = MIN (min_level, gi->glyph_item.item->analysis.level);
579 
580 		tmp_list = tmp_list->next;
581 	}
582 
583 	level_start_i = 0;
584 	level_start_node = glyph_items;
585 	tmp_list = glyph_items;
586 	for (i = 0; i < n_items; i++) {
587 		HTMLTextSlaveGlyphItem *gi= tmp_list->data;
588 
589 		if (gi->glyph_item.item->analysis.level == min_level) {
590 			if (min_level % 2) {
591 				if (i > level_start_i)
592 					result = g_slist_concat (reorder_glyph_items (level_start_node, i - level_start_i), result);
593 				result = g_slist_prepend (result, gi);
594 			} else {
595 				if (i > level_start_i)
596 					result = g_slist_concat (result, reorder_glyph_items (level_start_node, i - level_start_i));
597 				result = g_slist_append (result, gi);
598 			}
599 
600 			level_start_i = i + 1;
601 			level_start_node = tmp_list->next;
602 		}
603 
604 		tmp_list = tmp_list->next;
605 	}
606 
607 	if (min_level % 2) {
608 		if (i > level_start_i)
609 			result = g_slist_concat (reorder_glyph_items (level_start_node, i - level_start_i), result);
610 	} else {
611 		if (i > level_start_i)
612 			result = g_slist_concat (result, reorder_glyph_items (level_start_node, i - level_start_i));
613 	}
614 
615 	return result;
616 }
617 
618 static GSList *
get_glyph_items_in_range(HTMLTextSlave * slave,HTMLPainter * painter,gint start_offset,gint len)619 get_glyph_items_in_range (HTMLTextSlave *slave,
620                           HTMLPainter *painter,
621                           gint start_offset,
622                           gint len)
623 {
624 	HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter);
625 	gint i, offset, end_offset, n_items = 0;
626 	GSList *glyph_items = NULL;
627 
628 	start_offset += slave->posStart;
629 	end_offset = start_offset + len;
630 
631 	for (offset = 0, i = 0; i < pi->n; i++) {
632 		PangoItem *item = pi->entries[i].glyph_item.item;
633 
634 		/* do item and slave overlap? */
635 		if (MAX (offset, start_offset) < MIN (offset + item->num_chars, end_offset)) {
636 			HTMLTextSlaveGlyphItem *glyph_item = g_new (HTMLTextSlaveGlyphItem, 1);
637 
638 			/* use text glyph item */
639 			glyph_item->type = HTML_TEXT_SLAVE_GLYPH_ITEM_PARENTAL;
640 			glyph_item->glyph_item = pi->entries[i].glyph_item;
641 			glyph_item->widths = pi->entries[i].widths;
642 
643 			if (start_offset > offset) {
644 				/* need to cut the beginning of current glyph item */
645 				PangoGlyphItem *tmp_gi;
646 				gint split_index;
647 
648 				glyph_item->type = HTML_TEXT_SLAVE_GLYPH_ITEM_CREATED;
649 				glyph_item->widths = NULL;
650 				glyph_item->glyph_item.item = pango_item_copy (glyph_item->glyph_item.item);
651 				glyph_item->glyph_item.glyphs = pango_glyph_string_copy (glyph_item->glyph_item.glyphs);
652 
653 				split_index = g_utf8_offset_to_pointer (slave->owner->text + item->offset, start_offset - offset)
654 					- (slave->owner->text + item->offset);
655 				tmp_gi = pango_glyph_item_split (&glyph_item->glyph_item, slave->owner->text, split_index);
656 
657 				/* free the beginning we don't need */
658 				pango_glyph_item_free (tmp_gi);
659 
660 			}
661 
662 			if (offset + item->num_chars > end_offset) {
663 				/* need to cut the ending of current glyph item */
664 				PangoGlyphItem tmp_gi1, *tmp_gi2;
665 				gint split_index;
666 
667 				if (glyph_item->type == HTML_TEXT_SLAVE_GLYPH_ITEM_PARENTAL) {
668 					tmp_gi1.item = pango_item_copy (glyph_item->glyph_item.item);
669 					tmp_gi1.glyphs = pango_glyph_string_copy (glyph_item->glyph_item.glyphs);
670 				} else
671 					tmp_gi1 = glyph_item->glyph_item;
672 
673 				split_index = g_utf8_offset_to_pointer (slave->owner->text + tmp_gi1.item->offset, end_offset - MAX (start_offset, offset))
674 					- (slave->owner->text + tmp_gi1.item->offset);
675 				tmp_gi2 = pango_glyph_item_split (&tmp_gi1, slave->owner->text, split_index);
676 
677 				glyph_item->glyph_item = *tmp_gi2;
678 				tmp_gi2->item = NULL;
679 				tmp_gi2->glyphs = NULL;
680 
681 				/* free the tmp1 content and tmp2 container, but not the content */
682 				pango_item_free (tmp_gi1.item);
683 				pango_glyph_string_free (tmp_gi1.glyphs);
684 				pango_glyph_item_free (tmp_gi2);
685 
686 				glyph_item->type = HTML_TEXT_SLAVE_GLYPH_ITEM_CREATED;
687 				glyph_item->widths = NULL;
688 			}
689 
690 			glyph_items = g_slist_prepend (glyph_items, glyph_item);
691 			n_items++;
692 		}
693 
694 		if (offset + item->num_chars >= end_offset)
695 			break;
696 		offset += item->num_chars;
697 	}
698 
699 	if (glyph_items) {
700 		GSList *reversed;
701 
702 		reversed = g_slist_reverse (glyph_items);
703 		glyph_items = reorder_glyph_items (reversed, n_items);
704 		g_slist_free (reversed);
705 	}
706 
707 	return glyph_items;
708 }
709 
710 GSList *
html_text_slave_get_glyph_items(HTMLTextSlave * slave,HTMLPainter * painter)711 html_text_slave_get_glyph_items (HTMLTextSlave *slave,
712                                  HTMLPainter *painter)
713 {
714 	if (painter && (!slave->glyph_items || (HTML_OBJECT (slave)->change & HTML_CHANGE_RECALC_PI))) {
715 		clear_glyph_items (slave);
716 
717 		HTML_OBJECT (slave)->change &= ~HTML_CHANGE_RECALC_PI;
718 		slave->glyph_items = get_glyph_items_in_range (slave, painter, 0, slave->posLen);
719 	}
720 
721 	return slave->glyph_items;
722 }
723 
724 static gboolean
calc_glyph_range_size(HTMLText * text,PangoGlyphItem * glyph_item,gint start_index,gint end_index,gint * x_offset,gint * width,gint * asc,gint * height)725 calc_glyph_range_size (HTMLText *text,
726                        PangoGlyphItem *glyph_item,
727                        gint start_index,
728                        gint end_index,
729                        gint *x_offset,
730                        gint *width,
731                        gint *asc,
732                        gint *height)
733 {
734 	gint isect_start, isect_end;
735 
736 	isect_start = MAX (glyph_item->item->offset, start_index);
737 	isect_end = MIN (glyph_item->item->offset + glyph_item->item->length, end_index);
738 
739 	isect_start -= glyph_item->item->offset;
740 	isect_end -= glyph_item->item->offset;
741 
742 	/* printf ("calc_glyph_range_size isect start %d end %d (end_index %d)\n", isect_start, isect_end, end_index); */
743 
744 	if (isect_start <= isect_end) {
745 		PangoRectangle log_rect;
746 		gint start_x, end_x;
747 
748 		pango_glyph_string_index_to_x (glyph_item->glyphs,
749 					       text->text + glyph_item->item->offset,
750 					       glyph_item->item->length,
751 					       &glyph_item->item->analysis,
752 					       isect_start,
753 					       FALSE, &start_x);
754 
755 		if (isect_start < isect_end)
756 			pango_glyph_string_index_to_x (glyph_item->glyphs,
757 						       text->text + glyph_item->item->offset,
758 						       glyph_item->item->length,
759 						       &glyph_item->item->analysis,
760 						       isect_end,
761 						       FALSE, &end_x);
762 		else
763 			end_x = start_x;
764 
765 		if (asc || height)
766 			/* this call is used only to get ascent and height */
767 			pango_glyph_string_extents (glyph_item->glyphs, glyph_item->item->analysis.font, NULL, &log_rect);
768 
769 		/* printf ("selection_start_index %d selection_end_index %d isect_start %d isect_end %d start_x %d end_x %d cwidth %d width %d\n",
770 		 * selection_start_index, selection_end_index, isect_start, isect_end,
771 		 * html_painter_pango_to_engine (p, start_x), html_painter_pango_to_engine (p, end_x),
772 		 * html_painter_pango_to_engine (p, start_x < end_x ? (end_x - start_x) : (start_x - end_x)),
773 		 * html_painter_pango_to_engine (p, log_rect.width)); */
774 
775 		if (x_offset)
776 			*x_offset = MIN (start_x, end_x);
777 		if (width)
778 			*width = start_x < end_x ? (end_x - start_x) : (start_x - end_x);
779 		if (asc)
780 			*asc = PANGO_ASCENT (log_rect);
781 		if (height)
782 			*height = log_rect.height;
783 
784 		return TRUE;
785 	}
786 
787 	return FALSE;
788 }
789 
790 static void
draw_text(HTMLTextSlave * self,HTMLPainter * p,GtkHTMLFontStyle font_style,gint x,gint y,gint width,gint height,gint tx,gint ty)791 draw_text (HTMLTextSlave *self,
792            HTMLPainter *p,
793            GtkHTMLFontStyle font_style,
794            gint x,
795            gint y,
796            gint width,
797            gint height,
798            gint tx,
799            gint ty)
800 {
801 	HTMLObject *obj;
802 	HTMLText *text = self->owner;
803 	GSList *cur;
804 	gint run_width;
805 	gint selection_start_index = 0;
806 	gint selection_end_index = 0;
807 	gint isect_start, isect_end;
808 	gboolean selection;
809 	GdkColor selection_fg, selection_bg;
810 	HTMLEngine *e = NULL;
811 
812 	obj = HTML_OBJECT (self);
813 
814 	isect_start = MAX (text->select_start, self->posStart);
815 	isect_end = MIN (text->select_start + text->select_length, self->posStart + self->posLen);
816 	selection = isect_start < isect_end;
817 
818 	if (p->widget && GTK_IS_HTML (p->widget))
819 		e = html_object_engine (HTML_OBJECT (self->owner), GTK_HTML (p->widget)->engine);
820 
821 	if (selection) {
822 		gchar *end;
823 		gchar *start;
824 
825 		start = html_text_get_text (text, isect_start);
826 		end = g_utf8_offset_to_pointer (start, isect_end - isect_start);
827 
828 		selection_start_index = start - text->text;
829 		selection_end_index = end - text->text;
830 
831 		if (e) {
832 			selection_fg = html_colorset_get_color_allocated
833 				(e->settings->color_set, p,
834 				 p->focus ? HTMLHighlightTextColor : HTMLHighlightTextNFColor)->color;
835 			selection_bg = html_colorset_get_color_allocated
836 				(e->settings->color_set, p,
837 				 p->focus ? HTMLHighlightColor : HTMLHighlightNFColor)->color;
838 		}
839 	}
840 
841 	/* printf ("draw_text %d %d %d\n", selection_bg.red, selection_bg.green, selection_bg.blue); */
842 
843 	run_width = 0;
844 	for (cur = html_text_slave_get_glyph_items (self, p); cur; cur = cur->next) {
845 		HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) cur->data;
846 		GList *cur_se;
847 		gint cur_width;
848 
849 		if (e)
850 			html_painter_set_pen (p, &html_colorset_get_color_allocated (e->settings->color_set,
851 										     e->painter, HTMLTextColor)->color);
852 		cur_width = html_painter_draw_glyphs (p, obj->x + tx + html_painter_pango_to_engine (p, run_width),
853 						      obj->y + ty + get_ys (text, p), gi->glyph_item.item, gi->glyph_item.glyphs, NULL, NULL);
854 
855 		if (selection) {
856 			gint start_x, width, asc, height;
857 			gint cx, cy, cw, ch;
858 
859 			if (calc_glyph_range_size (text, &gi->glyph_item, selection_start_index, selection_end_index, &start_x, &width, &asc, &height) && width > 0) {
860 				html_painter_get_clip_rectangle (p, &cx, &cy, &cw, &ch);
861 /*				printf ("run_width; %d start_x %d index %d\n", run_width, start_x, selection_start_index); */
862 				html_painter_set_clip_rectangle (p,
863 							obj->x + tx + html_painter_pango_to_engine (p, run_width + start_x),
864 							obj->y + ty + get_ys (text, p) - html_painter_pango_to_engine (p, asc),
865 							html_painter_pango_to_engine (p, width),
866 							html_painter_pango_to_engine (p, height));
867 
868 				/* printf ("draw selection %d %d %d at %d, %d\n", selection_bg.red, selection_bg.green, selection_bg.blue,
869 				 * obj->x + tx + run_width, obj->y + ty + get_ys (text, p)); */
870 				html_painter_draw_glyphs (p, obj->x + tx + html_painter_pango_to_engine (p, run_width),
871 							  obj->y + ty + get_ys (text, p), gi->glyph_item.item, gi->glyph_item.glyphs,
872 							  &selection_fg, &selection_bg);
873 				html_painter_set_clip_rectangle (p, cx, cy, cw, ch);
874 			}
875 		}
876 
877 		for (cur_se = text->spell_errors; e && cur_se; cur_se = cur_se->next) {
878 			SpellError *se;
879 			guint ma, mi;
880 
881 			se = (SpellError *) cur_se->data;
882 			ma = MAX (se->off, self->posStart);
883 			mi = MIN (se->off + se->len, self->posStart + self->posLen);
884 
885 			if (ma < mi) {
886 				gint width, height, asc, start_x;
887 
888 				gchar *end;
889 				gchar *start;
890 				gint se_start_index, se_end_index;
891 
892 				start = html_text_get_text (text, ma);
893 				end = g_utf8_offset_to_pointer (start, mi - ma);
894 
895 				se_start_index = start - text->text;
896 				se_end_index = end - text->text;
897 
898 				if (calc_glyph_range_size (text, &gi->glyph_item, se_start_index, se_end_index, &start_x, &width, &asc, &height)) {
899 					html_painter_set_pen (p, &html_colorset_get_color_allocated (e->settings->color_set,
900 												     p, HTMLSpellErrorColor)->color);
901 					/* printf ("spell error: %s\n", html_text_get_text (slave->owner, off)); */
902 
903 					html_painter_draw_spell_error (p, obj->x + tx + html_painter_pango_to_engine (p, run_width + start_x),
904 								       obj->y + ty + get_ys (self->owner, p), html_painter_pango_to_engine (p, width));
905 				}
906 			}
907 			if (se->off > self->posStart + self->posLen)
908 				break;
909 		}
910 
911 		run_width += cur_width;
912 	}
913 }
914 
915 static void
draw_focus_rectangle(HTMLTextSlave * slave,HTMLPainter * painter,GdkRectangle * box)916 draw_focus_rectangle (HTMLTextSlave *slave,
917                        HTMLPainter *painter,
918                        GdkRectangle *box)
919 {
920 	HTMLGdkPainter *p;
921 	const double dashes[] = { 1, 2 };
922 	gint ndash = G_N_ELEMENTS (dashes);
923 	HTMLEngine *e;
924 
925 	if (painter->widget && GTK_IS_HTML (painter->widget))
926 		e = html_object_engine (HTML_OBJECT (slave->owner), GTK_HTML (painter->widget)->engine);
927 	else
928 		return;
929 
930 	if (HTML_IS_PRINTER (painter))
931 		return;
932 
933 	p = HTML_GDK_PAINTER (painter);
934 	/* printf ("draw_text_focus\n"); */
935 
936 	cairo_save (p->cr);
937 	gdk_cairo_set_source_color (p->cr,
938 				    &html_colorset_get_color_allocated (e->settings->color_set,
939 									painter, HTMLTextColor)->color);
940 	cairo_set_line_cap (p->cr, CAIRO_LINE_CAP_ROUND);
941 	cairo_set_line_width (p->cr, 1);
942 	cairo_set_dash (p->cr, dashes, ndash, 2);
943 	cairo_rectangle (
944 		p->cr,
945 		box->x - p->x1 - 0.5,
946 		box->y - p->y1 + 0.5,
947 		box->width + 1, box->height);
948 	cairo_stroke (p->cr);
949 	cairo_restore (p->cr);
950 }
951 
952 static void
draw_focus(HTMLTextSlave * slave,HTMLPainter * p,gint tx,gint ty)953 draw_focus (HTMLTextSlave *slave,
954             HTMLPainter *p,
955             gint tx,
956             gint ty)
957 {
958 	GdkRectangle rect;
959 	Link *link = html_text_get_link_at_offset (slave->owner, slave->owner->focused_link_offset);
960 
961 	if (link && MAX (link->start_offset, slave->posStart) < MIN (link->end_offset, slave->posStart + slave->posLen)) {
962 		gint bw = 0;
963 		html_object_get_bounds (HTML_OBJECT (slave), &rect);
964 		if (slave->posStart < link->start_offset)
965 			bw = html_text_calc_part_width (slave->owner, p, html_text_slave_get_text (slave),
966 							slave->posStart, link->start_offset - slave->posStart, NULL, NULL);
967 		rect.x += tx + bw;
968 		rect.width -= bw;
969 		if (slave->posStart + slave->posLen > link->end_offset)
970 			rect.width -= html_text_calc_part_width (slave->owner, p, slave->owner->text + link->end_index, link->end_offset,
971 								 slave->posStart + slave->posLen - link->end_offset, NULL, NULL);
972 		rect.y += ty;
973 		draw_focus_rectangle (slave, p, &rect);
974 	}
975 }
976 
977 static void
draw(HTMLObject * o,HTMLPainter * p,gint x,gint y,gint width,gint height,gint tx,gint ty)978 draw (HTMLObject *o,
979       HTMLPainter *p,
980       gint x,
981       gint y,
982       gint width,
983       gint height,
984       gint tx,
985       gint ty)
986 {
987 	HTMLTextSlave *slave;
988 	HTMLText *owner;
989 	GtkHTMLFontStyle font_style;
990 	GdkRectangle paint;
991 
992 	/* printf ("slave draw %p\n", o); */
993 
994 	slave = HTML_TEXT_SLAVE (o);
995 	if (!html_object_intersect (o, &paint, x, y, width, height) || slave->posLen == 0)
996 		return;
997 
998 	owner = slave->owner;
999 	font_style = html_text_get_font_style (owner);
1000 
1001 	draw_text (slave, p, font_style, x, y, width, height, tx, ty);
1002 
1003 	if (HTML_OBJECT (owner)->draw_focused)
1004 		draw_focus (slave, p, tx, ty);
1005 }
1006 
1007 static gint
calc_min_width(HTMLObject * o,HTMLPainter * painter)1008 calc_min_width (HTMLObject *o,
1009                 HTMLPainter *painter)
1010 {
1011 	return 0;
1012 }
1013 
1014 static gint
calc_preferred_width(HTMLObject * o,HTMLPainter * painter)1015 calc_preferred_width (HTMLObject *o,
1016                       HTMLPainter *painter)
1017 {
1018 	return 0;
1019 }
1020 
1021 static const gchar *
get_url(HTMLObject * o,gint offset)1022 get_url (HTMLObject *o,
1023          gint offset)
1024 {
1025 	HTMLTextSlave *slave;
1026 
1027 	slave = HTML_TEXT_SLAVE (o);
1028 	return html_object_get_url (HTML_OBJECT (slave->owner), offset);
1029 }
1030 
1031 static gint
calc_offset(HTMLTextSlave * slave,HTMLPainter * painter,gint x)1032 calc_offset (HTMLTextSlave *slave,
1033              HTMLPainter *painter,
1034              gint x)
1035 {
1036 	GSList *cur, *glyphs = html_text_slave_get_glyph_items (slave, painter);
1037 	gint width = 0, offset = html_object_get_direction (HTML_OBJECT (slave->owner)) == HTML_DIRECTION_RTL ? slave->posLen : 0;
1038 	PangoItem *item = NULL;
1039 
1040 	if (glyphs) {
1041 		gint i = 0;
1042 
1043 		for (cur = glyphs; cur; cur = cur->next) {
1044 			HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) cur->data;
1045 
1046 			item = gi->glyph_item.item;
1047 
1048 			if (gi->widths == NULL) {
1049 
1050 				gi->widths = g_new (PangoGlyphUnit, item->num_chars);
1051 				html_tmp_fix_pango_glyph_string_get_logical_widths (gi->glyph_item.glyphs, slave->owner->text + item->offset, item->length,
1052 										    item->analysis.level, gi->widths);
1053 			}
1054 
1055 			if (item->analysis.level % 2 == 0) {
1056 				/* LTR */
1057 				for (i = 0; i < item->num_chars; i++) {
1058 					if (x < html_painter_pango_to_engine (painter, width + gi->widths[i] / 2))
1059 						goto done;
1060 					width += gi->widths[i];
1061 				}
1062 			} else {
1063 				/* RTL */
1064 				for (i = item->num_chars - 1; i >= 0; i--) {
1065 					if (x < html_painter_pango_to_engine (painter, width + gi->widths[i] / 2)) {
1066 						i++;
1067 						goto done;
1068 					}
1069 					width += gi->widths[i];
1070 				}
1071 			}
1072 		}
1073 
1074 	done:
1075 
1076 		if (cur)
1077 			offset = g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + item->offset) + i;
1078 		else {
1079 			offset = html_object_get_direction (HTML_OBJECT (slave->owner)) == HTML_DIRECTION_RTL ? 0 : slave->posLen;
1080 		}
1081 	}
1082 
1083 /*	printf ("offset %d\n", offset); */
1084 
1085 	return offset;
1086 }
1087 
1088 static guint
get_offset_for_pointer(HTMLTextSlave * slave,HTMLPainter * painter,gint x,gint y)1089 get_offset_for_pointer (HTMLTextSlave *slave,
1090                         HTMLPainter *painter,
1091                         gint x,
1092                         gint y)
1093 {
1094 	g_return_val_if_fail (slave != NULL, 0);
1095 
1096 	x -= HTML_OBJECT (slave)->x;
1097 
1098 	if (x <= 0)
1099 		return 0;
1100 
1101 	if (x >= HTML_OBJECT (slave)->width - 1)
1102 		return slave->posLen;
1103 
1104 	if (slave->posLen > 1)
1105 		return calc_offset (slave, painter, x);
1106 	else
1107 		return x > HTML_OBJECT (slave)->width / 2 ? 1 : 0;
1108 }
1109 
1110 static HTMLObject *
check_point(HTMLObject * self,HTMLPainter * painter,gint x,gint y,guint * offset_return,gboolean for_cursor)1111 check_point (HTMLObject *self,
1112              HTMLPainter *painter,
1113              gint x,
1114              gint y,
1115              guint *offset_return,
1116              gboolean for_cursor)
1117 {
1118 	if (x >= self->x
1119 	    && x < self->x + MAX (1, self->width)
1120 	    && y >= self->y - self->ascent
1121 	    && y < self->y + self->descent) {
1122 		HTMLTextSlave *slave = HTML_TEXT_SLAVE (self);
1123 
1124 		if (offset_return != NULL)
1125 			*offset_return = slave->posStart
1126 				+ get_offset_for_pointer (slave, painter, x, y);
1127 
1128 		return HTML_OBJECT (slave->owner);
1129 	}
1130 
1131 	return NULL;
1132 }
1133 
1134 static void
clear_glyph_items(HTMLTextSlave * slave)1135 clear_glyph_items (HTMLTextSlave *slave)
1136 {
1137 	if (slave->glyph_items) {
1138 		glyph_items_destroy (slave->glyph_items);
1139 		slave->glyph_items = NULL;
1140 	}
1141 }
1142 
1143 static void
destroy(HTMLObject * obj)1144 destroy (HTMLObject *obj)
1145 {
1146 	HTMLTextSlave *slave = HTML_TEXT_SLAVE (obj);
1147 
1148 	clear_glyph_items (slave);
1149 
1150 	HTML_OBJECT_CLASS (parent_class)->destroy (obj);
1151 }
1152 
1153 void
html_text_slave_type_init(void)1154 html_text_slave_type_init (void)
1155 {
1156 	html_text_slave_class_init (&html_text_slave_class, HTML_TYPE_TEXTSLAVE, sizeof (HTMLTextSlave));
1157 }
1158 
1159 void
html_text_slave_class_init(HTMLTextSlaveClass * klass,HTMLType type,guint object_size)1160 html_text_slave_class_init (HTMLTextSlaveClass *klass,
1161                             HTMLType type,
1162                             guint object_size)
1163 {
1164 	HTMLObjectClass *object_class;
1165 
1166 	object_class = HTML_OBJECT_CLASS (klass);
1167 
1168 	html_object_class_init (object_class, type, object_size);
1169 
1170 	object_class->select_range = select_range;
1171 	object_class->copy = copy;
1172 	object_class->destroy = destroy;
1173 	object_class->draw = draw;
1174 	object_class->calc_size = html_text_slave_real_calc_size;
1175 	object_class->fit_line = hts_fit_line;
1176 	object_class->calc_min_width = calc_min_width;
1177 	object_class->calc_preferred_width = calc_preferred_width;
1178 	object_class->get_url = get_url;
1179 	object_class->get_length = get_length;
1180 	object_class->check_point = check_point;
1181 
1182 	parent_class = &html_object_class;
1183 }
1184 
1185 void
html_text_slave_init(HTMLTextSlave * slave,HTMLTextSlaveClass * klass,HTMLText * owner,guint posStart,guint posLen)1186 html_text_slave_init (HTMLTextSlave *slave,
1187                       HTMLTextSlaveClass *klass,
1188                       HTMLText *owner,
1189                       guint posStart,
1190                       guint posLen)
1191 {
1192 	HTMLObject *object;
1193 
1194 	object = HTML_OBJECT (slave);
1195 
1196 	html_object_init (object, HTML_OBJECT_CLASS (klass));
1197 
1198 	object->ascent = HTML_OBJECT (owner)->ascent;
1199 	object->descent = HTML_OBJECT (owner)->descent;
1200 
1201 	slave->posStart   = posStart;
1202 	slave->posLen     = posLen;
1203 	slave->owner      = owner;
1204 	slave->charStart  = NULL;
1205 	slave->pi         = NULL;
1206 	slave->glyph_items = NULL;
1207 
1208 	/* text slaves have always min_width 0 */
1209 	object->min_width = 0;
1210 	object->change   &= ~HTML_CHANGE_MIN_WIDTH;
1211 }
1212 
1213 HTMLObject *
html_text_slave_new(HTMLText * owner,guint posStart,guint posLen)1214 html_text_slave_new (HTMLText *owner,
1215                      guint posStart,
1216                      guint posLen)
1217 {
1218 	HTMLTextSlave *slave;
1219 
1220 	slave = g_new (HTMLTextSlave, 1);
1221 	html_text_slave_init (slave, &html_text_slave_class, owner, posStart, posLen);
1222 
1223 	return HTML_OBJECT (slave);
1224 }
1225 
1226 static gboolean
html_text_slave_is_index_in_glyph(HTMLTextSlave * slave,HTMLTextSlave * next_slave,GSList * cur,gint index,PangoItem * item)1227 html_text_slave_is_index_in_glyph (HTMLTextSlave *slave,
1228                                    HTMLTextSlave *next_slave,
1229                                    GSList *cur,
1230                                    gint index,
1231                                    PangoItem *item)
1232 {
1233 	if (item->analysis.level % 2 == 0) {
1234 		/* LTR */
1235 		return item->offset <= index
1236 			&& (index < item->offset + item->length
1237 			    || (index == item->offset + item->length &&
1238 				(!cur->next
1239 				 || (!next_slave && slave->owner->text_bytes == item->offset + item->length)
1240 				 || (next_slave && html_text_slave_get_text (next_slave) - next_slave->owner->text == item->offset + item->length))));
1241 	} else {
1242 		/* RTL */
1243 		return index <= item->offset + item->length
1244 			&& (item->offset < index
1245 			    || (index == item->offset &&
1246 				(!cur->next
1247 				 || (!next_slave && slave->owner->text_bytes == item->offset + item->length)
1248 				 || (next_slave && html_text_slave_get_text (next_slave) - next_slave->owner->text == item->offset))));
1249 	}
1250 }
1251 
1252 static HTMLTextSlaveGlyphItem *
html_text_slave_get_glyph_item_at_offset(HTMLTextSlave * slave,HTMLPainter * painter,gint offset,HTMLTextSlaveGlyphItem ** prev,HTMLTextSlaveGlyphItem ** next,gint * start_width,gint * index_out)1253 html_text_slave_get_glyph_item_at_offset (HTMLTextSlave *slave,
1254                                           HTMLPainter *painter,
1255                                           gint offset,
1256                                           HTMLTextSlaveGlyphItem **prev,
1257                                           HTMLTextSlaveGlyphItem **next,
1258                                           gint *start_width,
1259                                           gint *index_out)
1260 {
1261 	HTMLTextSlaveGlyphItem *rv = NULL;
1262 	HTMLTextSlaveGlyphItem *prev_gi, *next_gi;
1263 	HTMLTextSlave *next_slave = HTML_OBJECT (slave)->next && HTML_IS_TEXT_SLAVE (HTML_OBJECT (slave)->next) ? HTML_TEXT_SLAVE (HTML_OBJECT (slave)->next) : NULL;
1264 	GSList *cur;
1265 	gint index;
1266 
1267 	next_gi = NULL;
1268 
1269 	index = g_utf8_offset_to_pointer (html_text_slave_get_text (slave), offset) - slave->owner->text;
1270 	if (index_out)
1271 		*index_out = index;
1272 
1273 	if (start_width)
1274 		*start_width = 0;
1275 
1276 	cur = html_text_slave_get_glyph_items (slave, painter);
1277 	if (cur) {
1278 		for (prev_gi = NULL; cur; cur = cur->next) {
1279 			HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) cur->data;
1280 
1281 			if (html_text_slave_is_index_in_glyph (slave, next_slave, cur, index, gi->glyph_item.item)) {
1282 				next_gi = cur->next ? (HTMLTextSlaveGlyphItem *) cur->next->data : NULL;
1283 				rv = gi;
1284 				break;
1285 			}
1286 
1287 			prev_gi = gi;
1288 			if (start_width) {
1289 				PangoRectangle log_rect;
1290 
1291 				pango_glyph_string_extents (gi->glyph_item.glyphs, gi->glyph_item.item->analysis.font, NULL, &log_rect);
1292 				(*start_width) += log_rect.width;
1293 			}
1294 		}
1295 	} else {
1296 		prev_gi = next_gi = NULL;
1297 	}
1298 
1299 	if (prev)
1300 		*prev = prev_gi;
1301 
1302 	if (next)
1303 		*next = next_gi;
1304 
1305 	return rv;
1306 }
1307 
1308 static gboolean
html_text_slave_gi_left_edge(HTMLTextSlave * slave,HTMLCursor * cursor,HTMLTextSlaveGlyphItem * gi)1309 html_text_slave_gi_left_edge (HTMLTextSlave *slave,
1310                               HTMLCursor *cursor,
1311                               HTMLTextSlaveGlyphItem *gi)
1312 {
1313 	gint old_offset = cursor->offset;
1314 
1315 	if (gi->glyph_item.item->analysis.level % 2 == 0) {
1316 		/* LTR */
1317 		cursor->offset = slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave),
1318 									     slave->owner->text + gi->glyph_item.item->offset);
1319 		cursor->position += cursor->offset - old_offset;
1320 	} else {
1321 		/* RTL */
1322 		cursor->offset = slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave),
1323 									     slave->owner->text + gi->glyph_item.item->offset + gi->glyph_item.item->length);
1324 		cursor->position += cursor->offset - old_offset;
1325 	}
1326 
1327 	return TRUE;
1328 }
1329 
1330 static gboolean
html_text_slave_gi_right_edge(HTMLTextSlave * slave,HTMLCursor * cursor,HTMLTextSlaveGlyphItem * gi)1331 html_text_slave_gi_right_edge (HTMLTextSlave *slave,
1332                                HTMLCursor *cursor,
1333                                HTMLTextSlaveGlyphItem *gi)
1334 {
1335 	gint old_offset = cursor->offset;
1336 
1337 	if (gi->glyph_item.item->analysis.level % 2 == 0) {
1338 		/* LTR */
1339 		cursor->offset = slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave),
1340 									     slave->owner->text + gi->glyph_item.item->offset + gi->glyph_item.item->length);
1341 		cursor->position += cursor->offset - old_offset;
1342 	} else {
1343 		/* RTL */
1344 		cursor->offset = slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave),
1345 									     slave->owner->text + gi->glyph_item.item->offset);
1346 		cursor->position += cursor->offset - old_offset;
1347 	}
1348 
1349 	return TRUE;
1350 }
1351 
1352 static gboolean
html_text_slave_cursor_right_one(HTMLTextSlave * slave,HTMLPainter * painter,HTMLCursor * cursor)1353 html_text_slave_cursor_right_one (HTMLTextSlave *slave,
1354                                   HTMLPainter *painter,
1355                                   HTMLCursor *cursor)
1356 {
1357 	HTMLTextSlaveGlyphItem *prev, *next;
1358 	gint index;
1359 	HTMLTextSlaveGlyphItem *gi = html_text_slave_get_glyph_item_at_offset (slave, painter, cursor->offset - slave->posStart, &prev, &next, NULL, &index);
1360 
1361 	if (!gi)
1362 		return FALSE;
1363 
1364 	if (gi->glyph_item.item->analysis.level % 2 == 0) {
1365 		/* LTR */
1366 		if (index < gi->glyph_item.item->offset + gi->glyph_item.item->length) {
1367 			cursor->offset++;
1368 			cursor->position++;
1369 
1370 			return TRUE;
1371 		}
1372 	} else {
1373 		/* RTL */
1374 		if (index > gi->glyph_item.item->offset && index <= gi->glyph_item.item->offset + gi->glyph_item.item->length) {
1375 			cursor->offset--;
1376 			cursor->position--;
1377 
1378 			return TRUE;
1379 		}
1380 	}
1381 
1382 	if (next) {
1383 		if (html_text_slave_gi_left_edge (slave, cursor, next)) {
1384 			if (next->glyph_item.item->analysis.level % 2 == 0) {
1385 				/* LTR */
1386 				cursor->offset++;
1387 				cursor->position++;
1388 			} else {
1389 				/* RTL */
1390 				cursor->offset--;
1391 				cursor->position--;
1392 			}
1393 
1394 			return TRUE;
1395 		}
1396 	}
1397 
1398 	return FALSE;
1399 }
1400 
1401 gboolean
html_text_slave_cursor_right(HTMLTextSlave * slave,HTMLPainter * painter,HTMLCursor * cursor)1402 html_text_slave_cursor_right (HTMLTextSlave *slave,
1403                               HTMLPainter *painter,
1404                               HTMLCursor *cursor)
1405 {
1406 	HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter);
1407 	gboolean step_success;
1408 
1409 	do
1410 		step_success = html_text_slave_cursor_right_one (slave, painter, cursor);
1411 	while (step_success && !pi->attrs[cursor->offset].is_cursor_position);
1412 
1413 	return step_success;
1414 }
1415 
1416 static gboolean
html_text_slave_cursor_left_one(HTMLTextSlave * slave,HTMLPainter * painter,HTMLCursor * cursor)1417 html_text_slave_cursor_left_one (HTMLTextSlave *slave,
1418                                  HTMLPainter *painter,
1419                                  HTMLCursor *cursor)
1420 {
1421 	HTMLTextSlaveGlyphItem *prev, *next;
1422 	gint index;
1423 	HTMLObject *prev_obj = HTML_OBJECT (slave->owner)->prev;
1424 	HTMLTextSlaveGlyphItem *gi = html_text_slave_get_glyph_item_at_offset (slave, painter, cursor->offset - slave->posStart, &prev, &next, NULL, &index);
1425 
1426 /*	printf ("gi: %p item num chars: %d\n", gi, gi ? gi->glyph_item.item->num_chars : -1); */
1427 
1428 	if (!gi)
1429 		return FALSE;
1430 
1431 	if (gi->glyph_item.item->analysis.level % 2 == 0) {
1432 		/* LTR */
1433 		if (index - gi->glyph_item.item->offset > 1 || (!prev && !prev_obj && index - gi->glyph_item.item->offset > 0)) {
1434 			cursor->offset--;
1435 			cursor->position--;
1436 
1437 			return TRUE;
1438 		}
1439 	} else {
1440 		/* RTL */
1441 		if (index < gi->glyph_item.item->offset + gi->glyph_item.item->length) {
1442 			cursor->offset++;
1443 			cursor->position++;
1444 
1445 			return TRUE;
1446 		}
1447 	}
1448 
1449 	if (prev) {
1450 		if (html_text_slave_gi_right_edge (slave, cursor, prev)) {
1451 			if (prev->glyph_item.item->analysis.level % 2 == 0) {
1452 				/* LTR */
1453 				if (index - gi->glyph_item.item->offset == 0) {
1454 					cursor->offset--;
1455 					cursor->position--;
1456 				}
1457 			} else {
1458 				/* RTL */
1459 				cursor->offset++;
1460 				cursor->position++;
1461 			}
1462 
1463 			return TRUE;
1464 		}
1465 	}
1466 
1467 	return FALSE;
1468 }
1469 
1470 gboolean
html_text_slave_cursor_left(HTMLTextSlave * slave,HTMLPainter * painter,HTMLCursor * cursor)1471 html_text_slave_cursor_left (HTMLTextSlave *slave,
1472                              HTMLPainter *painter,
1473                              HTMLCursor *cursor)
1474 {
1475 	HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter);
1476 	gboolean step_success;
1477 
1478 	do
1479 		step_success = html_text_slave_cursor_left_one (slave, painter, cursor);
1480 	while (step_success && !pi->attrs[cursor->offset].is_cursor_position);
1481 
1482 	return step_success;
1483 }
1484 
1485 static gboolean
html_text_slave_get_left_edge(HTMLTextSlave * slave,HTMLPainter * painter,HTMLCursor * cursor)1486 html_text_slave_get_left_edge (HTMLTextSlave *slave,
1487                                HTMLPainter *painter,
1488                                HTMLCursor *cursor)
1489 {
1490 	HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter);
1491 	gint old_offset = cursor->offset;
1492 	gint old_position = cursor->position;
1493 
1494 	cursor->offset = html_text_slave_get_left_edge_offset (slave, painter);
1495 
1496 	if (pi->attrs[cursor->offset].is_cursor_position && old_offset != cursor->offset)
1497 		return TRUE;
1498 	else {
1499 		if (html_text_slave_cursor_right (slave, painter, cursor)) {
1500 			/* we should preserve position here as caller function correct position themselves */
1501 			cursor->position = old_position;
1502 			return TRUE;
1503 		} else
1504 			return FALSE;
1505 	}
1506 }
1507 
1508 static gboolean
html_text_slave_get_right_edge(HTMLTextSlave * slave,HTMLPainter * painter,HTMLCursor * cursor)1509 html_text_slave_get_right_edge (HTMLTextSlave *slave,
1510                                 HTMLPainter *painter,
1511                                 HTMLCursor *cursor)
1512 {
1513 	HTMLTextPangoInfo *pi = html_text_get_pango_info (slave->owner, painter);
1514 	gint old_offset = cursor->offset;
1515 	gint old_position = cursor->position;
1516 
1517 	cursor->offset = html_text_slave_get_right_edge_offset (slave, painter);
1518 
1519 	if (pi->attrs[cursor->offset].is_cursor_position && old_offset != cursor->offset)
1520 		return TRUE;
1521 	else {
1522 		if (html_text_slave_cursor_left (slave, painter, cursor)) {
1523 			/* we should preserve position here as caller function correct position themselves */
1524 			cursor->position = old_position;
1525 			return TRUE;
1526 		} else
1527 			return FALSE;
1528 	}
1529 }
1530 
1531 gboolean
html_text_slave_cursor_head(HTMLTextSlave * slave,HTMLCursor * cursor,HTMLPainter * painter)1532 html_text_slave_cursor_head (HTMLTextSlave *slave,
1533                              HTMLCursor *cursor,
1534                              HTMLPainter *painter)
1535 {
1536 	if (html_text_slave_get_glyph_items (slave, painter)) {
1537 		cursor->object = HTML_OBJECT (slave->owner);
1538 
1539 		if (html_text_get_pango_direction (slave->owner) != PANGO_DIRECTION_RTL) {
1540 			/* LTR */
1541 			return html_text_slave_get_left_edge (slave, painter, cursor);
1542 		} else {
1543 			/* RTL */
1544 			return html_text_slave_get_right_edge (slave, painter, cursor);
1545 		}
1546 	}
1547 
1548 	return FALSE;
1549 }
1550 
1551 gboolean
html_text_slave_cursor_tail(HTMLTextSlave * slave,HTMLCursor * cursor,HTMLPainter * painter)1552 html_text_slave_cursor_tail (HTMLTextSlave *slave,
1553                              HTMLCursor *cursor,
1554                              HTMLPainter *painter)
1555 {
1556 	if (html_text_slave_get_glyph_items (slave, painter)) {
1557 		cursor->object = HTML_OBJECT (slave->owner);
1558 
1559 		if (html_text_get_pango_direction (slave->owner) != PANGO_DIRECTION_RTL) {
1560 			/* LTR */
1561 			return html_text_slave_get_right_edge (slave, painter, cursor);
1562 		} else {
1563 			/* RTL */
1564 			return html_text_slave_get_left_edge (slave, painter, cursor);
1565 		}
1566 	}
1567 
1568 	return FALSE;
1569 }
1570 
1571 void
html_text_slave_get_cursor_base(HTMLTextSlave * slave,HTMLPainter * painter,guint offset,gint * x,gint * y)1572 html_text_slave_get_cursor_base (HTMLTextSlave *slave,
1573                                  HTMLPainter *painter,
1574                                  guint offset,
1575                                  gint *x,
1576                                  gint *y)
1577 {
1578 	HTMLTextSlaveGlyphItem *gi;
1579 	gint index, start_width;
1580 
1581 	html_object_calc_abs_position (HTML_OBJECT (slave), x, y);
1582 
1583 	gi = html_text_slave_get_glyph_item_at_offset (slave, painter, (gint) offset, NULL, NULL, &start_width, &index);
1584 
1585 /*	printf ("gi: %p index: %d start_width: %d item indexes %d %d\n", */
1586 /*		gi, index, start_width, gi ? gi->glyph_item.item->offset : -1, */
1587 /*		gi ? gi->glyph_item.item->offset + gi->glyph_item.item->length : -1); */
1588 
1589 	if (gi) {
1590 		gint start_x;
1591 
1592 		if (calc_glyph_range_size (slave->owner, &gi->glyph_item, index, index, &start_x, NULL, NULL, NULL) && x) {
1593 /*			printf ("start_width: %d start_x: %d\n", start_width, start_x); */
1594 			*x += html_painter_pango_to_engine (painter, start_width + start_x);
1595 		}
1596 	}
1597 }
1598 
1599 gint
html_text_slave_get_left_edge_offset(HTMLTextSlave * slave,HTMLPainter * painter)1600 html_text_slave_get_left_edge_offset (HTMLTextSlave *slave,
1601                                       HTMLPainter *painter)
1602 {
1603 	GSList *gis = html_text_slave_get_glyph_items (slave, painter);
1604 
1605 	if (gis) {
1606 		HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) gis->data;
1607 
1608 		if (gi->glyph_item.item->analysis.level % 2 == 0) {
1609 			/* LTR */
1610 			return slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset);
1611 		} else {
1612 			/* RTL */
1613 			return slave->posStart + MIN (slave->posLen, g_utf8_pointer_to_offset (html_text_slave_get_text (slave),
1614 											       slave->owner->text
1615 											       + gi->glyph_item.item->offset + gi->glyph_item.item->length));
1616 		}
1617 	} else {
1618 		if (slave->owner->text_len > 0)
1619 			g_warning ("html_text_slave_get_left_edge_offset failed");
1620 
1621 		return 0;
1622 	}
1623 }
1624 
1625 gint
html_text_slave_get_right_edge_offset(HTMLTextSlave * slave,HTMLPainter * painter)1626 html_text_slave_get_right_edge_offset (HTMLTextSlave *slave,
1627                                        HTMLPainter *painter)
1628 {
1629 	GSList *gis = html_text_slave_get_glyph_items (slave, painter);
1630 
1631 	if (gis) {
1632 		HTMLTextSlaveGlyphItem *gi = (HTMLTextSlaveGlyphItem *) g_slist_last (gis)->data;
1633 
1634 		if (gi->glyph_item.item->analysis.level % 2 == 0) {
1635 			/* LTR */
1636 			return slave->posStart + MIN (slave->posLen, g_utf8_pointer_to_offset (html_text_slave_get_text (slave),
1637 											       slave->owner->text
1638 											       + gi->glyph_item.item->offset + gi->glyph_item.item->length));
1639 		} else {
1640 			/* RTL */
1641 			return slave->posStart + g_utf8_pointer_to_offset (html_text_slave_get_text (slave), slave->owner->text + gi->glyph_item.item->offset);
1642 		}
1643 	} else {
1644 		if (slave->owner->text_len > 0)
1645 			g_warning ("html_text_slave_get_left_edge_offset failed");
1646 
1647 		return 0;
1648 	}
1649 }
1650