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, 2000 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 #include <sys/types.h>
28 #include <regex.h>
29 #include <math.h>
30 
31 #define PANGO_ENABLE_BACKEND /* Required to get PANGO_GLYPH_EMPTY */
32 
33 #include <pango/pango.h>
34 
35 #include "htmltext.h"
36 #include "htmlcolor.h"
37 #include "htmlcolorset.h"
38 #include "htmlcluealigned.h"
39 #include "htmlclueflow.h"
40 #include "htmlcursor.h"
41 #include "htmlgdkpainter.h"
42 #include "htmlplainpainter.h"
43 #include "htmlprinter.h"
44 #include "htmlengine.h"
45 #include "htmlengine-edit.h"
46 #include "htmlengine-edit-cut-and-paste.h"
47 #include "htmlengine-save.h"
48 #include "htmlentity.h"
49 #include "htmlsettings.h"
50 #include "htmltextslave.h"
51 #include "htmlundo.h"
52 
53 HTMLTextClass html_text_class;
54 static HTMLObjectClass *parent_class = NULL;
55 static const PangoAttrClass html_pango_attr_font_size_klass;
56 
57 #define HT_CLASS(x) HTML_TEXT_CLASS (HTML_OBJECT (x)->klass)
58 
59 #ifdef PANGO_GLYPH_EMPTY
60 #define EMPTY_GLYPH PANGO_GLYPH_EMPTY
61 #else
62 #define EMPTY_GLYPH 0
63 #endif
64 
65 static SpellError * spell_error_new         (guint off, guint len);
66 static void         spell_error_destroy     (SpellError *se);
67 static void         move_spell_errors       (GList *spell_errors, guint offset, gint delta);
68 static GList *      remove_spell_errors     (GList *spell_errors, guint offset, guint len);
69 static GList *      merge_spell_errors      (GList *se1, GList *se2);
70 static void         remove_text_slaves      (HTMLObject *self);
71 
72 /* void
73 debug_spell_errors (GList *se)
74 {
75 	for (; se; se = se->next)
76 		printf ("SE: %4d, %4d\n", ((SpellError *) se->data)->off, ((SpellError *) se->data)->len);
77 } */
78 
79 static inline gboolean
is_in_the_save_cluev(HTMLObject * text,HTMLObject * o)80 is_in_the_save_cluev (HTMLObject *text,
81                       HTMLObject *o)
82 {
83 	return html_object_nth_parent (o, 2) == html_object_nth_parent (text, 2);
84 }
85 
86 /* HTMLObject methods.  */
87 
88 HTMLTextPangoInfo *
html_text_pango_info_new(gint n)89 html_text_pango_info_new (gint n)
90 {
91 	HTMLTextPangoInfo *pi;
92 
93 	pi = g_new (HTMLTextPangoInfo, 1);
94 	pi->n = n;
95 	pi->entries = g_new0 (HTMLTextPangoInfoEntry, n);
96 	pi->attrs = NULL;
97 	pi->have_font = FALSE;
98 	pi->font_style = GTK_HTML_FONT_STYLE_DEFAULT;
99 	pi->face = NULL;
100 
101 	return pi;
102 }
103 
104 void
html_text_pango_info_destroy(HTMLTextPangoInfo * pi)105 html_text_pango_info_destroy (HTMLTextPangoInfo *pi)
106 {
107 	gint i;
108 
109 	for (i = 0; i < pi->n; i++) {
110 		pango_item_free (pi->entries[i].glyph_item.item);
111 		if (pi->entries[i].glyph_item.glyphs)
112 			pango_glyph_string_free (pi->entries[i].glyph_item.glyphs);
113 		g_free (pi->entries[i].widths);
114 	}
115 	g_free (pi->entries);
116 	g_free (pi->attrs);
117 	g_free (pi->face);
118 	g_free (pi);
119 }
120 
121 static void
pango_info_destroy(HTMLText * text)122 pango_info_destroy (HTMLText *text)
123 {
124 	if (text->pi) {
125 		html_text_pango_info_destroy (text->pi);
126 		text->pi = NULL;
127 	}
128 }
129 
130 static void
free_links(GSList * list)131 free_links (GSList *list)
132 {
133 	if (list) {
134 		GSList *l;
135 
136 		for (l = list; l; l = l->next)
137 			html_link_free ((Link *) l->data);
138 		g_slist_free (list);
139 	}
140 }
141 
142 void
html_text_free_attrs(GSList * attrs)143 html_text_free_attrs (GSList *attrs)
144 {
145 	if (attrs) {
146 		GSList *l;
147 
148 		for (l = attrs; l; l = l->next)
149 			pango_attribute_destroy ((PangoAttribute *) l->data);
150 		g_slist_free (attrs);
151 	}
152 }
153 
154 static void
copy(HTMLObject * s,HTMLObject * d)155 copy (HTMLObject *s,
156       HTMLObject *d)
157 {
158 	HTMLText *src  = HTML_TEXT (s);
159 	HTMLText *dest = HTML_TEXT (d);
160 	GList *cur;
161 	GSList *csl;
162 
163 	(* HTML_OBJECT_CLASS (parent_class)->copy) (s, d);
164 
165 	dest->text = g_strdup (src->text);
166 	dest->text_len      = src->text_len;
167 	dest->text_bytes    = src->text_bytes;
168 	dest->font_style    = src->font_style;
169 	dest->face          = g_strdup (src->face);
170 	dest->color         = src->color;
171 	dest->select_start  = 0;
172 	dest->select_length = 0;
173 	dest->attr_list     = pango_attr_list_copy (src->attr_list);
174 	dest->extra_attr_list = src->extra_attr_list ? pango_attr_list_copy (src->extra_attr_list) : NULL;
175 
176 	html_color_ref (dest->color);
177 
178 	dest->spell_errors = g_list_copy (src->spell_errors);
179 	cur = dest->spell_errors;
180 	while (cur) {
181 		SpellError *se = (SpellError *) cur->data;
182 		cur->data = spell_error_new (se->off, se->len);
183 		cur = cur->next;
184 	}
185 
186 	dest->links = g_slist_copy (src->links);
187 
188 	for (csl = dest->links; csl; csl = csl->next)
189 		csl->data = html_link_dup ((Link *) csl->data);
190 
191 	dest->pi = NULL;
192 	dest->direction = src->direction;
193 }
194 
195 /* static void
196 debug_word_width (HTMLText *t)
197 {
198 	guint i;
199  *
200 	printf ("words: %d | ", t->words);
201 	for (i = 0; i < t->words; i++)
202 		printf ("%d ", t->word_width[i]);
203 	printf ("\n");
204 }
205  *
206 static void
207 word_get_position (HTMLText *text,
208  *                 guint off,
209  *                 guint *word_out,
210  *                 guint *left_out,
211  *                 guint *right_out)
212 {
213 	const gchar *s, *ls;
214 	guint coff, loff;
215  *
216 	coff      = 0;
217 	*word_out = 0;
218 	s         = text->text;
219 	do {
220 		ls    = s;
221 		loff  = coff;
222 		s     = strchr (s, ' ');
223 		coff += s ? g_utf8_pointer_to_offset (ls, s) : g_utf8_strlen (ls, -1);
224 		(*word_out) ++;
225 		if (s)
226 			s++;
227 	} while (s && coff < off);
228  *
229 	*left_out  = off - loff;
230 	*right_out = coff - off;
231  *
232 	printf ("get position w: %d l: %d r: %d\n", *word_out, *left_out, *right_out);
233 } */
234 
235 static gboolean
cut_attr_list_filter(PangoAttribute * attr,gpointer data)236 cut_attr_list_filter (PangoAttribute *attr,
237                       gpointer data)
238 {
239 	PangoAttribute *range = (PangoAttribute *) data;
240 	gint delta;
241 
242 	if (attr->start_index >= range->start_index && attr->end_index <= range->end_index)
243 		return TRUE;
244 
245 	delta = range->end_index - range->start_index;
246 	if (attr->start_index > range->end_index) {
247 		attr->start_index -= delta;
248 		attr->end_index -= delta;
249 	} else if (attr->start_index > range->start_index) {
250 		attr->start_index = range->start_index;
251 		attr->end_index -= delta;
252 		if (attr->end_index <= attr->start_index)
253 			return TRUE;
254 	} else if (attr->end_index >= range->end_index)
255 		attr->end_index -= delta;
256 	else if (attr->end_index >= range->start_index)
257 		attr->end_index = range->start_index;
258 
259 	return FALSE;
260 }
261 
262 static void
cut_attr_list_list(PangoAttrList * attr_list,gint begin_index,gint end_index)263 cut_attr_list_list (PangoAttrList *attr_list,
264                     gint begin_index,
265                     gint end_index)
266 {
267 	PangoAttrList *removed;
268 	PangoAttribute range;
269 
270 	range.start_index = begin_index;
271 	range.end_index = end_index;
272 
273 	removed = pango_attr_list_filter (attr_list, cut_attr_list_filter, &range);
274 	if (removed)
275 		pango_attr_list_unref (removed);
276 }
277 
278 static void
cut_attr_list(HTMLText * text,gint begin_index,gint end_index)279 cut_attr_list (HTMLText *text,
280                gint begin_index,
281                gint end_index)
282 {
283 	cut_attr_list_list (text->attr_list, begin_index, end_index);
284 	if (text->extra_attr_list)
285 		cut_attr_list_list (text->extra_attr_list, begin_index, end_index);
286 }
287 
288 static void
cut_links_full(HTMLText * text,gint start_offset,gint end_offset,gint start_index,gint end_index,gint shift_offset,gint shift_index)289 cut_links_full (HTMLText *text,
290                 gint start_offset,
291                 gint end_offset,
292                 gint start_index,
293                 gint end_index,
294                 gint shift_offset,
295                 gint shift_index)
296 {
297 	GSList *l, *next;
298 	Link *link;
299 
300 	for (l = text->links; l; l = next) {
301 		next = l->next;
302 		link = (Link *) l->data;
303 
304 		if (start_offset <= link->start_offset && link->end_offset <= end_offset) {
305 			html_link_free (link);
306 			text->links = g_slist_delete_link (text->links, l);
307 		} else if (end_offset <= link->start_offset) {
308 			link->start_offset -= shift_offset;
309 			link->start_index -= shift_index;
310 			link->end_offset -= shift_offset;
311 			link->end_index -= shift_index;
312 		} else if (start_offset <= link->start_offset)  {
313 			link->start_offset = end_offset - shift_offset;
314 			link->end_offset -= shift_offset;
315 			link->start_index = end_index - shift_index;
316 			link->end_index -= shift_index;
317 		} else if (end_offset <= link->end_offset) {
318 			if (shift_offset > 0) {
319 				link->end_offset -= shift_offset;
320 				link->end_index -= shift_index;
321 			} else {
322 				if (link->end_offset == end_offset) {
323 					link->end_offset = start_offset;
324 					link->end_index = start_index;
325 				} else if (link->start_offset == start_offset) {
326 					link->start_offset = end_offset;
327 					link->start_index = end_index;
328 				} else {
329 					Link *dup = html_link_dup (link);
330 
331 					link->start_offset = end_offset;
332 					link->start_index = end_index;
333 					dup->end_offset = start_offset;
334 					dup->end_index = start_index;
335 
336 					l = g_slist_prepend (l, dup);
337 					next = l->next->next;
338 				}
339 			}
340 		} else if (start_offset < link->end_offset) {
341 			link->end_offset = start_offset;
342 			link->end_index = start_index;
343 		}
344 	}
345 }
346 
347 static void
cut_links(HTMLText * text,gint start_offset,gint end_offset,gint start_index,gint end_index)348 cut_links (HTMLText *text,
349            gint start_offset,
350            gint end_offset,
351            gint start_index,
352            gint end_index)
353 {
354 	cut_links_full (text, start_offset, end_offset, start_index, end_index, end_offset - start_offset, end_index - start_index);
355 }
356 
357 HTMLObject *
html_text_op_copy_helper(HTMLText * text,GList * from,GList * to,guint * len)358 html_text_op_copy_helper (HTMLText *text,
359                           GList *from,
360                           GList *to,
361                           guint *len)
362 {
363 	HTMLObject *rv;
364 	HTMLText *rvt;
365 	gchar *tail, *nt;
366 	gint begin, end, begin_index, end_index;
367 
368 	begin = (from) ? GPOINTER_TO_INT (from->data) : 0;
369 	end   = (to)   ? GPOINTER_TO_INT (to->data)   : text->text_len;
370 
371 	tail = html_text_get_text (text, end);
372 	begin_index = html_text_get_index (text, begin);
373 	end_index = tail - text->text;
374 
375 	*len += end - begin;
376 
377 	rv = html_object_dup (HTML_OBJECT (text));
378 	rvt = HTML_TEXT (rv);
379 	rvt->text_len = end - begin;
380 	rvt->text_bytes = end_index - begin_index;
381 	nt = g_strndup (rvt->text + begin_index, rvt->text_bytes);
382 	g_free (rvt->text);
383 	rvt->text = nt;
384 
385 	rvt->spell_errors = remove_spell_errors (rvt->spell_errors, 0, begin);
386 	rvt->spell_errors = remove_spell_errors (rvt->spell_errors, end, text->text_len - end);
387 
388 	if (end_index < text->text_bytes)
389 		cut_attr_list (rvt, end_index, text->text_bytes);
390 	if (begin_index > 0)
391 		cut_attr_list (rvt, 0, begin_index);
392 	if (end < text->text_len)
393 		cut_links (rvt, end, text->text_len, end_index, text->text_bytes);
394 	if (begin > 0)
395 		cut_links (rvt, 0, begin, 0, begin_index);
396 
397 	return rv;
398 }
399 
400 HTMLObject *
html_text_op_cut_helper(HTMLText * text,HTMLEngine * e,GList * from,GList * to,GList * left,GList * right,guint * len)401 html_text_op_cut_helper (HTMLText *text,
402                          HTMLEngine *e,
403                          GList *from,
404                          GList *to,
405                          GList *left,
406                          GList *right,
407                          guint *len)
408 {
409 	HTMLObject *rv;
410 	HTMLText *rvt;
411 	gint begin, end;
412 
413 	begin = (from) ? GPOINTER_TO_INT (from->data) : 0;
414 	end   = (to)   ? GPOINTER_TO_INT (to->data)   : text->text_len;
415 
416 	g_assert (begin <= end);
417 	g_assert (end <= text->text_len);
418 
419 	/* printf ("before cut '%s'\n", text->text);
420 	 * debug_word_width (text); */
421 
422 	remove_text_slaves (HTML_OBJECT (text));
423 	if (!html_object_could_remove_whole (HTML_OBJECT (text), from, to, left, right) || begin || end < text->text_len) {
424 		gchar *nt, *tail;
425 		gint begin_index, end_index;
426 
427 		if (begin == end)
428 			return HTML_OBJECT (html_text_new_with_len ("", 0, text->font_style, text->color));
429 
430 		rv = html_object_dup (HTML_OBJECT (text));
431 		rvt = HTML_TEXT (rv);
432 
433 		tail = html_text_get_text (text, end);
434 		begin_index = html_text_get_index (text, begin);
435 		end_index = tail - text->text;
436 		text->text_bytes -= tail - (text->text + begin_index);
437 		text->text[begin_index] = 0;
438 		cut_attr_list (text, begin_index, end_index);
439 		if (end_index < rvt->text_bytes)
440 			cut_attr_list (rvt, end_index, rvt->text_bytes);
441 		if (begin_index > 0)
442 			cut_attr_list (rvt, 0, begin_index);
443 		cut_links (text, begin, end, begin_index, end_index);
444 		if (end < rvt->text_len)
445 			cut_links (rvt, end, rvt->text_len, end_index, rvt->text_bytes);
446 		if (begin > 0)
447 			cut_links (rvt, 0, begin, 0, begin_index);
448 		nt = g_strconcat (text->text, tail, NULL);
449 		g_free (text->text);
450 
451 		rvt->spell_errors = remove_spell_errors (rvt->spell_errors, 0, begin);
452 		rvt->spell_errors = remove_spell_errors (rvt->spell_errors, end, text->text_len - end);
453 		move_spell_errors (rvt->spell_errors, begin, -begin);
454 
455 		text->text = nt;
456 		text->text_len -= end - begin;
457 		*len           += end - begin;
458 
459 		nt = g_strndup (rvt->text + begin_index, end_index - begin_index);
460 		g_free (rvt->text);
461 		rvt->text = nt;
462 		rvt->text_len = end - begin;
463 		rvt->text_bytes = end_index - begin_index;
464 
465 		text->spell_errors = remove_spell_errors (text->spell_errors, begin, end - begin);
466 		move_spell_errors (text->spell_errors, end, - (end - begin));
467 
468 		html_text_convert_nbsp (text, TRUE);
469 		html_text_convert_nbsp (rvt, TRUE);
470 		pango_info_destroy (text);
471 	} else {
472 		text->spell_errors = remove_spell_errors (text->spell_errors, 0, text->text_len);
473 		html_object_move_cursor_before_remove (HTML_OBJECT (text), e);
474 		html_object_change_set (HTML_OBJECT (text)->parent, HTML_CHANGE_ALL_CALC);
475 		/* force parent redraw */
476 		HTML_OBJECT (text)->parent->width = 0;
477 		html_object_remove_child (HTML_OBJECT (text)->parent, HTML_OBJECT (text));
478 
479 		rv    = HTML_OBJECT (text);
480 		*len += text->text_len;
481 	}
482 
483 	html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL_CALC);
484 
485 	/* printf ("after cut '%s'\n", text->text);
486 	 * debug_word_width (text); */
487 
488 	return rv;
489 }
490 
491 static HTMLObject *
op_copy(HTMLObject * self,HTMLObject * parent,HTMLEngine * e,GList * from,GList * to,guint * len)492 op_copy (HTMLObject *self,
493          HTMLObject *parent,
494          HTMLEngine *e,
495          GList *from,
496          GList *to,
497          guint *len)
498 {
499 	return html_text_op_copy_helper (HTML_TEXT (self), from, to, len);
500 }
501 
502 static HTMLObject *
op_cut(HTMLObject * self,HTMLEngine * e,GList * from,GList * to,GList * left,GList * right,guint * len)503 op_cut (HTMLObject *self,
504         HTMLEngine *e,
505         GList *from,
506         GList *to,
507         GList *left,
508         GList *right,
509         guint *len)
510 {
511 	return html_text_op_cut_helper (HTML_TEXT (self), e, from, to, left, right, len);
512 }
513 
514 static void
merge_links(HTMLText * t1,HTMLText * t2)515 merge_links (HTMLText *t1,
516              HTMLText *t2)
517 {
518 	Link *tail, *head;
519 	GSList *l;
520 
521 	if (t2->links) {
522 		for (l = t2->links; l; l = l->next) {
523 			Link *link = (Link *) l->data;
524 
525 			link->start_offset += t1->text_len;
526 			link->start_index += t1->text_bytes;
527 			link->end_offset += t1->text_len;
528 			link->end_index += t1->text_bytes;
529 		}
530 
531 		if (t1->links) {
532 			head = (Link *) t1->links->data;
533 			tail = (Link *) g_slist_last (t2->links)->data;
534 
535 			if (tail->start_offset == head->end_offset && html_link_equal (head, tail)) {
536 				tail->start_offset = head->start_offset;
537 				tail->start_index = head->start_index;
538 				html_link_free (head);
539 				t1->links = g_slist_delete_link (t1->links, t1->links);
540 			}
541 		}
542 
543 		t1->links = g_slist_concat (t2->links, t1->links);
544 		t2->links = NULL;
545 	}
546 }
547 
548 static gboolean
object_merge(HTMLObject * self,HTMLObject * with,HTMLEngine * e,GList ** left,GList ** right,HTMLCursor * cursor)549 object_merge (HTMLObject *self,
550               HTMLObject *with,
551               HTMLEngine *e,
552               GList **left,
553               GList **right,
554               HTMLCursor *cursor)
555 {
556 	HTMLText *t1, *t2;
557 	gchar *to_free;
558 
559 	t1 = HTML_TEXT (self);
560 	t2 = HTML_TEXT (with);
561 
562 	/* printf ("merge '%s' '%s'\n", t1->text, t2->text); */
563 
564 	/* merge_word_width (t1, t2, e->painter); */
565 
566 	if (e->cursor->object == with) {
567 		e->cursor->object  = self;
568 		e->cursor->offset += t1->text_len;
569 	}
570 
571 	/* printf ("--- before merge\n");
572 	 * debug_spell_errors (t1->spell_errors);
573 	 * printf ("---\n");
574 	 * debug_spell_errors (t2->spell_errors);
575 	 * printf ("---\n");
576 	*/
577 	move_spell_errors (t2->spell_errors, 0, t1->text_len);
578 	t1->spell_errors = merge_spell_errors (t1->spell_errors, t2->spell_errors);
579 	t2->spell_errors = NULL;
580 
581 	pango_attr_list_splice (t1->attr_list, t2->attr_list, t1->text_bytes, t2->text_bytes);
582 	if (t2->extra_attr_list) {
583 		if (!t1->extra_attr_list)
584 			t1->extra_attr_list = pango_attr_list_new ();
585 		pango_attr_list_splice (t1->extra_attr_list, t2->extra_attr_list, t1->text_bytes, t2->text_bytes);
586 	}
587 	merge_links (t1, t2);
588 
589 	to_free       = t1->text;
590 	t1->text      = g_strconcat (t1->text, t2->text, NULL);
591 	t1->text_len += t2->text_len;
592 	t1->text_bytes += t2->text_bytes;
593 	g_free (to_free);
594 	html_text_convert_nbsp (t1, TRUE);
595 	html_object_change_set (self, HTML_CHANGE_ALL_CALC);
596 	pango_info_destroy (t1);
597 	pango_info_destroy (t2);
598 
599 	/* html_text_request_word_width (t1, e->painter); */
600 	/* printf ("merged '%s'\n", t1->text);
601 	 * printf ("--- after merge\n");
602 	 * debug_spell_errors (t1->spell_errors);
603 	 * printf ("---\n"); */
604 
605 	return TRUE;
606 }
607 
608 static gboolean
split_attrs_filter_head(PangoAttribute * attr,gpointer data)609 split_attrs_filter_head (PangoAttribute *attr,
610                          gpointer data)
611 {
612 	gint index = GPOINTER_TO_INT (data);
613 
614 	if (attr->start_index >= index)
615 		return TRUE;
616 	else if (attr->end_index > index)
617 		attr->end_index = index;
618 
619 	return FALSE;
620 }
621 
622 static gboolean
split_attrs_filter_tail(PangoAttribute * attr,gpointer data)623 split_attrs_filter_tail (PangoAttribute *attr,
624                          gpointer data)
625 {
626 	gint index = GPOINTER_TO_INT (data);
627 
628 	if (attr->end_index <= index)
629 		return TRUE;
630 
631 	if (attr->start_index > index)
632 		attr->start_index -= index;
633 	else
634 		attr->start_index = 0;
635 	attr->end_index -= index;
636 
637 	return FALSE;
638 }
639 
640 static void
split_attrs(HTMLText * t1,HTMLText * t2,gint index)641 split_attrs (HTMLText *t1,
642              HTMLText *t2,
643              gint index)
644 {
645 	PangoAttrList *delete;
646 
647 	delete = pango_attr_list_filter (t1->attr_list, split_attrs_filter_head, GINT_TO_POINTER (index));
648 	if (delete)
649 		pango_attr_list_unref (delete);
650 	if (t1->extra_attr_list) {
651 		delete = pango_attr_list_filter (t1->extra_attr_list, split_attrs_filter_head, GINT_TO_POINTER (index));
652 		if (delete)
653 			pango_attr_list_unref (delete);
654 	}
655 	delete = pango_attr_list_filter (t2->attr_list, split_attrs_filter_tail, GINT_TO_POINTER (index));
656 	if (delete)
657 		pango_attr_list_unref (delete);
658 	if (t2->extra_attr_list) {
659 		delete = pango_attr_list_filter (t2->extra_attr_list, split_attrs_filter_tail, GINT_TO_POINTER (index));
660 		if (delete)
661 			pango_attr_list_unref (delete);
662 	}
663 }
664 
665 static void
split_links(HTMLText * t1,HTMLText * t2,gint offset,gint index)666 split_links (HTMLText *t1,
667              HTMLText *t2,
668              gint offset,
669              gint index)
670 {
671 	GSList *l, *prev = NULL;
672 
673 	for (l = t1->links; l; l = l->next) {
674 		Link *link = (Link *) l->data;
675 
676 		if (link->start_offset < offset) {
677 			if (link->end_offset > offset) {
678 				link->end_offset = offset;
679 				link->end_index = index;
680 			}
681 
682 			if (prev) {
683 				prev->next = NULL;
684 				free_links (t1->links);
685 			}
686 			t1->links = l;
687 			break;
688 		}
689 		prev = l;
690 
691 		if (!l->next) {
692 			free_links (t1->links);
693 			t1->links = NULL;
694 			break;
695 		}
696 	}
697 
698 	prev = NULL;
699 	for (l = t2->links; l; l = l->next) {
700 		Link *link = (Link *) l->data;
701 
702 		if (link->start_offset < offset) {
703 			if (link->end_offset > offset) {
704 				link->start_offset = offset;
705 				link->start_index = index;
706 				prev = l;
707 				l = l->next;
708 			}
709 			if (prev) {
710 				prev->next = NULL;
711 				free_links (l);
712 			} else {
713 				free_links (t2->links);
714 				t2->links = NULL;
715 			}
716 			break;
717 		}
718 		prev = l;
719 	}
720 
721 	for (l = t2->links; l; l = l->next) {
722 		Link *link = (Link *) l->data;
723 
724 		link->start_offset -= offset;
725 		link->start_index -= index;
726 		link->end_offset -= offset;
727 		link->end_index -= index;
728 	}
729 }
730 
731 static void
object_split(HTMLObject * self,HTMLEngine * e,HTMLObject * child,gint offset,gint level,GList ** left,GList ** right)732 object_split (HTMLObject *self,
733               HTMLEngine *e,
734               HTMLObject *child,
735               gint offset,
736               gint level,
737               GList **left,
738               GList **right)
739 {
740 	HTMLObject *dup, *prev;
741 	HTMLText *t1, *t2;
742 	gchar *tt;
743 	gint split_index;
744 
745 	g_assert (self->parent);
746 
747 	html_clue_remove_text_slaves (HTML_CLUE (self->parent));
748 
749 	t1              = HTML_TEXT (self);
750 	dup             = html_object_dup (self);
751 	tt              = t1->text;
752 	split_index     = html_text_get_index (t1, offset);
753 	t1->text        = g_strndup (tt, split_index);
754 	t1->text_len    = offset;
755 	t1->text_bytes  = split_index;
756 	g_free (tt);
757 	html_text_convert_nbsp (t1, TRUE);
758 
759 	t2              = HTML_TEXT (dup);
760 	tt              = t2->text;
761 	t2->text        = html_text_get_text (t2, offset);
762 	t2->text_len   -= offset;
763 	t2->text_bytes -= split_index;
764 	split_attrs (t1, t2, split_index);
765 	split_links (t1, t2, offset, split_index);
766 	if (!html_text_convert_nbsp (t2, FALSE))
767 		t2->text = g_strdup (t2->text);
768 	g_free (tt);
769 
770 	html_clue_append_after (HTML_CLUE (self->parent), dup, self);
771 
772 	prev = self->prev;
773 	if (t1->text_len == 0 && prev && html_object_merge (prev, self, e, NULL, NULL, NULL))
774 		self = prev;
775 
776 	if (t2->text_len == 0 && dup->next)
777 		html_object_merge (dup, dup->next, e, NULL, NULL, NULL);
778 
779 	/* printf ("--- before split offset %d dup len %d\n", offset, HTML_TEXT (dup)->text_len);
780 	 * debug_spell_errors (HTML_TEXT (self)->spell_errors); */
781 
782 	HTML_TEXT (self)->spell_errors = remove_spell_errors (HTML_TEXT (self)->spell_errors,
783 							      offset, HTML_TEXT (dup)->text_len);
784 	HTML_TEXT (dup)->spell_errors  = remove_spell_errors (HTML_TEXT (dup)->spell_errors,
785 							      0, HTML_TEXT (self)->text_len);
786 	move_spell_errors   (HTML_TEXT (dup)->spell_errors, 0, - HTML_TEXT (self)->text_len);
787 
788 	/* printf ("--- after split\n");
789 	 * printf ("left\n");
790 	 * debug_spell_errors (HTML_TEXT (self)->spell_errors);
791 	 * printf ("right\n");
792 	 * debug_spell_errors (HTML_TEXT (dup)->spell_errors);
793 	 * printf ("---\n");
794 	*/
795 
796 	*left  = g_list_prepend (*left, self);
797 	*right = g_list_prepend (*right, dup);
798 
799 	html_object_change_set (self, HTML_CHANGE_ALL_CALC);
800 	html_object_change_set (dup,  HTML_CHANGE_ALL_CALC);
801 
802 	pango_info_destroy (HTML_TEXT (self));
803 
804 	level--;
805 	if (level)
806 		html_object_split (self->parent, e, dup, 0, level, left, right);
807 }
808 
809 static gboolean
html_text_real_calc_size(HTMLObject * self,HTMLPainter * painter,GList ** changed_objs)810 html_text_real_calc_size (HTMLObject *self,
811                           HTMLPainter *painter,
812                           GList **changed_objs)
813 {
814 	self->width = 0;
815 	html_object_calc_preferred_width (self, painter);
816 
817 	return FALSE;
818 }
819 
820 static const gchar *
html_utf8_strnchr(const gchar * s,gchar c,gint len,gint * offset)821 html_utf8_strnchr (const gchar *s,
822                    gchar c,
823                    gint len,
824                    gint *offset)
825 {
826 	const gchar *res = NULL;
827 
828 	*offset = 0;
829 	while (s && *s && *offset < len) {
830 		if (*s == c) {
831 			res = s;
832 			break;
833 		}
834 		s = g_utf8_next_char (s);
835 		(*offset) ++;
836 	}
837 
838 	return res;
839 }
840 
841 gint
html_text_text_line_length(const gchar * text,gint * line_offset,guint len,gint * tabs)842 html_text_text_line_length (const gchar *text,
843                             gint *line_offset,
844                             guint len,
845                             gint *tabs)
846 {
847 	const gchar *tab, *found_tab;
848 	gint cl, l, skip, sum_skip;
849 
850 	/* printf ("lo: %d len: %d t: '%s'\n", line_offset, len, text); */
851 	if (tabs)
852 		*tabs = 0;
853 	l = 0;
854 	sum_skip = skip = 0;
855 	tab = text;
856 	while (tab && (found_tab = html_utf8_strnchr (tab, '\t', len - l, &cl)) && l < len) {
857 		l   += cl;
858 		if (l >= len)
859 			break;
860 		if (*line_offset != -1) {
861 			*line_offset  += cl;
862 			skip = 8 - (*line_offset % 8);
863 		}
864 		tab  = found_tab + 1;
865 
866 		*line_offset  += skip;
867 		if (*line_offset != -1)
868 			sum_skip += skip - 1;
869 		l++;
870 		if (tabs)
871 			(*tabs) ++;
872 	}
873 
874 	if (*line_offset != -1)
875 		(*line_offset) += len - l;
876 	/* printf ("ll: %d\n", len + sum_skip); */
877 
878 	return len + sum_skip;
879 }
880 
881 static guint
get_line_length(HTMLObject * self,HTMLPainter * p,gint line_offset)882 get_line_length (HTMLObject *self,
883                  HTMLPainter *p,
884                  gint line_offset)
885 {
886 	return html_clueflow_tabs (HTML_CLUEFLOW (self->parent), p)
887 		? html_text_text_line_length (HTML_TEXT (self)->text, &line_offset, HTML_TEXT (self)->text_len, NULL)
888 		: HTML_TEXT (self)->text_len;
889 }
890 
891 gint
html_text_get_line_offset(HTMLText * text,HTMLPainter * painter,gint offset)892 html_text_get_line_offset (HTMLText *text,
893                            HTMLPainter *painter,
894                            gint offset)
895 {
896 	gint line_offset = -1;
897 
898 	if (html_clueflow_tabs (HTML_CLUEFLOW (HTML_OBJECT (text)->parent), painter)) {
899 		line_offset = html_clueflow_get_line_offset (HTML_CLUEFLOW (HTML_OBJECT (text)->parent),
900 							     painter, HTML_OBJECT (text));
901 		if (offset) {
902 			gchar *s = text->text;
903 
904 			while (offset > 0 && s && *s) {
905 				if (*s == '\t')
906 					line_offset += 8 - (line_offset % 8);
907 				else
908 					line_offset++;
909 				s = g_utf8_next_char (s);
910 				offset--;
911 			}
912 		}
913 	}
914 
915 	return line_offset;
916 }
917 
918 gint
html_text_get_item_index(HTMLText * text,HTMLPainter * painter,gint offset,gint * item_offset)919 html_text_get_item_index (HTMLText *text,
920                           HTMLPainter *painter,
921                           gint offset,
922                           gint *item_offset)
923 {
924 	HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
925 	gint idx = 0;
926 
927 	if (pi->n > 0) {
928 		while (idx < pi->n - 1 && offset >= pi->entries[idx].glyph_item.item->num_chars) {
929 			offset -= pi->entries[idx].glyph_item.item->num_chars;
930 			idx++;
931 		}
932 
933 		*item_offset = offset;
934 	}
935 
936 	return idx;
937 }
938 
939 static void
update_asc_dsc(HTMLPainter * painter,PangoItem * item,gint * asc,gint * dsc)940 update_asc_dsc (HTMLPainter *painter,
941                 PangoItem *item,
942                 gint *asc,
943                 gint *dsc)
944 {
945 	PangoFontMetrics *pfm;
946 
947 	pfm = pango_font_get_metrics (item->analysis.font, item->analysis.language);
948 	if (asc)
949 		*asc = MAX (*asc, pango_font_metrics_get_ascent (pfm));
950 	if (dsc)
951 		*dsc = MAX (*dsc, pango_font_metrics_get_descent (pfm));
952 	pango_font_metrics_unref (pfm);
953 }
954 
955 static void
html_text_get_attr_list_list(PangoAttrList * get_attrs,PangoAttrList * attr_list,gint start_index,gint end_index)956 html_text_get_attr_list_list (PangoAttrList *get_attrs,
957                               PangoAttrList *attr_list,
958                               gint start_index,
959                               gint end_index)
960 {
961 	PangoAttrIterator *iter = pango_attr_list_get_iterator (attr_list);
962 
963 	if (iter) {
964 		do {
965 			gint begin, end;
966 
967 			pango_attr_iterator_range (iter, &begin, &end);
968 
969 			if (MAX (begin, start_index) < MIN (end, end_index)) {
970 				GSList *c, *l = pango_attr_iterator_get_attrs (iter);
971 
972 				for (c = l; c; c = c->next) {
973 					PangoAttribute *attr = (PangoAttribute *) c->data;
974 
975 					if (attr->start_index < start_index)
976 						attr->start_index = 0;
977 					else
978 						attr->start_index -= start_index;
979 
980 					if (attr->end_index > end_index)
981 						attr->end_index = end_index - start_index;
982 					else
983 						attr->end_index -= start_index;
984 
985 					c->data = NULL;
986 					pango_attr_list_insert (get_attrs, attr);
987 				}
988 				g_slist_free (l);
989 			}
990 		} while (pango_attr_iterator_next (iter));
991 
992 		pango_attr_iterator_destroy (iter);
993 	}
994 }
995 
996 PangoAttrList *
html_text_get_attr_list(HTMLText * text,gint start_index,gint end_index)997 html_text_get_attr_list (HTMLText *text,
998                          gint start_index,
999                          gint end_index)
1000 {
1001 	PangoAttrList *attrs = pango_attr_list_new ();
1002 
1003 	html_text_get_attr_list_list (attrs, text->attr_list, start_index, end_index);
1004 	if (text->extra_attr_list)
1005 		html_text_get_attr_list_list (attrs, text->extra_attr_list, start_index, end_index);
1006 
1007 	return attrs;
1008 }
1009 
1010 void
html_text_calc_text_size(HTMLText * t,HTMLPainter * painter,gint start_byte_offset,guint len,HTMLTextPangoInfo * pi,GList * glyphs,gint * line_offset,gint * width,gint * asc,gint * dsc)1011 html_text_calc_text_size (HTMLText *t,
1012                           HTMLPainter *painter,
1013                           gint start_byte_offset,
1014                           guint len,
1015                           HTMLTextPangoInfo *pi,
1016                           GList *glyphs,
1017                           gint *line_offset,
1018                           gint *width,
1019                           gint *asc,
1020                           gint *dsc)
1021 {
1022 		gchar *text = t->text + start_byte_offset;
1023 
1024 		html_painter_calc_entries_size (painter, text, len, pi, glyphs,
1025 						line_offset, width, asc, dsc);
1026 }
1027 
1028 gint
html_text_calc_part_width(HTMLText * text,HTMLPainter * painter,gchar * start,gint offset,gint len,gint * asc,gint * dsc)1029 html_text_calc_part_width (HTMLText *text,
1030                            HTMLPainter *painter,
1031                            gchar *start,
1032                            gint offset,
1033                            gint len,
1034                            gint *asc,
1035                            gint *dsc)
1036 {
1037 	gint idx, width = 0, line_offset;
1038 	gint ascent = 0, descent = 0; /* Quiet GCC */
1039 	gboolean need_ascent_descent = asc || dsc;
1040 	HTMLTextPangoInfo *pi;
1041 	PangoLanguage *language = NULL;
1042 	PangoFont *font = NULL;
1043 	gchar *s;
1044 
1045 	if (offset < 0)
1046 		return 0;
1047 
1048 	if (offset + len > text->text_len)
1049 		return 0;
1050 
1051 	if (need_ascent_descent) {
1052 		ascent = html_painter_engine_to_pango (painter,
1053 						       html_painter_get_space_asc (painter, html_text_get_font_style (text), text->face));
1054 		descent = html_painter_engine_to_pango (painter,
1055 							html_painter_get_space_dsc (painter, html_text_get_font_style (text), text->face));
1056 	}
1057 
1058 	if (text->text_len == 0 || len == 0)
1059 		goto out;
1060 
1061 	line_offset = html_text_get_line_offset (text, painter, offset);
1062 
1063 	if (start == NULL)
1064 		start = html_text_get_text (text, offset);
1065 
1066 	s = start;
1067 
1068 	pi = html_text_get_pango_info (text, painter);
1069 
1070 	idx = html_text_get_item_index (text, painter, offset, &offset);
1071 	if (need_ascent_descent) {
1072 		update_asc_dsc (painter, pi->entries[idx].glyph_item.item, &ascent, &descent);
1073 		font = pi->entries[idx].glyph_item.item->analysis.font;
1074 		language = pi->entries[idx].glyph_item.item->analysis.language;
1075 	}
1076 	while (len > 0) {
1077 		gint old_idx;
1078 
1079 		if (*s == '\t') {
1080 			gint skip = 8 - (line_offset % 8);
1081 			width += skip * pi->entries[idx].widths[offset];
1082 			line_offset += skip;
1083 		} else {
1084 			width += pi->entries[idx].widths[offset];
1085 			line_offset++;
1086 		}
1087 		len--;
1088 
1089 		old_idx = idx;
1090 		if (html_text_pi_forward (pi, &idx, &offset) && idx != old_idx)
1091 			if (len > 0 && (need_ascent_descent) && (pi->entries[idx].glyph_item.item->analysis.font != font
1092 								 || pi->entries[idx].glyph_item.item->analysis.language != language)) {
1093 				update_asc_dsc (painter, pi->entries[idx].glyph_item.item, &ascent, &descent);
1094 			}
1095 
1096 		s = g_utf8_next_char (s);
1097 	}
1098 
1099 out:
1100 	if (asc)
1101 		*asc = html_painter_pango_to_engine (painter, ascent);
1102 	if (dsc)
1103 		*dsc = html_painter_pango_to_engine (painter, descent);
1104 
1105 	return html_painter_pango_to_engine (painter, width);
1106 }
1107 
1108 static gint
calc_preferred_width(HTMLObject * self,HTMLPainter * painter)1109 calc_preferred_width (HTMLObject *self,
1110                       HTMLPainter *painter)
1111 {
1112 	HTMLText *text;
1113 	gint width;
1114 
1115 	text = HTML_TEXT (self);
1116 
1117 	width = html_text_calc_part_width (text, painter, text->text, 0, text->text_len, &self->ascent, &self->descent);
1118 	self->y = self->ascent;
1119 	if (html_clueflow_tabs (HTML_CLUEFLOW (self->parent), painter)) {
1120 		gint line_offset;
1121 		gint tabs;
1122 
1123 		line_offset = html_text_get_line_offset (text, painter, 0);
1124 		width += (html_text_text_line_length (text->text, &line_offset, text->text_len, &tabs) - text->text_len)*
1125 			html_painter_get_space_width (painter, html_text_get_font_style (text), text->face);
1126 	}
1127 
1128 	return MAX (1, width);
1129 }
1130 
1131 static void
remove_text_slaves(HTMLObject * self)1132 remove_text_slaves (HTMLObject *self)
1133 {
1134 	HTMLObject *next_obj;
1135 
1136 	/* Remove existing slaves */
1137 	next_obj = self->next;
1138 	while (next_obj != NULL
1139 	       && (HTML_OBJECT_TYPE (next_obj) == HTML_TYPE_TEXTSLAVE)) {
1140 		self->next = next_obj->next;
1141 		html_clue_remove (HTML_CLUE (next_obj->parent), next_obj);
1142 		html_object_destroy (next_obj);
1143 		next_obj = self->next;
1144 	}
1145 }
1146 
1147 static HTMLFitType
ht_fit_line(HTMLObject * o,HTMLPainter * painter,gboolean startOfLine,gboolean firstRun,gboolean next_to_floating,gint widthLeft)1148 ht_fit_line (HTMLObject *o,
1149              HTMLPainter *painter,
1150              gboolean startOfLine,
1151              gboolean firstRun,
1152              gboolean next_to_floating,
1153              gint widthLeft)
1154 {
1155 	HTMLText *text;
1156 	HTMLObject *text_slave;
1157 
1158 	text = HTML_TEXT (o);
1159 
1160 	remove_text_slaves (o);
1161 
1162 	/* Turn all text over to our slaves */
1163 	text_slave = html_text_slave_new (text, 0, HTML_TEXT (text)->text_len);
1164 	html_clue_append_after (HTML_CLUE (o->parent), text_slave, o);
1165 
1166 	return HTML_FIT_COMPLETE;
1167 }
1168 
1169 #if 0  /* No longer used? */
1170 static gint
1171 min_word_width_calc_tabs (HTMLText *text,
1172                           HTMLPainter *p,
1173                           gint idx,
1174                           gint *len)
1175 {
1176 	gchar *str, *end;
1177 	gint rv = 0, line_offset, wt, wl, i;
1178 	gint epos;
1179 	gboolean tab = FALSE;
1180 
1181 	if (!html_clueflow_tabs (HTML_CLUEFLOW (HTML_OBJECT (text)->parent), p))
1182 		return 0;
1183 
1184 	/* printf ("tabs %d\n", idx); */
1185 
1186 	str = text->text;
1187 	i = idx;
1188 	while (i > 0 && *str) {
1189 		if (*str == ' ')
1190 			i--;
1191 
1192 		str = g_utf8_next_char (str);
1193 	}
1194 
1195 	if (!*str)
1196 		return 0;
1197 
1198 	epos = 0;
1199 	end = str;
1200 	while (*end && *end != ' ') {
1201 		tab |= *end == '\t';
1202 
1203 		end = g_utf8_next_char (end);
1204 		epos++;
1205 	}
1206 
1207 	if (tab) {
1208 		line_offset = 0;
1209 
1210 		if (idx == 0) {
1211 			HTMLObject *prev;
1212 
1213 			prev = html_object_prev_not_slave (HTML_OBJECT (text));
1214 			if (prev && html_object_is_text (prev) /* FIXME-words && HTML_TEXT (prev)->words > 0 */) {
1215 				min_word_width_calc_tabs (HTML_TEXT (prev), p, /* FIXME-words HTML_TEXT (prev)->words - 1 */ HTML_TEXT (prev)->text_len - 1, &line_offset);
1216 				/* printf ("lo: %d\n", line_offset); */
1217 			}
1218 		}
1219 
1220 		wl = html_text_text_line_length (str, &line_offset, epos, &wt);
1221 	} else {
1222 		wl = epos;
1223 	}
1224 
1225 	rv = wl - epos;
1226 
1227 	if (len)
1228 		*len = wl;
1229 
1230 	/* printf ("tabs delta %d\n", rv); */
1231 	return rv;
1232 }
1233 #endif
1234 
1235 gint
html_text_pango_info_get_index(HTMLTextPangoInfo * pi,gint byte_offset,gint idx)1236 html_text_pango_info_get_index (HTMLTextPangoInfo *pi,
1237                                 gint byte_offset,
1238                                 gint idx)
1239 {
1240 	while (idx < pi->n && pi->entries[idx].glyph_item.item->offset + pi->entries[idx].glyph_item.item->length <= byte_offset)
1241 		idx++;
1242 
1243 	return idx;
1244 }
1245 
1246 static void
html_text_add_cite_color(PangoAttrList * attrs,HTMLText * text,HTMLClueFlow * flow,HTMLEngine * e)1247 html_text_add_cite_color (PangoAttrList *attrs,
1248                           HTMLText *text,
1249                           HTMLClueFlow *flow,
1250                           HTMLEngine *e)
1251 {
1252 	HTMLColor *cite_color = html_colorset_get_color (e->settings->color_set, HTMLCiteColor);
1253 
1254 	if (cite_color && flow->levels->len > 0 && flow->levels->data[0] == HTML_LIST_TYPE_BLOCKQUOTE_CITE) {
1255 		PangoAttribute *attr;
1256 
1257 		attr = pango_attr_foreground_new (cite_color->color.red, cite_color->color.green, cite_color->color.blue);
1258 		attr->start_index = 0;
1259 		attr->end_index = text->text_bytes;
1260 		pango_attr_list_change (attrs, attr);
1261 	}
1262 }
1263 
1264 void
html_text_remove_unwanted_line_breaks(gchar * s,gint len,PangoLogAttr * attrs)1265 html_text_remove_unwanted_line_breaks (gchar *s,
1266                                        gint len,
1267                                        PangoLogAttr *attrs)
1268 {
1269 	gint i;
1270 	gunichar last_uc = 0;
1271 
1272 	for (i = 0; i < len; i++) {
1273 		gunichar uc = g_utf8_get_char (s);
1274 
1275 		if (attrs[i].is_line_break) {
1276 			if (last_uc == '.' || last_uc == '/' ||
1277 			    last_uc == '-' || last_uc == '$' ||
1278 			    last_uc == '+' || last_uc == '?' ||
1279 			    last_uc == ')' ||
1280 			    last_uc == '}' ||
1281 			    last_uc == ']' ||
1282 			    last_uc == '>')
1283 				attrs[i].is_line_break = 0;
1284 			else if ((uc == '(' ||
1285 				  uc == '{' ||
1286 				  uc == '[' ||
1287 				  uc == '<'
1288 				  )
1289 				 && i > 0 && !attrs[i - 1].is_white)
1290 				attrs[i].is_line_break = 0;
1291 		}
1292 		s = g_utf8_next_char (s);
1293 		last_uc = uc;
1294 	}
1295 }
1296 
1297 PangoAttrList *
html_text_prepare_attrs(HTMLText * text,HTMLPainter * painter)1298 html_text_prepare_attrs (HTMLText *text,
1299                          HTMLPainter *painter)
1300 {
1301 	PangoAttrList *attrs;
1302 	HTMLClueFlow *flow = NULL;
1303 	HTMLEngine *e = NULL;
1304 	PangoAttribute *attr;
1305 
1306 	attrs = pango_attr_list_new ();
1307 
1308 	if (HTML_OBJECT (text)->parent && HTML_IS_CLUEFLOW (HTML_OBJECT (text)->parent))
1309 		flow = HTML_CLUEFLOW (HTML_OBJECT (text)->parent);
1310 
1311 	if (painter->widget && GTK_IS_HTML (painter->widget))
1312 		e = html_object_engine (HTML_OBJECT (text), GTK_HTML (painter->widget)->engine);
1313 
1314 	if (flow && e) {
1315 		html_text_add_cite_color (attrs, text, flow, e);
1316 	}
1317 
1318 	if (HTML_IS_PLAIN_PAINTER (painter)) {
1319 		attr = pango_attr_family_new (painter->font_manager.fixed.face ? painter->font_manager.fixed.face : "Monospace");
1320 		attr->start_index = 0;
1321 		attr->end_index = text->text_bytes;
1322 		pango_attr_list_insert (attrs, attr);
1323 		if (painter->font_manager.fix_size != painter->font_manager.var_size || fabs (painter->font_manager.magnification - 1.0) > 0.001) {
1324 			attr = pango_attr_size_new (painter->font_manager.fix_size * painter->font_manager.magnification);
1325 			attr->start_index = 0;
1326 			attr->end_index = text->text_bytes;
1327 			pango_attr_list_insert (attrs, attr);
1328 		}
1329 	} else {
1330 		if (fabs (painter->font_manager.magnification - 1.0) > 0.001) {
1331 			attr = pango_attr_size_new (painter->font_manager.var_size * painter->font_manager.magnification);
1332 			attr->start_index = 0;
1333 			attr->end_index = text->text_bytes;
1334 			pango_attr_list_insert (attrs, attr);
1335 		}
1336 		pango_attr_list_splice (attrs, text->attr_list, 0, 0);
1337 	}
1338 
1339 	if (text->extra_attr_list)
1340 		pango_attr_list_splice (attrs, text->extra_attr_list, 0, 0);
1341 	if (!HTML_IS_PLAIN_PAINTER (painter)) {
1342 		if (flow && e)
1343 			html_text_change_attrs (attrs, html_clueflow_get_default_font_style (flow), e, 0, text->text_bytes, TRUE);
1344 	}
1345 
1346 	if (text->links && e) {
1347 		HTMLColor *link_color;
1348 		GSList *l;
1349 
1350 		for (l = text->links; l; l = l->next) {
1351 			Link *link;
1352 
1353 			link = (Link *) l->data;
1354 
1355 			if (link->is_visited == FALSE)
1356 				link_color = html_colorset_get_color (e->settings->color_set, HTMLLinkColor);
1357 			else
1358 				link_color = html_colorset_get_color (e->settings->color_set, HTMLVLinkColor);
1359 			attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
1360 			attr->start_index = link->start_index;
1361 			attr->end_index = link->end_index;
1362 			pango_attr_list_change (attrs, attr);
1363 
1364 			attr = pango_attr_foreground_new (link_color->color.red, link_color->color.green, link_color->color.blue);
1365 			attr->start_index = link->start_index;
1366 			attr->end_index = link->end_index;
1367 			pango_attr_list_change (attrs, attr);
1368 		}
1369 	}
1370 
1371 	return attrs;
1372 }
1373 
1374 PangoDirection
html_text_get_pango_direction(HTMLText * text)1375 html_text_get_pango_direction (HTMLText *text)
1376 {
1377 	if (HTML_OBJECT (text)->change & HTML_CHANGE_RECALC_PI)
1378 		return pango_find_base_dir (text->text, text->text_bytes);
1379 	else
1380 		return text->direction;
1381 }
1382 
1383 static PangoDirection
get_pango_base_direction(HTMLText * text)1384 get_pango_base_direction (HTMLText *text)
1385 {
1386 	switch (html_object_get_direction (HTML_OBJECT (text))) {
1387 	case HTML_DIRECTION_RTL:
1388 		return PANGO_DIRECTION_RTL;
1389 	case HTML_DIRECTION_LTR:
1390 		return PANGO_DIRECTION_LTR;
1391 	case HTML_DIRECTION_DERIVED:
1392 	default:
1393 		if (text->text)
1394 			return html_text_get_pango_direction (text);
1395 		else
1396 			return PANGO_DIRECTION_LTR;
1397 	}
1398 }
1399 
1400 /**
1401  * pango_glyph_string_get_logical_widths:
1402  * @glyphs: a #PangoGlyphString
1403  * @text: the text corresponding to the glyphs
1404  * @length: the length of @text, in bytes
1405  * @embedding_level: the embedding level of the string
1406  * @logical_widths: an array whose length is g_utf8_strlen (text, length)
1407  *                  to be filled in with the resulting character widths.
1408  *
1409  * Given a #PangoGlyphString resulting from pango_shape() and the corresponding
1410  * text, determine the screen width corresponding to each character. When
1411  * multiple characters compose a single cluster, the width of the entire
1412  * cluster is divided equally among the characters.
1413  **/
1414 
1415 void
html_tmp_fix_pango_glyph_string_get_logical_widths(PangoGlyphString * glyphs,const gchar * text,gint length,gint embedding_level,gint * logical_widths)1416 html_tmp_fix_pango_glyph_string_get_logical_widths (PangoGlyphString *glyphs,
1417                                                     const gchar *text,
1418                                                     gint length,
1419                                                     gint embedding_level,
1420                                                     gint *logical_widths)
1421 {
1422   gint i, j;
1423   gint last_cluster = 0;
1424   gint width = 0;
1425   gint last_cluster_width = 0;
1426   const gchar *p = text;		/* Points to start of current cluster */
1427 
1428   /* printf ("html_tmp_fix_pango_glyph_string_get_logical_widths"); */
1429 
1430   for (i = 0; i <= glyphs->num_glyphs; i++)
1431     {
1432       gint glyph_index = (embedding_level % 2 == 0) ? i : glyphs->num_glyphs - i - 1;
1433 
1434       /* If this glyph belongs to a new cluster, or we're at the end, find
1435        * the start of the next cluster, and assign the widths for this cluster.
1436        */
1437       if (i == glyphs->num_glyphs || p != text + glyphs->log_clusters[glyph_index])
1438 	{
1439 	  gint next_cluster = last_cluster;
1440 
1441 	  if (i < glyphs->num_glyphs)
1442 	    {
1443 	      while (p < text + glyphs->log_clusters[glyph_index])
1444 		{
1445 		  next_cluster++;
1446 		  p = g_utf8_next_char (p);
1447 		}
1448 	    }
1449 	  else
1450 	    {
1451 	      while (p < text + length)
1452 		{
1453 		  next_cluster++;
1454 		  p = g_utf8_next_char (p);
1455 		}
1456 	    }
1457 
1458 	  for (j = last_cluster; j < next_cluster; j++) {
1459 	    logical_widths[j] = (width - last_cluster_width) / (next_cluster - last_cluster);
1460 	    /* printf (" %d", logical_widths [j]); */
1461 	  }
1462 
1463 	  if (last_cluster != next_cluster) {
1464 		  last_cluster = next_cluster;
1465 		  last_cluster_width = width;
1466 	  }
1467 	}
1468 
1469       if (i < glyphs->num_glyphs)
1470 	width += glyphs->glyphs[glyph_index].geometry.width;
1471     }
1472   /* printf ("\n"); */
1473 }
1474 
1475 static void
html_text_shape_tab(HTMLText * text,PangoGlyphString * glyphs)1476 html_text_shape_tab (HTMLText *text,
1477                      PangoGlyphString *glyphs)
1478 {
1479 	/* copied from pango sources */
1480 	pango_glyph_string_set_size (glyphs, 1);
1481 
1482 	glyphs->glyphs[0].glyph = EMPTY_GLYPH;
1483 	glyphs->glyphs[0].geometry.x_offset = 0;
1484 	glyphs->glyphs[0].geometry.y_offset = 0;
1485 	glyphs->glyphs[0].attr.is_cluster_start = 1;
1486 
1487 	glyphs->log_clusters[0] = 0;
1488 
1489 	glyphs->glyphs[0].geometry.width = 48 * PANGO_SCALE;
1490 }
1491 
1492 HTMLTextPangoInfo *
html_text_get_pango_info(HTMLText * text,HTMLPainter * painter)1493 html_text_get_pango_info (HTMLText *text,
1494                           HTMLPainter *painter)
1495 {
1496 	if (HTML_OBJECT (text)->change & HTML_CHANGE_RECALC_PI)	{
1497 		pango_info_destroy (text);
1498 		HTML_OBJECT (text)->change &= ~HTML_CHANGE_RECALC_PI;
1499 		text->direction = pango_find_base_dir (text->text, text->text_bytes);
1500 	}
1501 	if (!text->pi) {
1502 		GList *items, *cur;
1503 		PangoAttrList *attrs;
1504 		gint i, offset;
1505 
1506 		attrs = html_text_prepare_attrs (text, painter);
1507 		items = pango_itemize_with_base_dir (painter->pango_context, get_pango_base_direction (text), text->text, 0, text->text_bytes, attrs, NULL);
1508 		pango_attr_list_unref (attrs);
1509 
1510 		/* create pango info */
1511 		text->pi = html_text_pango_info_new (g_list_length (items));
1512 		text->pi->have_font = TRUE;
1513 		text->pi->font_style = html_text_get_font_style (text);
1514 		text->pi->face = g_strdup (text->face);
1515 		text->pi->attrs = g_new (PangoLogAttr, text->text_len + 1);
1516 
1517 		/* get line breaks */
1518 		offset = 0;
1519 		for (cur = items; cur; cur = cur->next) {
1520 			PangoItem tmp_item;
1521 			PangoItem *item;
1522 			gint start_offset;
1523 
1524 			start_offset = offset;
1525 			item = (PangoItem *) cur->data;
1526 			offset += item->num_chars;
1527 			tmp_item = *item;
1528 			while (cur->next) {
1529 				PangoItem *next_item = (PangoItem *) cur->next->data;
1530 				if (tmp_item.analysis.lang_engine == next_item->analysis.lang_engine) {
1531 					tmp_item.length += next_item->length;
1532 					tmp_item.num_chars += next_item->num_chars;
1533 					offset += next_item->num_chars;
1534 					cur = cur->next;
1535 				} else
1536 					break;
1537 			}
1538 
1539 			pango_break (text->text + tmp_item.offset, tmp_item.length, &tmp_item.analysis, text->pi->attrs + start_offset, tmp_item.num_chars + 1);
1540 		}
1541 
1542 		if (text->pi && text->pi->attrs)
1543 			html_text_remove_unwanted_line_breaks (text->text, text->text_len, text->pi->attrs);
1544 
1545 		for (i = 0, cur = items; i < text->pi->n; i++, cur = cur->next)
1546 			text->pi->entries[i].glyph_item.item = (PangoItem *) cur->data;
1547 
1548 		for (i = 0; i < text->pi->n; i++) {
1549 			PangoItem *item;
1550 			PangoGlyphString *glyphs;
1551 
1552 			item = text->pi->entries[i].glyph_item.item;
1553 			glyphs = text->pi->entries[i].glyph_item.glyphs = pango_glyph_string_new ();
1554 
1555 			/* printf ("item pos %d len %d\n", item->offset, item->length); */
1556 
1557 			text->pi->entries[i].widths = g_new (PangoGlyphUnit, item->num_chars);
1558 			if (text->text[item->offset] == '\t')
1559 				html_text_shape_tab (text, glyphs);
1560 			else
1561 				pango_shape (text->text + item->offset, item->length, &item->analysis, glyphs);
1562 			html_tmp_fix_pango_glyph_string_get_logical_widths (glyphs, text->text + item->offset, item->length,
1563 									    item->analysis.level, text->pi->entries[i].widths);
1564 		}
1565 
1566 		g_list_free (items);
1567 	}
1568 	return text->pi;
1569 }
1570 
1571 gboolean
html_text_pi_backward(HTMLTextPangoInfo * pi,gint * ii,gint * io)1572 html_text_pi_backward (HTMLTextPangoInfo *pi,
1573                        gint *ii,
1574                        gint *io)
1575 {
1576 	if (*io <= 0) {
1577 		if (*ii <= 0)
1578 			return FALSE;
1579 		(*ii) --;
1580 		*io = pi->entries [*ii].glyph_item.item->num_chars - 1;
1581 	} else
1582 		(*io) --;
1583 
1584 	return TRUE;
1585 }
1586 
1587 gboolean
html_text_pi_forward(HTMLTextPangoInfo * pi,gint * ii,gint * io)1588 html_text_pi_forward (HTMLTextPangoInfo *pi,
1589                       gint *ii,
1590                       gint *io)
1591 {
1592 	if (*io >= pi->entries[*ii].glyph_item.item->num_chars - 1) {
1593 		if (*ii >= pi->n -1)
1594 			return FALSE;
1595 		(*ii) ++;
1596 		*io = 0;
1597 	} else
1598 		(*io) ++;
1599 
1600 	return TRUE;
1601 }
1602 
1603 /**
1604  * html_text_tail_white_space:
1605  * @text: a #HTMLText object
1606  * @painter: a #HTMLPainter object
1607  * @offset: offset into the text of @text, in characters
1608  * @ii: index of current item
1609  * @io: offset within current item, in characters
1610  * @white_len: length of found trailing white space, in characters
1611  * @line_offset:
1612  * @s: pointer into the text of @text corresponding to @offset
1613  *
1614  * Used to chop off one character worth of whitespace at a particular position.
1615  *
1616  * Return value: width of found trailing white space, in Pango units
1617  **/
1618 gint
html_text_tail_white_space(HTMLText * text,HTMLPainter * painter,gint offset,gint ii,gint io,gint * white_len,gint line_offset,gchar * s)1619 html_text_tail_white_space (HTMLText *text,
1620                             HTMLPainter *painter,
1621                             gint offset,
1622                             gint ii,
1623                             gint io,
1624                             gint *white_len,
1625                             gint line_offset,
1626                             gchar *s)
1627 {
1628 	HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
1629 	gint wl = 0;
1630 	gint ww = 0;
1631 
1632 	if (html_text_pi_backward (pi, &ii, &io)) {
1633 		s = g_utf8_prev_char (s);
1634 		offset--;
1635 		if (pi->attrs[offset].is_white) {
1636 			if (*s == '\t' && offset > 1) {
1637 				gint skip = 8, co = offset - 1;
1638 
1639 				do {
1640 					s = g_utf8_prev_char (s);
1641 					co--;
1642 					if (*s != '\t')
1643 						skip--;
1644 				} while (s && co > 0 && *s != '\t');
1645 
1646 				ww += skip * pi->entries[ii].widths[io];
1647 			} else {
1648 				ww += pi->entries[ii].widths[io];
1649 			}
1650 			wl++;
1651 		}
1652 	}
1653 
1654 	if (white_len)
1655 		*white_len = wl;
1656 
1657 	return ww;
1658 }
1659 
1660 static void
update_mw(HTMLText * text,HTMLPainter * painter,gint offset,gint * last_offset,gint * ww,gint * mw,gint ii,gint io,gchar * s,gint line_offset)1661 update_mw (HTMLText *text,
1662            HTMLPainter *painter,
1663            gint offset,
1664            gint *last_offset,
1665            gint *ww,
1666            gint *mw,
1667            gint ii,
1668            gint io,
1669            gchar *s,
1670            gint line_offset) {
1671 	*ww -= html_text_tail_white_space (text, painter, offset, ii, io, NULL, line_offset, s);
1672         if (*ww > *mw)
1673 		*mw = *ww;
1674 	*ww = 0;
1675 
1676 	*last_offset = offset;
1677 }
1678 
1679 gboolean
html_text_is_line_break(PangoLogAttr attr)1680 html_text_is_line_break (PangoLogAttr attr)
1681 {
1682 	return attr.is_line_break;
1683 }
1684 
1685 static gint
calc_min_width(HTMLObject * self,HTMLPainter * painter)1686 calc_min_width (HTMLObject *self,
1687                 HTMLPainter *painter)
1688 {
1689 	HTMLText *text = HTML_TEXT (self);
1690 	HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
1691 	gint mw = 0, ww;
1692 	gint ii, io, offset, last_offset, line_offset;
1693 	gchar *s;
1694 
1695 	ww = 0;
1696 
1697 	last_offset = offset = 0;
1698 	ii = io = 0;
1699 	line_offset = html_text_get_line_offset (text, painter, 0);
1700 	s = text->text;
1701 	while (offset < text->text_len) {
1702 		if (offset > 0 && html_text_is_line_break (pi->attrs[offset]))
1703 			update_mw (text, painter, offset, &last_offset, &ww, &mw, ii, io, s, line_offset);
1704 
1705 		if (*s == '\t') {
1706 			gint skip = 8 - (line_offset % 8);
1707 			ww += skip * pi->entries[ii].widths[io];
1708 			line_offset += skip;
1709 		} else {
1710 			ww += pi->entries[ii].widths[io];
1711 			line_offset++;
1712 		}
1713 
1714 		s = g_utf8_next_char (s);
1715 		offset++;
1716 
1717 		html_text_pi_forward (pi, &ii, &io);
1718 	}
1719 
1720 	if (ww > mw)
1721 		mw = ww;
1722 
1723 	return MAX (1, html_painter_pango_to_engine (painter, mw));
1724 }
1725 
1726 static void
draw(HTMLObject * o,HTMLPainter * p,gint x,gint y,gint width,gint height,gint tx,gint ty)1727 draw (HTMLObject *o,
1728       HTMLPainter *p,
1729       gint x,
1730       gint y,
1731       gint width,
1732       gint height,
1733       gint tx,
1734       gint ty)
1735 {
1736 }
1737 
1738 static gboolean
accepts_cursor(HTMLObject * object)1739 accepts_cursor (HTMLObject *object)
1740 {
1741 	return TRUE;
1742 }
1743 
1744 static gboolean
save_open_attrs(HTMLEngineSaveState * state,GSList * attrs)1745 save_open_attrs (HTMLEngineSaveState *state,
1746                  GSList *attrs)
1747 {
1748 	gboolean rv = TRUE;
1749 
1750 	for (; attrs; attrs = attrs->next) {
1751 		PangoAttribute *attr = (PangoAttribute *) attrs->data;
1752 		HTMLEngine *e = state->engine;
1753 		const gchar *tag = NULL;
1754 		gboolean free_tag = FALSE;
1755 
1756 		switch (attr->klass->type) {
1757 		case PANGO_ATTR_WEIGHT:
1758 			tag = "<B>";
1759 			break;
1760 		case PANGO_ATTR_STYLE:
1761 			tag = "<I>";
1762 			break;
1763 		case PANGO_ATTR_UNDERLINE:
1764 			tag = "<U>";
1765 			break;
1766 		case PANGO_ATTR_STRIKETHROUGH:
1767 			tag = "<S>";
1768 			break;
1769 		case PANGO_ATTR_SIZE:
1770 			if (attr->klass == &html_pango_attr_font_size_klass) {
1771 				HTMLPangoAttrFontSize *size = (HTMLPangoAttrFontSize *) attr;
1772 				if ((size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3 && (size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0) {
1773 					tag = g_strdup_printf ("<FONT SIZE=\"%d\">", size->style & GTK_HTML_FONT_STYLE_SIZE_MASK);
1774 					free_tag = TRUE;
1775 				}
1776 			}
1777 			break;
1778 		case PANGO_ATTR_FAMILY: {
1779 			PangoAttrString *family_attr = (PangoAttrString *) attr;
1780 
1781 			if (!g_ascii_strcasecmp (e->painter->font_manager.fixed.face
1782 					? e->painter->font_manager.fixed.face : "Monospace",
1783 					family_attr->value))
1784 				tag = "<TT>";
1785 		}
1786 		break;
1787 		case PANGO_ATTR_FOREGROUND: {
1788 			PangoAttrColor *color = (PangoAttrColor *) attr;
1789 			tag = g_strdup_printf ("<FONT COLOR=\"#%02x%02x%02x\">",
1790 					       (color->color.red >> 8) & 0xff, (color->color.green >> 8) & 0xff, (color->color.blue >> 8) & 0xff);
1791 			free_tag = TRUE;
1792 		}
1793 			break;
1794 		default:
1795 			break;
1796 		}
1797 
1798 		if (tag) {
1799 			if (!html_engine_save_output_string (state, "%s", tag))
1800 				rv = FALSE;
1801 			if (free_tag)
1802 				g_free ((gpointer) tag);
1803 			if (!rv)
1804 				break;
1805 		}
1806 	}
1807 
1808 	return TRUE;
1809 }
1810 
1811 static gboolean
save_close_attrs(HTMLEngineSaveState * state,GSList * attrs)1812 save_close_attrs (HTMLEngineSaveState *state,
1813                   GSList *attrs)
1814 {
1815 	for (; attrs; attrs = attrs->next) {
1816 		PangoAttribute *attr = (PangoAttribute *) attrs->data;
1817 		HTMLEngine *e = state->engine;
1818 		const gchar *tag = NULL;
1819 
1820 		switch (attr->klass->type) {
1821 		case PANGO_ATTR_WEIGHT:
1822 			tag = "</B>";
1823 			break;
1824 		case PANGO_ATTR_STYLE:
1825 			tag = "</I>";
1826 			break;
1827 		case PANGO_ATTR_UNDERLINE:
1828 			tag = "</U>";
1829 			break;
1830 		case PANGO_ATTR_STRIKETHROUGH:
1831 			tag = "</S>";
1832 			break;
1833 		case PANGO_ATTR_SIZE:
1834 			if (attr->klass == &html_pango_attr_font_size_klass) {
1835 				HTMLPangoAttrFontSize *size = (HTMLPangoAttrFontSize *) attr;
1836 				if ((size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3 && (size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0)
1837 					tag = "</FONT>";
1838 			}
1839 			break;
1840 		case PANGO_ATTR_FOREGROUND:
1841 			tag = "</FONT>";
1842 			break;
1843 		case PANGO_ATTR_FAMILY: {
1844 			PangoAttrString *family_attr = (PangoAttrString *) attr;
1845 
1846 			if (!g_ascii_strcasecmp (e->painter->font_manager.fixed.face
1847 					? e->painter->font_manager.fixed.face : "Monospace",
1848 					family_attr->value))
1849 				tag = "</TT>";
1850 		}
1851 		break;
1852 		default:
1853 			break;
1854 		}
1855 
1856 		if (tag)
1857 			if (!html_engine_save_output_string (state, "%s", tag))
1858 				return FALSE;
1859 	}
1860 
1861 	return TRUE;
1862 }
1863 
1864 static gboolean
save_text_part(HTMLText * text,HTMLEngineSaveState * state,guint start_index,guint end_index)1865 save_text_part (HTMLText *text,
1866                 HTMLEngineSaveState *state,
1867                 guint start_index,
1868                 guint end_index)
1869 {
1870 	gchar *str;
1871 	gint len;
1872 	gboolean rv;
1873 
1874 	str = g_strndup (text->text + start_index, end_index - start_index);
1875 	len = g_utf8_pointer_to_offset (text->text + start_index, text->text + end_index);
1876 
1877 	rv = html_engine_save_encode (state, str, len);
1878 	g_free (str);
1879 	return rv;
1880 }
1881 
1882 static gboolean
save_link_open(Link * link,HTMLEngineSaveState * state)1883 save_link_open (Link *link,
1884                 HTMLEngineSaveState *state)
1885 {
1886 	return html_engine_save_delims_and_vals (state,
1887 			"<A HREF=\"", link->url,
1888 			"\">", NULL);
1889 }
1890 
1891 static gboolean
save_link_close(Link * link,HTMLEngineSaveState * state)1892 save_link_close (Link *link,
1893                  HTMLEngineSaveState *state)
1894 {
1895 	return html_engine_save_output_string (state, "%s", "</A>");
1896 }
1897 
1898 static gboolean
save_text(HTMLText * text,HTMLEngineSaveState * state,guint start_index,guint end_index,GSList ** l,gboolean * link_started)1899 save_text (HTMLText *text,
1900            HTMLEngineSaveState *state,
1901            guint start_index,
1902            guint end_index,
1903            GSList **l,
1904            gboolean *link_started)
1905 {
1906 	if (*l) {
1907 		Link *link;
1908 
1909 		link = (Link *) (*l)->data;
1910 
1911 		while (*l && ((!*link_started && start_index <= link->start_index && link->start_index < end_index)
1912 			      || (*link_started && link->end_index <= end_index))) {
1913 			if (!*link_started && start_index <= link->start_index && link->start_index < end_index) {
1914 				if (!save_text_part (text, state, start_index, link->start_index))
1915 					return FALSE;
1916 				*link_started = TRUE;
1917 				save_link_open (link, state);
1918 				start_index = link->start_index;
1919 			}
1920 			if (*link_started && link->end_index <= end_index) {
1921 				if (!save_text_part (text, state, start_index, link->end_index))
1922 					return FALSE;
1923 				save_link_close (link, state);
1924 				*link_started = FALSE;
1925 				(*l) = (*l)->next;
1926 				start_index = link->end_index;
1927 				if (*l)
1928 					link = (Link *) (*l)->data;
1929 			}
1930 		}
1931 
1932 	}
1933 
1934 	if (start_index < end_index)
1935 		return save_text_part (text, state, start_index, end_index);
1936 
1937 	return TRUE;
1938 }
1939 
1940 static gboolean
save(HTMLObject * self,HTMLEngineSaveState * state)1941 save (HTMLObject *self,
1942       HTMLEngineSaveState *state)
1943 {
1944 	HTMLText *text = HTML_TEXT (self);
1945 	PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
1946 
1947 	if (iter) {
1948 		GSList *l, *links = g_slist_reverse (g_slist_copy (text->links));
1949 		gboolean link_started = FALSE;
1950 
1951 		l = links;
1952 
1953 		do {
1954 			GSList *attrs;
1955 			gint start_index, end_index;
1956 
1957 			attrs = pango_attr_iterator_get_attrs (iter);
1958 			pango_attr_iterator_range (iter,
1959 						   &start_index,
1960 						   &end_index);
1961 			if (end_index > text->text_bytes)
1962 				end_index = text->text_bytes;
1963 
1964 			if (attrs)
1965 				save_open_attrs (state, attrs);
1966 			save_text (text, state, start_index, end_index, &l, &link_started);
1967 			if (attrs) {
1968 				attrs = g_slist_reverse (attrs);
1969 				save_close_attrs (state, attrs);
1970 				html_text_free_attrs (attrs);
1971 			}
1972 		} while (pango_attr_iterator_next (iter));
1973 
1974 		pango_attr_iterator_destroy (iter);
1975 		g_slist_free (links);
1976 	}
1977 
1978 	return TRUE;
1979 }
1980 
1981 static gboolean
save_plain(HTMLObject * self,HTMLEngineSaveState * state,gint requested_width)1982 save_plain (HTMLObject *self,
1983             HTMLEngineSaveState *state,
1984             gint requested_width)
1985 {
1986 	HTMLText *text;
1987 
1988 	text = HTML_TEXT (self);
1989 
1990 	return html_engine_save_output_string (state, "%s", text->text);
1991 }
1992 
1993 static guint
get_length(HTMLObject * self)1994 get_length (HTMLObject *self)
1995 {
1996 	return HTML_TEXT (self)->text_len;
1997 }
1998 
1999 /* #define DEBUG_NBSP */
2000 
2001 struct TmpDeltaRecord
2002 {
2003 	gint index;		/* Byte index within original string  */
2004 	gint delta;		/* New delta (character at index was modified,
2005 				 * new delta applies to characters afterwards)
2006 				 */
2007 };
2008 
2009 /* Called when current character is not white space or at end of string */
2010 static gboolean
check_last_white(gint white_space,gunichar last_white,gint * delta_out)2011 check_last_white (gint white_space,
2012                   gunichar last_white,
2013                   gint *delta_out)
2014 {
2015 	if (white_space > 0 && last_white == ENTITY_NBSP) {
2016 		(*delta_out) --; /* &nbsp; => &sp; is one byte shorter in UTF-8 */
2017 		return TRUE;
2018 	}
2019 
2020 	return FALSE;
2021 }
2022 
2023 /* Called when current character is white space */
2024 static gboolean
check_prev_white(gint white_space,gunichar last_white,gint * delta_out)2025 check_prev_white (gint white_space,
2026                   gunichar last_white,
2027                   gint *delta_out)
2028 {
2029 	if (white_space > 0 && last_white == ' ') {
2030 		(*delta_out) ++; /* &sp; => &nbsp; is one byte longer in UTF-8 */
2031 		return TRUE;
2032 	}
2033 
2034 	return FALSE;
2035 }
2036 
2037 static GSList *
add_change(GSList * list,gint index,gint delta)2038 add_change (GSList *list,
2039             gint index,
2040             gint delta)
2041 {
2042 	struct TmpDeltaRecord *rec = g_new (struct TmpDeltaRecord, 1);
2043 
2044 	rec->index = index;
2045 	rec->delta = delta;
2046 
2047 	return g_slist_prepend (list, rec);
2048 }
2049 
2050 /* This function does a pre-scan for the transformation in convert_nbsp,
2051  * which converts a sequence of N white space characters (&sp; or &nbsp;)
2052  * into N-1 &nbsp and 1 &sp;.
2053  *
2054  * delta_out: total change in byte length of string
2055  * changes_out: location to store series of records for each change in offset
2056  *              between the original string and the new string.
2057  * returns: %TRUE if any records were stored in changes_out
2058  */
2059 static gboolean
is_convert_nbsp_needed(const gchar * s,gint * delta_out,GSList ** changes_out)2060 is_convert_nbsp_needed (const gchar *s,
2061                         gint *delta_out,
2062                         GSList **changes_out)
2063 {
2064 	gunichar uc, last_white = 0;
2065 	gboolean change;
2066 	gint white_space;
2067 	const gchar *p, *last_p;
2068 
2069 	*delta_out = 0;
2070 
2071 	last_p = NULL;		/* Quiet GCC */
2072 	white_space = 0;
2073 	for (p = s; *p; p = g_utf8_next_char (p)) {
2074 		uc = g_utf8_get_char (p);
2075 
2076 		if (uc == ENTITY_NBSP || uc == ' ') {
2077 			change = check_prev_white (white_space, last_white, delta_out);
2078 			white_space++;
2079 			last_white = uc;
2080 		} else {
2081 			change = check_last_white (white_space, last_white, delta_out);
2082 			white_space = 0;
2083 		}
2084 		if (change)
2085 			*changes_out = add_change (*changes_out, last_p - s, *delta_out);
2086 		last_p = p;
2087 	}
2088 
2089 	if (check_last_white (white_space, last_white, delta_out))
2090 		*changes_out = add_change (*changes_out, last_p - s, *delta_out);
2091 
2092 	*changes_out = g_slist_reverse (*changes_out);
2093 
2094 	return *changes_out != NULL;
2095 }
2096 
2097 /* Called when current character is white space */
2098 static void
write_prev_white_space(gint white_space,gchar ** fill)2099 write_prev_white_space (gint white_space,
2100                         gchar **fill)
2101 {
2102 	if (white_space > 0) {
2103 #ifdef DEBUG_NBSP
2104 		printf ("&nbsp;");
2105 #endif
2106 		**fill = 0xc2; (*fill) ++;
2107 		**fill = 0xa0; (*fill) ++;
2108 	}
2109 }
2110 
2111 /* Called when current character is not white space or at end of string */
2112 static void
write_last_white_space(gint white_space,gchar ** fill)2113 write_last_white_space (gint white_space,
2114                         gchar **fill)
2115 {
2116 	if (white_space > 0) {
2117 #ifdef DEBUG_NBSP
2118 		printf (" ");
2119 #endif
2120 		**fill = ' '; (*fill) ++;
2121 	}
2122 }
2123 
2124 /* converts a sequence of N white space characters (&sp; or &nbsp;)
2125  * into N-1 &nbsp and 1 &sp;.
2126  */
2127 static void
convert_nbsp(gchar * fill,const gchar * text)2128 convert_nbsp (gchar *fill,
2129               const gchar *text)
2130 {
2131 	gint white_space;
2132 	gunichar uc;
2133 	const gchar *this_p, *p;
2134 
2135 	p = text;
2136 	white_space = 0;
2137 
2138 #ifdef DEBUG_NBSP
2139 	printf ("convert_nbsp: %s --> \"", p);
2140 #endif
2141 
2142 	while (*p) {
2143 		this_p = p;
2144 		uc = g_utf8_get_char (p);
2145 		p = g_utf8_next_char (p);
2146 
2147 		if (uc == ENTITY_NBSP || uc == ' ') {
2148 			write_prev_white_space (white_space, &fill);
2149 			white_space++;
2150 		} else {
2151 			write_last_white_space (white_space, &fill);
2152 			white_space = 0;
2153 #ifdef DEBUG_NBSP
2154 			printf ("*");
2155 #endif
2156 			strncpy (fill, this_p, p - this_p);
2157 			fill += p - this_p;
2158 		}
2159 	}
2160 
2161 	write_last_white_space (white_space, &fill);
2162 	*fill = 0;
2163 
2164 #ifdef DEBUG_NBSP
2165 	printf ("\"\n");
2166 #endif
2167 }
2168 
2169 static void
update_index_interval(guint * start_index,guint * end_index,GSList * changes)2170 update_index_interval (guint *start_index,
2171                        guint *end_index,
2172                        GSList *changes)
2173 {
2174 	GSList *c;
2175 	gint index, delta;
2176 
2177 	index = delta = 0;
2178 
2179 	for (c = changes; c && *start_index > index; c = c->next) {
2180 		struct TmpDeltaRecord *rec = c->data;
2181 
2182 		if (*start_index > index && *start_index <= rec->index) {
2183 			(*start_index) += delta;
2184 			break;
2185 		}
2186 		index = rec->index;
2187 		delta = rec->delta;
2188 	}
2189 
2190 	if (c == NULL && *start_index > index) {
2191 		(*start_index) += delta;
2192 		(*end_index) += delta;
2193 		return;
2194 	}
2195 
2196 	for (; c && *end_index > index; c = c->next) {
2197 		struct TmpDeltaRecord *rec = c->data;
2198 
2199 		if (*end_index > index && *end_index <= rec->index) {
2200 			(*end_index) += delta;
2201 			break;
2202 		}
2203 		index = rec->index;
2204 		delta = rec->delta;
2205 	}
2206 
2207 	if (c == NULL && *end_index > index)
2208 		(*end_index) += delta;
2209 }
2210 
2211 static gboolean
update_attributes_filter(PangoAttribute * attr,gpointer data)2212 update_attributes_filter (PangoAttribute *attr,
2213                           gpointer data)
2214 {
2215 	update_index_interval (&attr->start_index, &attr->end_index, (GSList *) data);
2216 
2217 	return FALSE;
2218 }
2219 
2220 static void
update_attributes(PangoAttrList * attrs,GSList * changes)2221 update_attributes (PangoAttrList *attrs,
2222                    GSList *changes)
2223 {
2224 	pango_attr_list_filter (attrs, update_attributes_filter, changes);
2225 }
2226 
2227 static void
update_links(GSList * links,GSList * changes)2228 update_links (GSList *links,
2229               GSList *changes)
2230 {
2231 	GSList *cl;
2232 
2233 	for (cl = links; cl; cl = cl->next) {
2234 		Link *link = (Link *) cl->data;
2235 		update_index_interval (&link->start_index, &link->end_index, changes);
2236 	}
2237 }
2238 
2239 static void
free_changes(GSList * changes)2240 free_changes (GSList *changes)
2241 {
2242 	GSList *c;
2243 
2244 	for (c = changes; c; c = c->next)
2245 		g_free (c->data);
2246 	g_slist_free (changes);
2247 }
2248 
2249 gboolean
html_text_convert_nbsp(HTMLText * text,gboolean free_text)2250 html_text_convert_nbsp (HTMLText *text,
2251                         gboolean free_text)
2252 {
2253 	GSList *changes = NULL;
2254 	gint delta;
2255 
2256 	if (is_convert_nbsp_needed (text->text, &delta, &changes)) {
2257 		gchar *to_free;
2258 
2259 		to_free = text->text;
2260 		text->text = g_malloc (strlen (to_free) + delta + 1);
2261 		text->text_bytes += delta;
2262 		convert_nbsp (text->text, to_free);
2263 		if (free_text)
2264 			g_free (to_free);
2265 		if (changes) {
2266 			if (text->attr_list)
2267 				update_attributes (text->attr_list, changes);
2268 			if (text->extra_attr_list)
2269 				update_attributes (text->extra_attr_list, changes);
2270 			if (text->links)
2271 				update_links (text->links, changes);
2272 			free_changes (changes);
2273 		}
2274 		html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
2275 		return TRUE;
2276 	}
2277 	return FALSE;
2278 }
2279 
2280 static void
move_spell_errors(GList * spell_errors,guint offset,gint delta)2281 move_spell_errors (GList *spell_errors,
2282                    guint offset,
2283                    gint delta)
2284 {
2285 	SpellError *se;
2286 
2287 	if (!delta)
2288 		return;
2289 
2290 	while (spell_errors) {
2291 		se = (SpellError *) spell_errors->data;
2292 		if (se->off >= offset)
2293 			se->off += delta;
2294 		spell_errors = spell_errors->next;
2295 	}
2296 }
2297 
2298 static GList *
remove_one(GList * list,GList * link)2299 remove_one (GList *list,
2300             GList *link)
2301 {
2302 	spell_error_destroy ((SpellError *) link->data);
2303 	list = g_list_remove_link (list, link);
2304 	g_list_free (link);
2305 
2306 	return list;
2307 }
2308 
2309 static GList *
remove_spell_errors(GList * spell_errors,guint offset,guint len)2310 remove_spell_errors (GList *spell_errors,
2311                      guint offset,
2312                      guint len)
2313 {
2314 	SpellError *se;
2315 	GList *cur, *cnext;
2316 
2317 	cur = spell_errors;
2318 	while (cur) {
2319 		cnext = cur->next;
2320 		se = (SpellError *) cur->data;
2321 		if (se->off < offset) {
2322 			if (se->off + se->len > offset) {
2323 				if (se->off + se->len <= offset + len)
2324 					se->len = offset - se->off;
2325 				else
2326 					se->len -= len;
2327 				if (se->len < 2)
2328 					spell_errors = remove_one (spell_errors, cur);
2329 			}
2330 		} else if (se->off < offset + len) {
2331 			if (se->off + se->len <= offset + len)
2332 				spell_errors = remove_one (spell_errors, cur);
2333 			else {
2334 				se->len -= offset + len - se->off;
2335 				se->off  = offset + len;
2336 				if (se->len < 2)
2337 					spell_errors = remove_one (spell_errors, cur);
2338 			}
2339 		}
2340 		cur = cnext;
2341 	}
2342 	return spell_errors;
2343 }
2344 
2345 static gint
se_cmp(SpellError * a,SpellError * b)2346 se_cmp (SpellError *a,
2347         SpellError *b)
2348 {
2349 	guint o1 = a->off;
2350 	guint o2 = b->off;
2351 
2352 	return (o1 < o2) ? -1 : (o1 == o2) ? 0 : 1;
2353 }
2354 
2355 static GList *
merge_spell_errors(GList * se1,GList * se2)2356 merge_spell_errors (GList *se1,
2357                     GList *se2)
2358 {
2359 	GList *merged = NULL;
2360 	GList *link;
2361 
2362 	/* Build the merge list in reverse order. */
2363 	while (se1 != NULL && se2 != NULL) {
2364 
2365 		/* Pop the lesser of the two list heads. */
2366 		if (se_cmp (se1->data, se2->data) < 0) {
2367 			link = se1;
2368 			se1 = g_list_remove_link (se1, link);
2369 		} else {
2370 			link = se2;
2371 			se2 = g_list_remove_link (se2, link);
2372 		}
2373 
2374 		/* Merge unique items, discard duplicates. */
2375 		if (merged == NULL || se_cmp (link->data, merged->data) != 0)
2376 			merged = g_list_concat (link, merged);
2377 		else {
2378 			spell_error_destroy (link->data);
2379 			g_list_free (link);
2380 		}
2381 	}
2382 
2383 	merged = g_list_reverse (merged);
2384 
2385 	/* At this point at least one of the two input lists are empty,
2386 	 * so just append them both to the end of the merge list. */
2387 
2388 	merged = g_list_concat (merged, se1);
2389 	merged = g_list_concat (merged, se2);
2390 
2391 	return merged;
2392 }
2393 
2394 static HTMLObject *
check_point(HTMLObject * self,HTMLPainter * painter,gint x,gint y,guint * offset_return,gboolean for_cursor)2395 check_point (HTMLObject *self,
2396              HTMLPainter *painter,
2397              gint x,
2398              gint y,
2399              guint *offset_return,
2400              gboolean for_cursor)
2401 {
2402 	return NULL;
2403 }
2404 
2405 static void
queue_draw(HTMLText * text,HTMLEngine * engine,guint offset,guint len)2406 queue_draw (HTMLText *text,
2407             HTMLEngine *engine,
2408             guint offset,
2409             guint len)
2410 {
2411 	HTMLObject *obj;
2412 
2413 	for (obj = HTML_OBJECT (text)->next; obj != NULL; obj = obj->next) {
2414 		HTMLTextSlave *slave;
2415 
2416 		if (HTML_OBJECT_TYPE (obj) != HTML_TYPE_TEXTSLAVE)
2417 			continue;
2418 
2419 		slave = HTML_TEXT_SLAVE (obj);
2420 
2421 		if (offset < slave->posStart + slave->posLen
2422 		    && (len == 0 || offset + len >= slave->posStart)) {
2423 			html_engine_queue_draw (engine, obj);
2424 			if (len != 0 && slave->posStart + slave->posLen > offset + len)
2425 				break;
2426 		}
2427 	}
2428 }
2429 
2430 /* This is necessary to merge the text-specified font style with that of the
2431  * HTMLClueFlow parent.  */
2432 static GtkHTMLFontStyle
get_font_style(const HTMLText * text)2433 get_font_style (const HTMLText *text)
2434 {
2435 	HTMLObject *parent;
2436 	GtkHTMLFontStyle font_style;
2437 
2438 	parent = HTML_OBJECT (text)->parent;
2439 
2440 	if (parent && HTML_OBJECT_TYPE (parent) == HTML_TYPE_CLUEFLOW) {
2441 		GtkHTMLFontStyle parent_style;
2442 
2443 		parent_style = html_clueflow_get_default_font_style (HTML_CLUEFLOW (parent));
2444 		font_style = gtk_html_font_style_merge (parent_style, text->font_style);
2445 	} else {
2446 		font_style = gtk_html_font_style_merge (GTK_HTML_FONT_STYLE_SIZE_3, text->font_style);
2447 	}
2448 
2449 	return font_style;
2450 }
2451 
2452 static void
set_font_style(HTMLText * text,HTMLEngine * engine,GtkHTMLFontStyle style)2453 set_font_style (HTMLText *text,
2454                 HTMLEngine *engine,
2455                 GtkHTMLFontStyle style)
2456 {
2457 	if (text->font_style == style)
2458 		return;
2459 
2460 	text->font_style = style;
2461 
2462 	html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL_CALC);
2463 
2464 	if (engine != NULL) {
2465 		html_object_relayout (HTML_OBJECT (text)->parent, engine, HTML_OBJECT (text));
2466 		html_engine_queue_draw (engine, HTML_OBJECT (text));
2467 	}
2468 }
2469 
2470 static void
destroy(HTMLObject * obj)2471 destroy (HTMLObject *obj)
2472 {
2473 	HTMLText *text = HTML_TEXT (obj);
2474 	html_color_unref (text->color);
2475 	html_text_spell_errors_clear (text);
2476 	g_free (text->text);
2477 	g_free (text->face);
2478 	pango_info_destroy (text);
2479 	pango_attr_list_unref (text->attr_list);
2480 	text->attr_list = NULL;
2481 	if (text->extra_attr_list) {
2482 		pango_attr_list_unref (text->extra_attr_list);
2483 		text->extra_attr_list = NULL;
2484 	}
2485 	free_links (text->links);
2486 	text->links = NULL;
2487 
2488 	HTML_OBJECT_CLASS (parent_class)->destroy (obj);
2489 }
2490 
2491 static gboolean
select_range(HTMLObject * self,HTMLEngine * engine,guint offset,gint length,gboolean queue_draw)2492 select_range (HTMLObject *self,
2493               HTMLEngine *engine,
2494               guint offset,
2495               gint length,
2496               gboolean queue_draw)
2497 {
2498 	HTMLText *text;
2499 	HTMLObject *p;
2500 	HTMLTextPangoInfo *pi = html_text_get_pango_info (HTML_TEXT (self), engine->painter);
2501 	gboolean changed;
2502 
2503 	text = HTML_TEXT (self);
2504 
2505 	if (length < 0 || length + offset > HTML_TEXT (self)->text_len)
2506 		length = HTML_TEXT (self)->text_len - offset;
2507 
2508 	/* extend to cursor positions */
2509 	while (offset > 0 && !pi->attrs[offset].is_cursor_position) {
2510 		offset--;
2511 		length++;
2512 	}
2513 
2514 	while (offset + length < text->text_len && !pi->attrs[offset + length].is_cursor_position)
2515 		length++;
2516 
2517 	/* printf ("updated offset: %d length: %d (end offset %d)\n", offset, length, offset + length); */
2518 
2519 	if (offset != text->select_start || length != text->select_length)
2520 		changed = TRUE;
2521 	else
2522 		changed = FALSE;
2523 
2524 	/* printf ("select range %d, %d\n", offset, length); */
2525 	if (queue_draw) {
2526 		for (p = self->next;
2527 		     p != NULL && HTML_OBJECT_TYPE (p) == HTML_TYPE_TEXTSLAVE;
2528 		     p = p->next) {
2529 			HTMLTextSlave *slave;
2530 			gboolean was_selected, is_selected;
2531 			guint max;
2532 
2533 			slave = HTML_TEXT_SLAVE (p);
2534 
2535 			max = slave->posStart + slave->posLen;
2536 
2537 			if (text->select_start + text->select_length > slave->posStart
2538 			    && text->select_start < max)
2539 				was_selected = TRUE;
2540 			else
2541 				was_selected = FALSE;
2542 
2543 			if (offset + length > slave->posStart && offset < max)
2544 				is_selected = TRUE;
2545 			else
2546 				is_selected = FALSE;
2547 
2548 			if (was_selected && is_selected) {
2549 				gint diff1, diff2;
2550 
2551 				diff1 = offset - slave->posStart;
2552 				diff2 = text->select_start - slave->posStart;
2553 
2554 				/* printf ("offsets diff 1: %d 2: %d\n", diff1, diff2); */
2555 				if (diff1 != diff2) {
2556 					html_engine_queue_draw (engine, p);
2557 				} else {
2558 					diff1 = offset + length - slave->posStart;
2559 					diff2 = (text->select_start + text->select_length
2560 						 - slave->posStart);
2561 
2562 					/* printf ("lens diff 1: %d 2: %d\n", diff1, diff2); */
2563 					if (diff1 != diff2)
2564 						html_engine_queue_draw (engine, p);
2565 				}
2566 			} else {
2567 				if ((!was_selected && is_selected) || (was_selected && !is_selected))
2568 					html_engine_queue_draw (engine, p);
2569 			}
2570 		}
2571 	}
2572 
2573 	text->select_start = offset;
2574 	text->select_length = length;
2575 
2576 	if (length == 0)
2577 		self->selected = FALSE;
2578 	else
2579 		self->selected = TRUE;
2580 
2581 	return changed;
2582 }
2583 
2584 static HTMLObject *
set_link(HTMLObject * self,HTMLColor * color,const gchar * url,const gchar * target)2585 set_link (HTMLObject *self,
2586           HTMLColor *color,
2587           const gchar *url,
2588           const gchar *target)
2589 {
2590 	/* HTMLText *text = HTML_TEXT (self); */
2591 
2592 	/* FIXME-link return url ? html_link_text_new_with_len (text->text, text->text_len, text->font_style, color, url, target) : NULL; */
2593 	return NULL;
2594 }
2595 
2596 static void
append_selection_string(HTMLObject * self,GString * buffer)2597 append_selection_string (HTMLObject *self,
2598                          GString *buffer)
2599 {
2600 	HTMLText *text;
2601 	const gchar *p, *last;
2602 
2603 	text = HTML_TEXT (self);
2604 	if (text->select_length == 0)
2605 		return;
2606 
2607 	p    = html_text_get_text (text, text->select_start);
2608 	last = g_utf8_offset_to_pointer (p, text->select_length);
2609 
2610 	/* OPTIMIZED
2611 	last = html_text_get_text (text,
2612 				 * text->select_start + text->select_length);
2613 	*/
2614 	html_engine_save_string_append_nonbsp (buffer,
2615 					       (guchar *) p,
2616 					       last - p);
2617 
2618 }
2619 
2620 static void
get_cursor(HTMLObject * self,HTMLPainter * painter,guint offset,gint * x1,gint * y1,gint * x2,gint * y2)2621 get_cursor (HTMLObject *self,
2622             HTMLPainter *painter,
2623             guint offset,
2624             gint *x1,
2625             gint *y1,
2626             gint *x2,
2627             gint *y2)
2628 {
2629 	HTMLObject *slave;
2630 	guint ascent, descent;
2631 
2632 	html_object_get_cursor_base (self, painter, offset, x2, y2);
2633 
2634 	slave = self->next;
2635 	if (slave == NULL || HTML_OBJECT_TYPE (slave) != HTML_TYPE_TEXTSLAVE) {
2636 		ascent = self->ascent;
2637 		descent = self->descent;
2638 	} else {
2639 		ascent = slave->ascent;
2640 		descent = slave->descent;
2641 	}
2642 
2643 	*x1 = *x2;
2644 	*y1 = *y2 - ascent;
2645 	*y2 += descent - 1;
2646 }
2647 
2648 static void
html_text_get_cursor_base(HTMLObject * self,HTMLPainter * painter,guint offset,gint * x,gint * y)2649 html_text_get_cursor_base (HTMLObject *self,
2650                            HTMLPainter *painter,
2651                            guint offset,
2652                            gint *x,
2653                            gint *y)
2654 {
2655 	HTMLTextSlave *slave = html_text_get_slave_at_offset (HTML_TEXT (self), NULL, offset);
2656 
2657 	/* printf ("slave: %p\n", slave); */
2658 
2659 	if (slave)
2660 		html_text_slave_get_cursor_base (slave, painter, offset - slave->posStart, x, y);
2661 	else {
2662 		g_warning ("Getting cursor base for an HTMLText with no slaves -- %p\n", (gpointer) self);
2663 		html_object_calc_abs_position (self, x, y);
2664 	}
2665 }
2666 
2667 Link *
html_text_get_link_at_offset(HTMLText * text,gint offset)2668 html_text_get_link_at_offset (HTMLText *text,
2669                               gint offset)
2670 {
2671 	GSList *l;
2672 
2673 	for (l = text->links; l; l = l->next) {
2674 		Link *link = (Link *) l->data;
2675 
2676 		if (link->start_offset <= offset && offset <= link->end_offset)
2677 			return link;
2678 	}
2679 
2680 	return NULL;
2681 }
2682 
2683 static const gchar *
get_url(HTMLObject * object,gint offset)2684 get_url (HTMLObject *object,
2685          gint offset)
2686 {
2687 	Link *link = html_text_get_link_at_offset (HTML_TEXT (object), offset);
2688 
2689 	return link ? link->url : NULL;
2690 }
2691 
2692 static const gchar *
get_target(HTMLObject * object,gint offset)2693 get_target (HTMLObject *object,
2694             gint offset)
2695 {
2696 	Link *link = html_text_get_link_at_offset (HTML_TEXT (object), offset);
2697 
2698 	return link ? link->target : NULL;
2699 }
2700 
2701 HTMLTextSlave *
html_text_get_slave_at_offset(HTMLText * text,HTMLTextSlave * start,gint offset)2702 html_text_get_slave_at_offset (HTMLText *text,
2703                                HTMLTextSlave *start,
2704                                gint offset)
2705 {
2706 	HTMLObject *obj = start ? HTML_OBJECT (start) : HTML_OBJECT (text)->next;
2707 
2708 	while (obj && HTML_IS_TEXT_SLAVE (obj) && HTML_TEXT_SLAVE (obj)->posStart + HTML_TEXT_SLAVE (obj)->posLen < offset)
2709 		obj = obj->next;
2710 
2711 	if (obj && HTML_IS_TEXT_SLAVE (obj) && HTML_TEXT_SLAVE (obj)->posStart + HTML_TEXT_SLAVE (obj)->posLen >= offset)
2712 		return HTML_TEXT_SLAVE (obj);
2713 
2714 	return NULL;
2715 }
2716 
2717 static gboolean
html_text_cursor_prev_slave(HTMLObject * slave,HTMLPainter * painter,HTMLCursor * cursor)2718 html_text_cursor_prev_slave (HTMLObject *slave,
2719                              HTMLPainter *painter,
2720                              HTMLCursor *cursor)
2721 {
2722 	gint offset = cursor->offset;
2723 
2724 	while (slave->prev && HTML_IS_TEXT_SLAVE (slave->prev)) {
2725 		if (HTML_TEXT_SLAVE (slave->prev)->posLen) {
2726 			if (html_text_slave_cursor_tail (HTML_TEXT_SLAVE (slave->prev), cursor, painter)) {
2727 				cursor->position += cursor->offset - offset;
2728 				return TRUE;
2729 			} else
2730 				break;
2731 		}
2732 		slave = slave->prev;
2733 	}
2734 
2735 	return FALSE;
2736 }
2737 
2738 static gboolean
html_text_cursor_next_slave(HTMLObject * slave,HTMLPainter * painter,HTMLCursor * cursor)2739 html_text_cursor_next_slave (HTMLObject *slave,
2740                              HTMLPainter *painter,
2741                              HTMLCursor *cursor)
2742 {
2743 	gint offset = cursor->offset;
2744 
2745 	while (slave->next && HTML_IS_TEXT_SLAVE (slave->next)) {
2746 		if (HTML_TEXT_SLAVE (slave->next)->posLen) {
2747 			if (html_text_slave_cursor_head (HTML_TEXT_SLAVE (slave->next), cursor, painter)) {
2748 				cursor->position += cursor->offset - offset;
2749 				return TRUE;
2750 			} else
2751 				break;
2752 		}
2753 		slave = slave->next;
2754 	}
2755 
2756 	return FALSE;
2757 }
2758 
2759 static gboolean
html_text_cursor_forward(HTMLObject * self,HTMLCursor * cursor,HTMLEngine * engine)2760 html_text_cursor_forward (HTMLObject *self,
2761                           HTMLCursor *cursor,
2762                           HTMLEngine *engine)
2763 {
2764 	HTMLText *text;
2765 	HTMLTextPangoInfo *pi = NULL;
2766 	gint len, attrpos = 0;
2767 	gboolean retval = FALSE;
2768 
2769 	g_assert (self);
2770 	g_assert (cursor->object == self);
2771 
2772 	if (html_object_is_container (self))
2773 		return FALSE;
2774 
2775 	text = HTML_TEXT (self);
2776 	pi = html_text_get_pango_info (text, engine->painter);
2777 	len = html_object_get_length (self);
2778 	do {
2779 		attrpos = cursor->offset;
2780 		if (attrpos < len) {
2781 			cursor->offset++;
2782 			cursor->position++;
2783 			retval = TRUE;
2784 		} else {
2785 			retval = FALSE;
2786 			break;
2787 		}
2788 	} while (attrpos < len &&
2789 		 !pi->attrs[attrpos].is_sentence_end &&
2790 		 !pi->attrs[attrpos + 1].is_cursor_position);
2791 
2792 	return retval;
2793 }
2794 
2795 static gboolean
html_cursor_allow_zero_offset(HTMLCursor * cursor,HTMLObject * o)2796 html_cursor_allow_zero_offset (HTMLCursor *cursor,
2797                                HTMLObject *o)
2798 {
2799 	if (cursor->offset == 1) {
2800 		HTMLObject *prev;
2801 
2802 		prev = html_object_prev_not_slave (o);
2803 		if (!prev || HTML_IS_CLUEALIGNED (prev))
2804 			return TRUE;
2805 		else {
2806 			while (prev && !html_object_accepts_cursor (prev))
2807 				prev = html_object_prev_not_slave (prev);
2808 
2809 			if (!prev)
2810 				return TRUE;
2811 		}
2812 	}
2813 
2814 	return FALSE;
2815 }
2816 
2817 static gboolean
html_text_cursor_backward(HTMLObject * self,HTMLCursor * cursor,HTMLEngine * engine)2818 html_text_cursor_backward (HTMLObject *self,
2819                            HTMLCursor *cursor,
2820                            HTMLEngine *engine)
2821 {
2822 	HTMLText *text;
2823 	HTMLTextPangoInfo *pi = NULL;
2824 	gint attrpos = 0;
2825 	gboolean retval = FALSE;
2826 
2827 	g_assert (self);
2828 	g_assert (cursor->object == self);
2829 
2830 	if (html_object_is_container (self))
2831 		return FALSE;
2832 
2833 	text = HTML_TEXT (self);
2834 	pi = html_text_get_pango_info (text, engine->painter);
2835 	do {
2836 		attrpos = cursor->offset;
2837 		if (cursor->offset > 1 ||
2838 		    html_cursor_allow_zero_offset (cursor, self)) {
2839 			cursor->offset--;
2840 			cursor->position--;
2841 			retval = TRUE;
2842 		} else {
2843 			retval = FALSE;
2844 			break;
2845 		}
2846 	} while (attrpos > 0 &&
2847 		 !pi->attrs[attrpos].is_sentence_start &&
2848 		 !pi->attrs[attrpos - 1].is_cursor_position);
2849 
2850 	return retval;
2851 }
2852 
2853 static gboolean
html_text_cursor_right(HTMLObject * self,HTMLPainter * painter,HTMLCursor * cursor)2854 html_text_cursor_right (HTMLObject *self,
2855                         HTMLPainter *painter,
2856                         HTMLCursor *cursor)
2857 {
2858 	HTMLTextSlave *slave;
2859 
2860 	g_assert (self);
2861 	g_assert (cursor->object == self);
2862 
2863 	slave = html_text_get_slave_at_offset (HTML_TEXT (self), NULL, cursor->offset);
2864 
2865 	if (slave) {
2866 		if (html_text_slave_cursor_right (slave, painter, cursor))
2867 			return TRUE;
2868 		else {
2869 			if (self->parent) {
2870 				if (html_object_get_direction (self->parent) == HTML_DIRECTION_RTL)
2871 					return html_text_cursor_prev_slave (HTML_OBJECT (slave), painter, cursor);
2872 				else
2873 					return html_text_cursor_next_slave (HTML_OBJECT (slave), painter, cursor);
2874 			}
2875 		}
2876 	}
2877 
2878 	return FALSE;
2879 }
2880 
2881 static gboolean
html_text_cursor_left(HTMLObject * self,HTMLPainter * painter,HTMLCursor * cursor)2882 html_text_cursor_left (HTMLObject *self,
2883                        HTMLPainter *painter,
2884                        HTMLCursor *cursor)
2885 {
2886 	HTMLTextSlave *slave;
2887 
2888 	g_assert (self);
2889 	g_assert (cursor->object == self);
2890 
2891 	slave = html_text_get_slave_at_offset (HTML_TEXT (self), NULL, cursor->offset);
2892 
2893 	if (slave) {
2894 		if (html_text_slave_cursor_left (slave, painter, cursor))
2895 			return TRUE;
2896 		else {
2897 			if (self->parent) {
2898 				if (html_object_get_direction (self->parent) == HTML_DIRECTION_RTL)
2899 					return html_text_cursor_next_slave (HTML_OBJECT (slave), painter, cursor);
2900 				else
2901 					return html_text_cursor_prev_slave (HTML_OBJECT (slave), painter, cursor);
2902 			}
2903 		}
2904 	}
2905 
2906 	return FALSE;
2907 }
2908 
2909 static gboolean
html_text_backspace(HTMLObject * self,HTMLCursor * cursor,HTMLEngine * engine)2910 html_text_backspace (HTMLObject *self,
2911                      HTMLCursor *cursor,
2912                      HTMLEngine *engine)
2913 {
2914 	HTMLText *text;
2915 	HTMLTextPangoInfo *pi = NULL;
2916 	guint attrpos = 0, prevpos;
2917 	gboolean retval = FALSE;
2918 
2919 	g_assert (self);
2920 	g_assert (cursor->object == self);
2921 
2922 	text = HTML_TEXT (self);
2923 	pi = html_text_get_pango_info (text, engine->painter);
2924 	prevpos = cursor->offset;
2925 	do {
2926 		attrpos = cursor->offset;
2927 		if (cursor->offset > 1 ||
2928 		    html_cursor_allow_zero_offset (cursor, self)) {
2929 			cursor->offset--;
2930 			cursor->position--;
2931 			retval = TRUE;
2932 		} else {
2933 			if (cursor->offset == prevpos)
2934 				retval = FALSE;
2935 			break;
2936 		}
2937 	} while (attrpos > 0 && !pi->attrs[attrpos].is_cursor_position);
2938 
2939 	if (!retval) {
2940 		HTMLObject *prev;
2941 		gint offset = cursor->offset;
2942 
2943 		/* maybe no characters in this line. */
2944 		prev = html_object_prev_cursor (cursor->object, &offset);
2945 		cursor->offset = offset;
2946 		if (prev) {
2947 			if (!html_object_is_container (prev))
2948 				cursor->offset = html_object_get_length (prev);
2949 			cursor->object = prev;
2950 			cursor->position--;
2951 			retval = TRUE;
2952 		}
2953 	}
2954 	if (retval) {
2955 		if (pi->attrs[attrpos].backspace_deletes_character) {
2956 			gchar *cluster_text = &text->text[prevpos];
2957 			gchar *normalized_text = NULL;
2958 			glong len;
2959 			gint offset = cursor->offset, pos = cursor->position;
2960 
2961 			normalized_text = g_utf8_normalize (cluster_text,
2962 							    prevpos - attrpos,
2963 							    G_NORMALIZE_NFD);
2964 			len = g_utf8_strlen (normalized_text, -1);
2965 			html_engine_delete (engine);
2966 			if (len > 1) {
2967 				html_engine_insert_text (engine, normalized_text,
2968 							 g_utf8_offset_to_pointer (normalized_text, len - 1) - normalized_text);
2969 				html_cursor_jump_to (cursor, engine, self, offset);
2970 			}
2971 			if (normalized_text)
2972 				g_free (normalized_text);
2973 			/* restore a cursor position and offset for a split cursor */
2974 			engine->cursor->offset = offset;
2975 			engine->cursor->position = pos;
2976 		} else {
2977 			html_engine_delete (engine);
2978 		}
2979 	}
2980 
2981 	return retval;
2982 }
2983 
2984 static gint
html_text_get_right_edge_offset(HTMLObject * o,HTMLPainter * painter,gint offset)2985 html_text_get_right_edge_offset (HTMLObject *o,
2986                                  HTMLPainter *painter,
2987                                  gint offset)
2988 {
2989 	HTMLTextSlave *slave = html_text_get_slave_at_offset (HTML_TEXT (o), NULL, offset);
2990 
2991 	if (slave) {
2992 		return html_text_slave_get_right_edge_offset (slave, painter);
2993 	} else {
2994 		g_warning ("getting right edge offset from text object without slave(s)");
2995 
2996 		return HTML_TEXT (o)->text_len;
2997 	}
2998 }
2999 
3000 static gint
html_text_get_left_edge_offset(HTMLObject * o,HTMLPainter * painter,gint offset)3001 html_text_get_left_edge_offset (HTMLObject *o,
3002                                 HTMLPainter *painter,
3003                                 gint offset)
3004 {
3005 	HTMLTextSlave *slave = html_text_get_slave_at_offset (HTML_TEXT (o), NULL, offset);
3006 
3007 	if (slave) {
3008 		return html_text_slave_get_left_edge_offset (slave, painter);
3009 	} else {
3010 		g_warning ("getting left edge offset from text object without slave(s)");
3011 
3012 		return 0;
3013 	}
3014 }
3015 
3016 void
html_text_type_init(void)3017 html_text_type_init (void)
3018 {
3019 	html_text_class_init (&html_text_class, HTML_TYPE_TEXT, sizeof (HTMLText));
3020 }
3021 
3022 void
html_text_class_init(HTMLTextClass * klass,HTMLType type,guint object_size)3023 html_text_class_init (HTMLTextClass *klass,
3024                       HTMLType type,
3025                       guint object_size)
3026 {
3027 	HTMLObjectClass *object_class;
3028 
3029 	object_class = HTML_OBJECT_CLASS (klass);
3030 
3031 	html_object_class_init (object_class, type, object_size);
3032 
3033 	object_class->destroy = destroy;
3034 	object_class->copy = copy;
3035 	object_class->op_copy = op_copy;
3036 	object_class->op_cut = op_cut;
3037 	object_class->merge = object_merge;
3038 	object_class->split = object_split;
3039 	object_class->draw = draw;
3040 	object_class->accepts_cursor = accepts_cursor;
3041 	object_class->calc_size = html_text_real_calc_size;
3042 	object_class->calc_preferred_width = calc_preferred_width;
3043 	object_class->calc_min_width = calc_min_width;
3044 	object_class->fit_line = ht_fit_line;
3045 	object_class->get_cursor = get_cursor;
3046 	object_class->get_cursor_base = html_text_get_cursor_base;
3047 	object_class->save = save;
3048 	object_class->save_plain = save_plain;
3049 	object_class->check_point = check_point;
3050 	object_class->select_range = select_range;
3051 	object_class->get_length = get_length;
3052 	object_class->get_line_length = get_line_length;
3053 	object_class->set_link = set_link;
3054 	object_class->append_selection_string = append_selection_string;
3055 	object_class->get_url = get_url;
3056 	object_class->get_target = get_target;
3057 	object_class->cursor_forward = html_text_cursor_forward;
3058 	object_class->cursor_backward = html_text_cursor_backward;
3059 	object_class->cursor_right = html_text_cursor_right;
3060 	object_class->cursor_left = html_text_cursor_left;
3061 	object_class->backspace = html_text_backspace;
3062 	object_class->get_right_edge_offset = html_text_get_right_edge_offset;
3063 	object_class->get_left_edge_offset = html_text_get_left_edge_offset;
3064 
3065 	/* HTMLText methods.  */
3066 
3067 	klass->queue_draw = queue_draw;
3068 	klass->get_font_style = get_font_style;
3069 	klass->set_font_style = set_font_style;
3070 
3071 	parent_class = &html_object_class;
3072 }
3073 
3074 /* almost identical copy of glib's _g_utf8_make_valid() */
3075 static gchar *
_html_text_utf8_make_valid(const gchar * name,gint len)3076 _html_text_utf8_make_valid (const gchar *name,
3077                             gint len)
3078 {
3079 	GString *string;
3080 	const gchar *remainder, *invalid;
3081 	gint remaining_bytes, valid_bytes, total_bytes;
3082 
3083 	g_return_val_if_fail (name != NULL, NULL);
3084 
3085 	string = NULL;
3086 	remainder = name;
3087 	if (len == -1) {
3088 		remaining_bytes = strlen (name);
3089 	} else {
3090 		const gchar *start = name, *end = name;
3091 
3092 		while (len > 0) {
3093 			gunichar uc = g_utf8_get_char_validated (end, -1);
3094 
3095 			if (uc == (gunichar) -2 || uc == (gunichar) -1) {
3096 				end++;
3097 			} else if (uc == 0) {
3098 				break;
3099 			} else {
3100 				end = g_utf8_next_char (end);
3101 			}
3102 
3103 			len--;
3104 		}
3105 
3106 		remaining_bytes = end - start;
3107 	}
3108 
3109 	total_bytes = remaining_bytes;
3110 
3111 	while (remaining_bytes != 0) {
3112 		if (g_utf8_validate (remainder, remaining_bytes, &invalid))
3113 			break;
3114 		valid_bytes = invalid - remainder;
3115 
3116 		if (string == NULL)
3117 			string = g_string_sized_new (remaining_bytes);
3118 
3119 		g_string_append_len (string, remainder, valid_bytes);
3120 		/* append U+FFFD REPLACEMENT CHARACTER */
3121 		g_string_append (string, "\357\277\275");
3122 
3123 		remaining_bytes -= valid_bytes + 1;
3124 		remainder = invalid + 1;
3125 	}
3126 
3127 	if (string == NULL)
3128 		return g_strndup (name, total_bytes);
3129 
3130 	g_string_append (string, remainder);
3131 
3132 	g_assert (g_utf8_validate (string->str, -1, NULL));
3133 
3134 	return g_string_free (string, FALSE);
3135 }
3136 
3137 /**
3138  * html_text_sanitize:
3139  * @str_in: text string to sanitize (in)
3140  * @str_out: newly allocated text string sanitized (out)
3141  * @len: length of text, in characters (in/out). (A value of
3142  *       -1 on input means to use all characters in @str)
3143  *
3144  * Validates a UTF-8 string up to the given number of characters.
3145  *
3146  * Return value: number of bytes in the output value of @str
3147  **/
3148 gsize
html_text_sanitize(const gchar * str_in,gchar ** str_out,gint * len)3149 html_text_sanitize (const gchar *str_in,
3150                     gchar **str_out,
3151                     gint *len)
3152 {
3153 	g_return_val_if_fail (str_in != NULL, 0);
3154 	g_return_val_if_fail (str_out != NULL, 0);
3155 	g_return_val_if_fail (len != NULL, 0);
3156 
3157 	*str_out = _html_text_utf8_make_valid (str_in, *len);
3158 	g_return_val_if_fail (*str_out != NULL, 0);
3159 
3160 	*len = g_utf8_strlen (*str_out, -1);
3161 	return strlen (*str_out);
3162 }
3163 
3164 void
html_text_init(HTMLText * text,HTMLTextClass * klass,const gchar * str,gint len,GtkHTMLFontStyle font_style,HTMLColor * color)3165 html_text_init (HTMLText *text,
3166                 HTMLTextClass *klass,
3167                 const gchar *str,
3168                 gint len,
3169                 GtkHTMLFontStyle font_style,
3170                 HTMLColor *color)
3171 {
3172 	g_assert (color);
3173 
3174 	html_object_init (HTML_OBJECT (text), HTML_OBJECT_CLASS (klass));
3175 
3176 	text->text_bytes = html_text_sanitize (str, &text->text, &len);
3177 	text->text_len = len;
3178 
3179 	text->font_style    = font_style;
3180 	text->face          = NULL;
3181 	text->color         = color;
3182 	text->spell_errors  = NULL;
3183 	text->select_start  = 0;
3184 	text->select_length = 0;
3185 	text->pi            = NULL;
3186 	text->attr_list     = pango_attr_list_new ();
3187 	text->extra_attr_list = NULL;
3188 	text->links         = NULL;
3189 
3190 	html_color_ref (color);
3191 }
3192 
3193 HTMLObject *
html_text_new_with_len(const gchar * str,gint len,GtkHTMLFontStyle font,HTMLColor * color)3194 html_text_new_with_len (const gchar *str,
3195                         gint len,
3196                         GtkHTMLFontStyle font,
3197                         HTMLColor *color)
3198 {
3199 	HTMLText *text;
3200 
3201 	text = g_new (HTMLText, 1);
3202 
3203 	html_text_init (text, &html_text_class, str, len, font, color);
3204 
3205 	return HTML_OBJECT (text);
3206 }
3207 
3208 HTMLObject *
html_text_new(const gchar * text,GtkHTMLFontStyle font,HTMLColor * color)3209 html_text_new (const gchar *text,
3210                GtkHTMLFontStyle font,
3211                HTMLColor *color)
3212 {
3213 	return html_text_new_with_len (text, -1, font, color);
3214 }
3215 
3216 void
html_text_queue_draw(HTMLText * text,HTMLEngine * engine,guint offset,guint len)3217 html_text_queue_draw (HTMLText *text,
3218                       HTMLEngine *engine,
3219                       guint offset,
3220                       guint len)
3221 {
3222 	g_return_if_fail (text != NULL);
3223 	g_return_if_fail (engine != NULL);
3224 
3225 	(* HT_CLASS (text)->queue_draw) (text, engine, offset, len);
3226 }
3227 
3228 
3229 GtkHTMLFontStyle
html_text_get_font_style(const HTMLText * text)3230 html_text_get_font_style (const HTMLText *text)
3231 {
3232 	g_return_val_if_fail (text != NULL, GTK_HTML_FONT_STYLE_DEFAULT);
3233 
3234 	return (* HT_CLASS (text)->get_font_style) (text);
3235 }
3236 
3237 void
html_text_set_font_style(HTMLText * text,HTMLEngine * engine,GtkHTMLFontStyle style)3238 html_text_set_font_style (HTMLText *text,
3239                           HTMLEngine *engine,
3240                           GtkHTMLFontStyle style)
3241 {
3242 	g_return_if_fail (text != NULL);
3243 
3244 	(* HT_CLASS (text)->set_font_style) (text, engine, style);
3245 }
3246 
3247 void
html_text_set_font_face(HTMLText * text,HTMLFontFace * face)3248 html_text_set_font_face (HTMLText *text,
3249                          HTMLFontFace *face)
3250 {
3251 	if (text->face)
3252 		g_free (text->face);
3253 	text->face = g_strdup (face);
3254 }
3255 
3256 void
html_text_set_text(HTMLText * text,const gchar * new_text)3257 html_text_set_text (HTMLText *text,
3258                     const gchar *new_text)
3259 {
3260 	g_free (text->text);
3261 	text->text = NULL;
3262 	text->text_len = -1;
3263 	text->text_bytes = html_text_sanitize (new_text, &text->text,
3264 					       (gint *) &text->text_len);
3265 	html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
3266 }
3267 
3268 /* spell checking */
3269 
3270 #include "htmlinterval.h"
3271 
3272 static SpellError *
spell_error_new(guint off,guint len)3273 spell_error_new (guint off,
3274                  guint len)
3275 {
3276 	SpellError *se = g_new (SpellError, 1);
3277 
3278 	se->off = off;
3279 	se->len = len;
3280 
3281 	return se;
3282 }
3283 
3284 static void
spell_error_destroy(SpellError * se)3285 spell_error_destroy (SpellError *se)
3286 {
3287 	g_free (se);
3288 }
3289 
3290 void
html_text_spell_errors_clear(HTMLText * text)3291 html_text_spell_errors_clear (HTMLText *text)
3292 {
3293 	g_list_foreach (text->spell_errors, (GFunc) spell_error_destroy, NULL);
3294 	g_list_free    (text->spell_errors);
3295 	text->spell_errors = NULL;
3296 }
3297 
3298 void
html_text_spell_errors_clear_interval(HTMLText * text,HTMLInterval * i)3299 html_text_spell_errors_clear_interval (HTMLText *text,
3300                                        HTMLInterval *i)
3301 {
3302 	GList *cur, *cnext;
3303 	SpellError *se;
3304 	guint offset, len;
3305 
3306 	offset = html_interval_get_start  (i, HTML_OBJECT (text));
3307 	len    = html_interval_get_length (i, HTML_OBJECT (text));
3308 	cur    = text->spell_errors;
3309 
3310 	/* printf ("html_text_spell_errors_clear_interval %s %d %d\n", text->text, offset, len); */
3311 
3312 	while (cur) {
3313 		cnext = cur->next;
3314 		se    = (SpellError *) cur->data;
3315 		/* test overlap */
3316 		if (MAX (offset, se->off) <= MIN (se->off + se->len, offset + len)) {
3317 			text->spell_errors = g_list_remove_link (text->spell_errors, cur);
3318 			spell_error_destroy (se);
3319 			g_list_free (cur);
3320 		}
3321 		cur = cnext;
3322 	}
3323 }
3324 
3325 void
html_text_spell_errors_add(HTMLText * text,guint off,guint len)3326 html_text_spell_errors_add (HTMLText *text,
3327                             guint off,
3328                             guint len)
3329 {
3330 	text->spell_errors = merge_spell_errors (
3331 		text->spell_errors, g_list_prepend (
3332 		NULL, spell_error_new (off, len)));
3333 }
3334 
3335 guint
html_text_get_bytes(HTMLText * text)3336 html_text_get_bytes (HTMLText *text)
3337 {
3338 	return strlen (text->text);
3339 }
3340 
3341 gchar *
html_text_get_text(HTMLText * text,guint offset)3342 html_text_get_text (HTMLText *text,
3343                     guint offset)
3344 {
3345 	gchar *s = text->text;
3346 
3347 	while (offset-- && s && *s)
3348 		s = g_utf8_next_char (s);
3349 
3350 	return s;
3351 }
3352 
3353 guint
html_text_get_index(HTMLText * text,guint offset)3354 html_text_get_index (HTMLText *text,
3355                      guint offset)
3356 {
3357 	return html_text_get_text (text, offset) - text->text;
3358 }
3359 
3360 gunichar
html_text_get_char(HTMLText * text,guint offset)3361 html_text_get_char (HTMLText *text,
3362                     guint offset)
3363 {
3364 	gunichar uc;
3365 
3366 	uc = g_utf8_get_char (html_text_get_text (text, offset));
3367 	return uc;
3368 }
3369 
3370 /* magic links */
3371 
3372 struct _HTMLMagicInsertMatch
3373 {
3374 	const gchar *regex;
3375 	regex_t *preg;
3376 	const gchar *prefix;
3377 };
3378 
3379 typedef struct _HTMLMagicInsertMatch HTMLMagicInsertMatch;
3380 
3381 static HTMLMagicInsertMatch mim[] = {
3382 	/* prefixed expressions */
3383 	{ "(news|telnet|nntp|file|http|ftp|sftp|https|webcal)://([-a-z0-9]+(:[-a-z0-9]+)?@)?[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-a-z0-9_$.+!*(),;:@%&=?/~#']*[^]'.}>\\) ,?!;:\"]?)?", NULL, NULL },
3384 	{ "(sip|h323|callto):([-_a-z0-9.'\\+]+(:[0-9]{1,5})?(/[-_a-z0-9.']+)?)(@([-_a-z0-9.%=?]+|([0-9]{1,3}.){3}[0-9]{1,3})?)?(:[0-9]{1,5})?", NULL, NULL },
3385 	{ "mailto:[-_a-z0-9.'\\+]+@[-_a-z0-9.%=?]+", NULL, NULL },
3386 	/* not prefixed expression */
3387 	{ "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, "http://" },
3388 	{ "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, "ftp://" },
3389 	{ "[-_a-z0-9.'\\+]+@[-_a-z0-9.%=?]+", NULL, "mailto:" }
3390 };
3391 
3392 void
html_engine_init_magic_links(void)3393 html_engine_init_magic_links (void)
3394 {
3395 	gint i;
3396 
3397 	for (i = 0; i < G_N_ELEMENTS (mim); i++) {
3398 		mim[i].preg = g_new0 (regex_t, 1);
3399 		if (regcomp (mim[i].preg, mim[i].regex, REG_EXTENDED | REG_ICASE)) {
3400 			/* error */
3401 			g_free (mim[i].preg);
3402 			mim[i].preg = 0;
3403 		}
3404 	}
3405 }
3406 
3407 static void
paste_link(HTMLEngine * engine,HTMLText * text,gint so,gint eo,const gchar * prefix)3408 paste_link (HTMLEngine *engine,
3409             HTMLText *text,
3410             gint so,
3411             gint eo,
3412             const gchar *prefix)
3413 {
3414 	gchar *href;
3415 	gchar *base;
3416 
3417 	base = g_strndup (html_text_get_text (text, so), html_text_get_index (text, eo) - html_text_get_index (text, so));
3418 	href = (prefix) ? g_strconcat (prefix, base, NULL) : g_strdup (base);
3419 	g_free (base);
3420 
3421 	html_text_add_link (text, engine, href, NULL, so, eo);
3422 	g_free (href);
3423 }
3424 
3425 gboolean
html_text_magic_link(HTMLText * text,HTMLEngine * engine,guint offset)3426 html_text_magic_link (HTMLText *text,
3427                       HTMLEngine *engine,
3428                       guint offset)
3429 {
3430 	regmatch_t pmatch[2];
3431 	gint i;
3432 	gboolean rv = FALSE, exec = TRUE;
3433 	gint saved_position;
3434 	gunichar uc;
3435 	gchar *str, *cur;
3436 
3437 	if (!offset)
3438 		return FALSE;
3439 	offset--;
3440 
3441 	/* printf ("html_text_magic_link\n"); */
3442 
3443 	html_undo_level_begin (engine->undo, "Magic link", "Remove magic link");
3444 	saved_position = engine->cursor->position;
3445 
3446 	cur = str = html_text_get_text (text, offset);
3447 
3448 	/* check forward to ensure chars are < 0x80, could be removed once we have utf8 regex */
3449 	while (cur && *cur) {
3450 		cur = g_utf8_next_char (cur);
3451 		if (!*cur)
3452 			break;
3453 		uc = g_utf8_get_char (cur);
3454 		if (uc >= 0x80) {
3455 			exec = FALSE;
3456 			break;
3457 		} else if (uc == ' ' || uc == ENTITY_NBSP) {
3458 			break;
3459 		}
3460 	}
3461 
3462 	uc = g_utf8_get_char (str);
3463 	if (uc >= 0x80)
3464 		exec = FALSE;
3465 	while (exec && uc != ' ' && uc != ENTITY_NBSP && offset) {
3466 		str = g_utf8_prev_char (str);
3467 		uc = g_utf8_get_char (str);
3468 		if (uc >= 0x80)
3469 			exec = FALSE;
3470 		offset--;
3471 	}
3472 
3473 	if (uc == ' ' || uc == ENTITY_NBSP)
3474 		str = g_utf8_next_char (str);
3475 
3476 	if (exec) {
3477 		gboolean done = FALSE;
3478 		guint32 str_offset = 0, str_length = strlen (str);
3479 
3480 		while (!done) {
3481 			done = TRUE;
3482 			for (i = 0; i < G_N_ELEMENTS (mim); i++) {
3483 				if (mim[i].preg && !regexec (mim[i].preg, str + str_offset, 2, pmatch, 0)) {
3484 					paste_link (engine, text,
3485 						    g_utf8_pointer_to_offset (text->text, str + str_offset + pmatch[0].rm_so),
3486 						    g_utf8_pointer_to_offset (text->text, str + str_offset + pmatch[0].rm_eo), mim[i].prefix);
3487 						rv = TRUE;
3488 						str_offset += pmatch[0].rm_eo + 1;
3489 						done = str_offset >= str_length;
3490 						break;
3491 				}
3492 			}
3493 		}
3494 	}
3495 
3496 	html_undo_level_end (engine->undo, engine);
3497 	html_cursor_jump_to_position_no_spell (engine->cursor, engine, saved_position);
3498 
3499 	return rv;
3500 }
3501 
3502 /*
3503  * magic links end
3504  */
3505 
3506 gint
html_text_trail_space_width(HTMLText * text,HTMLPainter * painter)3507 html_text_trail_space_width (HTMLText *text,
3508                              HTMLPainter *painter)
3509 {
3510 	return text->text_len > 0 && html_text_get_char (text, text->text_len - 1) == ' '
3511 		? html_painter_get_space_width (painter, html_text_get_font_style (text), text->face) : 0;
3512 }
3513 
3514 void
html_text_append(HTMLText * text,const gchar * pstr,gint len)3515 html_text_append (HTMLText *text,
3516                   const gchar *pstr,
3517                   gint len)
3518 {
3519 	gchar *to_delete, *str = NULL;
3520 	guint bytes;
3521 
3522 	to_delete       = text->text;
3523 	bytes = html_text_sanitize (pstr, &str, &len);
3524 	text->text_len += len;
3525 	text->text      = g_malloc (text->text_bytes + bytes + 1);
3526 
3527 	memcpy (text->text, to_delete, text->text_bytes);
3528 	memcpy (text->text + text->text_bytes, str, bytes);
3529 	text->text_bytes += bytes;
3530 	text->text[text->text_bytes] = '\0';
3531 
3532 	g_free (to_delete);
3533 	g_free (str);
3534 
3535 	html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
3536 }
3537 
3538 void
html_text_append_link_full(HTMLText * text,gchar * url,gchar * target,gint start_index,gint end_index,gint start_offset,gint end_offset)3539 html_text_append_link_full (HTMLText *text,
3540                             gchar *url,
3541                             gchar *target,
3542                             gint start_index,
3543                             gint end_index,
3544                             gint start_offset,
3545                             gint end_offset)
3546 {
3547 	text->links = g_slist_prepend (text->links, html_link_new (url, target, start_index, end_index, start_offset, end_offset, FALSE));
3548 }
3549 
3550 static void
html_text_offsets_to_indexes(HTMLText * text,gint so,gint eo,gint * si,gint * ei)3551 html_text_offsets_to_indexes (HTMLText *text,
3552                               gint so,
3553                               gint eo,
3554                               gint *si,
3555                               gint *ei)
3556 {
3557 	*si = html_text_get_index (text, so);
3558 	*ei = g_utf8_offset_to_pointer (text->text + *si, eo - so) - text->text;
3559 }
3560 
3561 void
html_text_append_link(HTMLText * text,gchar * url,gchar * target,gint start_offset,gint end_offset)3562 html_text_append_link (HTMLText *text,
3563                        gchar *url,
3564                        gchar *target,
3565                        gint start_offset,
3566                        gint end_offset)
3567 {
3568 	gint start_index, end_index;
3569 
3570 	html_text_offsets_to_indexes (text, start_offset, end_offset, &start_index, &end_index);
3571 	html_text_append_link_full (text, url, target, start_index, end_index, start_offset, end_offset);
3572 }
3573 
3574 void
html_text_add_link_full(HTMLText * text,HTMLEngine * e,gchar * url,gchar * target,gint start_index,gint end_index,gint start_offset,gint end_offset)3575 html_text_add_link_full (HTMLText *text,
3576                          HTMLEngine *e,
3577                          gchar *url,
3578                          gchar *target,
3579                          gint start_index,
3580                          gint end_index,
3581                          gint start_offset,
3582                          gint end_offset)
3583 {
3584 	GSList *l, *prev = NULL;
3585 	Link *link;
3586 
3587 	cut_links_full (text, start_offset, end_offset, start_index, end_index, 0, 0);
3588 
3589 	if (text->links == NULL)
3590 		html_text_append_link_full (text, url, target, start_index, end_index, start_offset, end_offset);
3591 	else {
3592 		Link *plink = NULL, *new_link = html_link_new (url, target, start_index, end_index, start_offset, end_offset, FALSE);
3593 
3594 		for (l = text->links; new_link && l; l = l->next) {
3595 			link = (Link *) l->data;
3596 			if (new_link->start_offset >= link->end_offset) {
3597 				if (new_link->start_offset == link->end_offset && html_link_equal (link, new_link)) {
3598 					link->end_offset = end_offset;
3599 					link->end_index = end_index;
3600 					html_link_free (new_link);
3601 					new_link = NULL;
3602 				} else {
3603 					text->links = g_slist_insert_before (text->links, l, new_link);
3604 					link = new_link;
3605 					new_link = NULL;
3606 				}
3607 				if (plink && html_link_equal (plink, link) && plink->start_offset == link->end_offset) {
3608 					plink->start_offset = link->start_offset;
3609 					plink->start_index = link->start_index;
3610 					text->links = g_slist_remove (text->links, link);
3611 					html_link_free (link);
3612 					link = plink;
3613 				}
3614 				plink = link;
3615 				prev = l;
3616 			}
3617 		}
3618 
3619 		if (new_link && prev) {
3620 			if (prev->next)
3621 				text->links = g_slist_insert_before (text->links, prev->next, new_link);
3622 			else
3623 				text->links = g_slist_append (text->links, new_link);
3624 		} else if (new_link)
3625 			text->links = g_slist_prepend (text->links, new_link);
3626 	}
3627 
3628 	HTML_OBJECT (text)->change |= HTML_CHANGE_RECALC_PI;
3629 }
3630 
3631 void
html_text_add_link(HTMLText * text,HTMLEngine * e,gchar * url,gchar * target,gint start_offset,gint end_offset)3632 html_text_add_link (HTMLText *text,
3633                     HTMLEngine *e,
3634                     gchar *url,
3635                     gchar *target,
3636                     gint start_offset,
3637                     gint end_offset)
3638 {
3639 	gint start_index, end_index;
3640 
3641 	html_text_offsets_to_indexes (text, start_offset, end_offset, &start_index, &end_index);
3642 	html_text_add_link_full (text, e, url, target, start_index, end_index, start_offset, end_offset);
3643 }
3644 
3645 void
html_text_remove_links(HTMLText * text)3646 html_text_remove_links (HTMLText *text)
3647 {
3648 	if (text->links) {
3649 		free_links (text->links);
3650 		text->links = NULL;
3651 		html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_RECALC_PI);
3652 	}
3653 }
3654 
3655 /* HTMLTextSlave * */
3656 /* html_text_get_slave_at_offset (HTMLObject *o, gint offset) */
3657 /* { */
3658 /*	if (!o || (!HTML_IS_TEXT (o) && !HTML_IS_TEXT_SLAVE (o))) */
3659 /*		return NULL; */
3660 
3661 /*	if (HTML_IS_TEXT (o)) */
3662 /*		o = o->next; */
3663 
3664 /*	while (o && HTML_IS_TEXT_SLAVE (o)) { */
3665 /*		if (HTML_IS_TEXT_SLAVE (o) && HTML_TEXT_SLAVE (o)->posStart <= offset */
3666 /*		    && (offset < HTML_TEXT_SLAVE (o)->posStart + HTML_TEXT_SLAVE (o)->posLen */
3667 /*			|| (offset == HTML_TEXT_SLAVE (o)->posStart + HTML_TEXT_SLAVE (o)->posLen && HTML_TEXT_SLAVE (o)->owner->text_len == offset))) */
3668 /*			return HTML_TEXT_SLAVE (o); */
3669 /*		o = o->next; */
3670 /*	} */
3671 
3672 /*	return NULL; */
3673 /* } */
3674 
3675 Link *
html_text_get_link_slaves_at_offset(HTMLText * text,gint offset,HTMLTextSlave ** start,HTMLTextSlave ** end)3676 html_text_get_link_slaves_at_offset (HTMLText *text,
3677                                      gint offset,
3678                                      HTMLTextSlave **start,
3679                                      HTMLTextSlave **end)
3680 {
3681 	Link *link = html_text_get_link_at_offset (text, offset);
3682 
3683 	if (link) {
3684 		*start = html_text_get_slave_at_offset (text, NULL, link->start_offset);
3685 		*end = html_text_get_slave_at_offset (text, *start, link->end_offset);
3686 
3687 		if (*start && *end)
3688 			return link;
3689 	}
3690 
3691 	return NULL;
3692 }
3693 
3694 gboolean
html_text_get_link_rectangle(HTMLText * text,HTMLPainter * painter,gint offset,gint * x1,gint * y1,gint * x2,gint * y2)3695 html_text_get_link_rectangle (HTMLText *text,
3696                               HTMLPainter *painter,
3697                               gint offset,
3698                               gint *x1,
3699                               gint *y1,
3700                               gint *x2,
3701                               gint *y2)
3702 {
3703 	HTMLTextSlave *start;
3704 	HTMLTextSlave *end;
3705 	Link *link;
3706 
3707 	link = html_text_get_link_slaves_at_offset (text, offset, &start, &end);
3708 	if (link) {
3709 		gint xs, ys, xe, ye;
3710 
3711 		html_object_calc_abs_position (HTML_OBJECT (start), &xs, &ys);
3712 		xs += html_text_calc_part_width (text, painter, html_text_slave_get_text (start), start->posStart, link->start_offset - start->posStart, NULL, NULL);
3713 		ys -= HTML_OBJECT (start)->ascent;
3714 
3715 		html_object_calc_abs_position (HTML_OBJECT (end), &xe, &ye);
3716 		xe += HTML_OBJECT (end)->width;
3717 		xe -= html_text_calc_part_width (text, painter, text->text + link->end_index, link->end_offset, end->posStart + start->posLen - link->end_offset, NULL, NULL);
3718 		ye += HTML_OBJECT (end)->descent;
3719 
3720 		*x1 = MIN (xs, xe);
3721 		*y1 = MIN (ys, ye);
3722 		*x2 = MAX (xs, xe);
3723 		*y2 = MAX (ys, ye);
3724 
3725 		return TRUE;
3726 	}
3727 
3728 	return FALSE;
3729 }
3730 
3731 gboolean
html_text_prev_link_offset(HTMLText * text,gint * offset)3732 html_text_prev_link_offset (HTMLText *text,
3733                             gint *offset)
3734 {
3735 	GSList *l;
3736 
3737 	for (l = text->links; l; l = l->next) {
3738 		Link *link = (Link *) l->data;
3739 
3740 		if (link->start_offset <= *offset && *offset <= link->end_offset) {
3741 			if (l->next) {
3742 				*offset = ((Link *) l->next->data)->end_offset - 1;
3743 				return TRUE;
3744 			}
3745 			break;
3746 		}
3747 	}
3748 
3749 	return FALSE;
3750 }
3751 
3752 gboolean
html_text_next_link_offset(HTMLText * text,gint * offset)3753 html_text_next_link_offset (HTMLText *text,
3754                             gint *offset)
3755 {
3756 	GSList *l, *prev = NULL;
3757 
3758 	for (l = text->links; l; l = l->next) {
3759 		Link *link = (Link *) l->data;
3760 
3761 		if (link->start_offset <= *offset && *offset <= link->end_offset) {
3762 			if (prev) {
3763 				*offset = ((Link *) prev->data)->start_offset + 1;
3764 				return TRUE;
3765 			}
3766 			break;
3767 		}
3768 		prev = l;
3769 	}
3770 
3771 	return FALSE;
3772 }
3773 
3774 gboolean
html_text_first_link_offset(HTMLText * text,gint * offset)3775 html_text_first_link_offset (HTMLText *text,
3776                              gint *offset)
3777 {
3778 	if (text->links)
3779 		*offset = ((Link *) g_slist_last (text->links)->data)->start_offset + 1;
3780 
3781 	return text->links != NULL;
3782 }
3783 
3784 gboolean
html_text_last_link_offset(HTMLText * text,gint * offset)3785 html_text_last_link_offset (HTMLText *text,
3786                             gint *offset)
3787 {
3788 	if (text->links)
3789 		*offset = ((Link *) text->links->data)->end_offset - 1;
3790 
3791 	return text->links != NULL;
3792 }
3793 
3794 gchar *
html_text_get_link_text(HTMLText * text,gint offset)3795 html_text_get_link_text (HTMLText *text,
3796                          gint offset)
3797 {
3798 	Link *link = html_text_get_link_at_offset (text, offset);
3799 	gchar *start;
3800 
3801 	start = html_text_get_text (text, link->start_offset);
3802 
3803 	return g_strndup (start, g_utf8_offset_to_pointer (start, link->end_offset - link->start_offset) - start);
3804 }
3805 
3806 void
html_link_set_url_and_target(Link * link,gchar * url,gchar * target)3807 html_link_set_url_and_target (Link *link,
3808                               gchar *url,
3809                               gchar *target)
3810 {
3811 	if (!link)
3812 		return;
3813 
3814 	g_free (link->url);
3815 	g_free (link->target);
3816 
3817 	link->url = g_strdup (url);
3818 	link->target = g_strdup (target);
3819 }
3820 
3821 Link *
html_link_dup(Link * l)3822 html_link_dup (Link *l)
3823 {
3824 	Link *nl = g_new (Link, 1);
3825 
3826 	nl->url = g_strdup (l->url);
3827 	nl->target = g_strdup (l->target);
3828 	nl->start_offset = l->start_offset;
3829 	nl->end_offset = l->end_offset;
3830 	nl->start_index = l->start_index;
3831 	nl->end_index = l->end_index;
3832 	nl->is_visited = l->is_visited;
3833 
3834 	return nl;
3835 }
3836 
3837 void
html_link_free(Link * link)3838 html_link_free (Link *link)
3839 {
3840 	g_return_if_fail (link != NULL);
3841 
3842 	g_free (link->url);
3843 	g_free (link->target);
3844 	g_free (link);
3845 }
3846 
3847 gboolean
html_link_equal(Link * l1,Link * l2)3848 html_link_equal (Link *l1,
3849                  Link *l2)
3850 {
3851 	return l1->url && l2->url && !g_ascii_strcasecmp (l1->url, l2->url)
3852 		&& (l1->target == l2->target || (l1->target && l2->target && !g_ascii_strcasecmp (l1->target, l2->target)));
3853 }
3854 
3855 Link *
html_link_new(gchar * url,gchar * target,guint start_index,guint end_index,gint start_offset,gint end_offset,gboolean is_visited)3856 html_link_new (gchar *url,
3857                gchar *target,
3858                guint start_index,
3859                guint end_index,
3860                gint start_offset,
3861                gint end_offset,
3862                gboolean is_visited)
3863 {
3864 	Link *link = g_new0 (Link, 1);
3865 
3866 	link->url = g_strdup (url);
3867 	link->target = g_strdup (target);
3868 	link->start_offset = start_offset;
3869 	link->end_offset = end_offset;
3870 	link->start_index = start_index;
3871 	link->end_index = end_index;
3872 	link->is_visited = is_visited;
3873 
3874 	return link;
3875 }
3876 
3877 /* extended pango attributes */
3878 
3879 static PangoAttribute *
html_pango_attr_font_size_copy(const PangoAttribute * attr)3880 html_pango_attr_font_size_copy (const PangoAttribute *attr)
3881 {
3882 	HTMLPangoAttrFontSize *font_size_attr = (HTMLPangoAttrFontSize *) attr, *new_attr;
3883 
3884 	new_attr = (HTMLPangoAttrFontSize *) html_pango_attr_font_size_new (font_size_attr->style);
3885 	new_attr->attr_int.value = font_size_attr->attr_int.value;
3886 
3887 	return (PangoAttribute *) new_attr;
3888 }
3889 
3890 static void
html_pango_attr_font_size_destroy(PangoAttribute * attr)3891 html_pango_attr_font_size_destroy (PangoAttribute *attr)
3892 {
3893 	g_free (attr);
3894 }
3895 
3896 static gboolean
html_pango_attr_font_size_equal(const PangoAttribute * attr1,const PangoAttribute * attr2)3897 html_pango_attr_font_size_equal (const PangoAttribute *attr1,
3898                                  const PangoAttribute *attr2)
3899 {
3900 	const HTMLPangoAttrFontSize *font_size_attr1 = (const HTMLPangoAttrFontSize *) attr1;
3901 	const HTMLPangoAttrFontSize *font_size_attr2 = (const HTMLPangoAttrFontSize *) attr2;
3902 
3903 	return (font_size_attr1->style == font_size_attr2->style);
3904 }
3905 
3906 void
html_pango_attr_font_size_calc(HTMLPangoAttrFontSize * attr,HTMLEngine * e)3907 html_pango_attr_font_size_calc (HTMLPangoAttrFontSize *attr,
3908                                 HTMLEngine *e)
3909 {
3910 	gint size, base_size, real_size;
3911 
3912 	base_size = (attr->style & GTK_HTML_FONT_STYLE_FIXED) ? e->painter->font_manager.fix_size : e->painter->font_manager.var_size;
3913 	if ((attr->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0)
3914 		size = (attr->style & GTK_HTML_FONT_STYLE_SIZE_MASK) - GTK_HTML_FONT_STYLE_SIZE_3;
3915 	else
3916 		size = 0;
3917 	real_size = e->painter->font_manager.magnification * ((gdouble) base_size + (size > 0 ? (1 << size) : size) * base_size / 8.0);
3918 
3919 	attr->attr_int.value = real_size;
3920 }
3921 
3922 static const PangoAttrClass html_pango_attr_font_size_klass = {
3923 	PANGO_ATTR_SIZE,
3924 	html_pango_attr_font_size_copy,
3925 	html_pango_attr_font_size_destroy,
3926 	html_pango_attr_font_size_equal
3927 };
3928 
3929 PangoAttribute *
html_pango_attr_font_size_new(GtkHTMLFontStyle style)3930 html_pango_attr_font_size_new (GtkHTMLFontStyle style)
3931 {
3932 	HTMLPangoAttrFontSize *result = g_new (HTMLPangoAttrFontSize, 1);
3933 	result->attr_int.attr.klass = &html_pango_attr_font_size_klass;
3934 	result->style = style;
3935 
3936 	return (PangoAttribute *) result;
3937 }
3938 
3939 static gboolean
calc_font_size_filter(PangoAttribute * attr,gpointer data)3940 calc_font_size_filter (PangoAttribute *attr,
3941                        gpointer data)
3942 {
3943 	HTMLEngine *e = HTML_ENGINE (data);
3944 
3945 	if (attr->klass->type == PANGO_ATTR_SIZE)
3946 		html_pango_attr_font_size_calc ((HTMLPangoAttrFontSize *) attr, e);
3947 	else if (attr->klass->type == PANGO_ATTR_FAMILY) {
3948 		/* FIXME: this is not very nice. we set it here as it's only used when fonts changed.
3949 		 * once family in style is used again, that code must be updated */
3950 		PangoAttrString *sa = (PangoAttrString *) attr;
3951 		g_free (sa->value);
3952 		sa->value = g_strdup (e->painter->font_manager.fixed.face ? e->painter->font_manager.fixed.face : "Monospace");
3953 	}
3954 
3955 	return FALSE;
3956 }
3957 
3958 void
html_text_calc_font_size(HTMLText * text,HTMLEngine * e)3959 html_text_calc_font_size (HTMLText *text,
3960                           HTMLEngine *e)
3961 {
3962 	pango_attr_list_filter (text->attr_list, calc_font_size_filter, e);
3963 }
3964 
3965 static GtkHTMLFontStyle
style_from_attrs(PangoAttrIterator * iter)3966 style_from_attrs (PangoAttrIterator *iter)
3967 {
3968 	GtkHTMLFontStyle style = GTK_HTML_FONT_STYLE_DEFAULT;
3969 	GSList *list, *l;
3970 
3971 	list = pango_attr_iterator_get_attrs (iter);
3972 	for (l = list; l; l = l->next) {
3973 		PangoAttribute *attr = (PangoAttribute *) l->data;
3974 
3975 		switch (attr->klass->type) {
3976 		case PANGO_ATTR_WEIGHT:
3977 			style |= GTK_HTML_FONT_STYLE_BOLD;
3978 			break;
3979 		case PANGO_ATTR_UNDERLINE:
3980 			style |= GTK_HTML_FONT_STYLE_UNDERLINE;
3981 			break;
3982 		case PANGO_ATTR_STRIKETHROUGH:
3983 			style |= GTK_HTML_FONT_STYLE_STRIKEOUT;
3984 			break;
3985 		case PANGO_ATTR_STYLE:
3986 			style |= GTK_HTML_FONT_STYLE_ITALIC;
3987 			break;
3988 		case PANGO_ATTR_SIZE:
3989 			style |= ((HTMLPangoAttrFontSize *) attr)->style;
3990 			break;
3991 		case PANGO_ATTR_FAMILY:
3992 			style |= GTK_HTML_FONT_STYLE_FIXED;
3993 			break;
3994 		default:
3995 			break;
3996 		}
3997 	}
3998 
3999 	html_text_free_attrs (list);
4000 
4001 	return style;
4002 }
4003 
4004 GtkHTMLFontStyle
html_text_get_fontstyle_at_index(HTMLText * text,gint index)4005 html_text_get_fontstyle_at_index (HTMLText *text,
4006                                   gint index)
4007 {
4008 	GtkHTMLFontStyle style = GTK_HTML_FONT_STYLE_DEFAULT;
4009 	PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
4010 
4011 	if (iter) {
4012 		do {
4013 			gint start_index, end_index;
4014 
4015 			pango_attr_iterator_range (iter, &start_index, &end_index);
4016 			if (start_index <= index && index <= end_index) {
4017 				style |= style_from_attrs (iter);
4018 				break;
4019 			}
4020 		} while (pango_attr_iterator_next (iter));
4021 
4022 		pango_attr_iterator_destroy (iter);
4023 	}
4024 
4025 	return style;
4026 }
4027 
4028 GtkHTMLFontStyle
html_text_get_style_conflicts(HTMLText * text,GtkHTMLFontStyle style,gint start_index,gint end_index)4029 html_text_get_style_conflicts (HTMLText *text,
4030                                GtkHTMLFontStyle style,
4031                                gint start_index,
4032                                gint end_index)
4033 {
4034 	GtkHTMLFontStyle conflicts = GTK_HTML_FONT_STYLE_DEFAULT;
4035 	PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
4036 
4037 	if (iter) {
4038 		do {
4039 			gint iter_start_index, iter_end_index;
4040 
4041 			pango_attr_iterator_range (iter, &iter_start_index, &iter_end_index);
4042 			if (MAX (start_index, iter_start_index)  < MIN (end_index, iter_end_index))
4043 				conflicts |= style_from_attrs (iter) ^ style;
4044 			if (iter_start_index > end_index)
4045 				break;
4046 		} while (pango_attr_iterator_next (iter));
4047 
4048 		pango_attr_iterator_destroy (iter);
4049 	}
4050 
4051 	return conflicts;
4052 }
4053 
4054 void
html_text_change_attrs(PangoAttrList * attr_list,GtkHTMLFontStyle style,HTMLEngine * e,gint start_index,gint end_index,gboolean avoid_default_size)4055 html_text_change_attrs (PangoAttrList *attr_list,
4056                         GtkHTMLFontStyle style,
4057                         HTMLEngine *e,
4058                         gint start_index,
4059                         gint end_index,
4060                         gboolean avoid_default_size)
4061 {
4062 	PangoAttribute *attr;
4063 
4064 	/* style */
4065 	if (style & GTK_HTML_FONT_STYLE_BOLD) {
4066 		attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
4067 		attr->start_index = start_index;
4068 		attr->end_index = end_index;
4069 		pango_attr_list_change (attr_list, attr);
4070 	}
4071 
4072 	if (style & GTK_HTML_FONT_STYLE_ITALIC) {
4073 		attr = pango_attr_style_new (PANGO_STYLE_ITALIC);
4074 		attr->start_index = start_index;
4075 		attr->end_index = end_index;
4076 		pango_attr_list_change (attr_list, attr);
4077 	}
4078 
4079 	if (style & GTK_HTML_FONT_STYLE_UNDERLINE) {
4080 		attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
4081 		attr->start_index = start_index;
4082 		attr->end_index = end_index;
4083 		pango_attr_list_change (attr_list, attr);
4084 	}
4085 
4086 	if (style & GTK_HTML_FONT_STYLE_STRIKEOUT) {
4087 		attr = pango_attr_strikethrough_new (TRUE);
4088 		attr->start_index = start_index;
4089 		attr->end_index = end_index;
4090 		pango_attr_list_change (attr_list, attr);
4091 	}
4092 
4093 	if (style & GTK_HTML_FONT_STYLE_FIXED) {
4094 		attr = pango_attr_family_new (e->painter->font_manager.fixed.face ? e->painter->font_manager.fixed.face : "Monospace");
4095 		attr->start_index = start_index;
4096 		attr->end_index = end_index;
4097 		pango_attr_list_change (attr_list, attr);
4098 	}
4099 
4100 	if (!avoid_default_size
4101 	    || (((style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_DEFAULT)
4102 		&& ((style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3))
4103 	    || ((style & GTK_HTML_FONT_STYLE_FIXED) &&
4104 		e->painter->font_manager.fix_size != e->painter->font_manager.var_size)) {
4105 		attr = html_pango_attr_font_size_new (style);
4106 		html_pango_attr_font_size_calc ((HTMLPangoAttrFontSize *) attr, e);
4107 		attr->start_index = start_index;
4108 		attr->end_index = end_index;
4109 		pango_attr_list_change (attr_list, attr);
4110 	}
4111 }
4112 
4113 void
html_text_set_style_in_range(HTMLText * text,GtkHTMLFontStyle style,HTMLEngine * e,gint start_index,gint end_index)4114 html_text_set_style_in_range (HTMLText *text,
4115                               GtkHTMLFontStyle style,
4116                               HTMLEngine *e,
4117                               gint start_index,
4118                               gint end_index)
4119 {
4120 	html_text_change_attrs (text->attr_list, style, e, start_index, end_index, TRUE);
4121 }
4122 
4123 void
html_text_set_style(HTMLText * text,GtkHTMLFontStyle style,HTMLEngine * e)4124 html_text_set_style (HTMLText *text,
4125                      GtkHTMLFontStyle style,
4126                      HTMLEngine *e)
4127 {
4128 	html_text_set_style_in_range (text, style, e, 0, text->text_bytes);
4129 }
4130 
4131 static gboolean
unset_style_filter(PangoAttribute * attr,gpointer data)4132 unset_style_filter (PangoAttribute *attr,
4133                     gpointer data)
4134 {
4135 	GtkHTMLFontStyle style = GPOINTER_TO_INT (data);
4136 
4137 	switch (attr->klass->type) {
4138 	case PANGO_ATTR_WEIGHT:
4139 		if (style & GTK_HTML_FONT_STYLE_BOLD)
4140 			return TRUE;
4141 		break;
4142 	case PANGO_ATTR_STYLE:
4143 		if (style & GTK_HTML_FONT_STYLE_ITALIC)
4144 			return TRUE;
4145 		break;
4146 	case PANGO_ATTR_UNDERLINE:
4147 		if (style & GTK_HTML_FONT_STYLE_UNDERLINE)
4148 			return TRUE;
4149 		break;
4150 	case PANGO_ATTR_STRIKETHROUGH:
4151 		if (style & GTK_HTML_FONT_STYLE_STRIKEOUT)
4152 			return TRUE;
4153 		break;
4154 	case PANGO_ATTR_SIZE:
4155 		if (((HTMLPangoAttrFontSize *) attr)->style & style)
4156 			return TRUE;
4157 		break;
4158 	case PANGO_ATTR_FAMILY:
4159 		if (style & GTK_HTML_FONT_STYLE_FIXED)
4160 			return TRUE;
4161 		break;
4162 	default:
4163 		break;
4164 	}
4165 
4166 	return FALSE;
4167 }
4168 
4169 void
html_text_unset_style(HTMLText * text,GtkHTMLFontStyle style)4170 html_text_unset_style (HTMLText *text,
4171                        GtkHTMLFontStyle style)
4172 {
4173 	pango_attr_list_filter (text->attr_list, unset_style_filter, GINT_TO_POINTER (style));
4174 }
4175 
4176 static HTMLColor *
color_from_attrs(PangoAttrIterator * iter)4177 color_from_attrs (PangoAttrIterator *iter)
4178 {
4179 	HTMLColor *color = NULL;
4180 	GSList *list, *l;
4181 
4182 	list = pango_attr_iterator_get_attrs (iter);
4183 	for (l = list; l; l = l->next) {
4184 		PangoAttribute *attr = (PangoAttribute *) l->data;
4185 		PangoAttrColor *ca;
4186 
4187 		switch (attr->klass->type) {
4188 		case PANGO_ATTR_FOREGROUND:
4189 			ca = (PangoAttrColor *) attr;
4190 			color = html_color_new_from_rgb (ca->color.red, ca->color.green, ca->color.blue);
4191 			break;
4192 		default:
4193 			break;
4194 		}
4195 	}
4196 
4197 	html_text_free_attrs (list);
4198 
4199 	return color;
4200 }
4201 
4202 static HTMLColor *
html_text_get_first_color_in_range(HTMLText * text,HTMLEngine * e,gint start_index,gint end_index)4203 html_text_get_first_color_in_range (HTMLText *text,
4204                                     HTMLEngine *e,
4205                                     gint start_index,
4206                                     gint end_index)
4207 {
4208 	HTMLColor *color = NULL;
4209 	PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
4210 
4211 	if (iter) {
4212 		do {
4213 			gint iter_start_index, iter_end_index;
4214 
4215 			pango_attr_iterator_range (iter, &iter_start_index, &iter_end_index);
4216 			if (MAX (iter_start_index, start_index) <= MIN (iter_end_index, end_index)) {
4217 				color = color_from_attrs (iter);
4218 				break;
4219 			}
4220 		} while (pango_attr_iterator_next (iter));
4221 
4222 		pango_attr_iterator_destroy (iter);
4223 	}
4224 
4225 	if (!color) {
4226 		color = html_colorset_get_color (e->settings->color_set, HTMLTextColor);
4227 		html_color_ref (color);
4228 	}
4229 
4230 	return color;
4231 }
4232 
4233 HTMLColor *
html_text_get_color_at_index(HTMLText * text,HTMLEngine * e,gint index)4234 html_text_get_color_at_index (HTMLText *text,
4235                               HTMLEngine *e,
4236                               gint index)
4237 {
4238 	return html_text_get_first_color_in_range (text, e, index, index);
4239 }
4240 
4241 HTMLColor *
html_text_get_color(HTMLText * text,HTMLEngine * e,gint start_index)4242 html_text_get_color (HTMLText *text,
4243                      HTMLEngine *e,
4244                      gint start_index)
4245 {
4246 	return html_text_get_first_color_in_range (text, e, start_index, text->text_bytes);
4247 }
4248 
4249 void
html_text_set_color_in_range(HTMLText * text,HTMLColor * color,gint start_index,gint end_index)4250 html_text_set_color_in_range (HTMLText *text,
4251                               HTMLColor *color,
4252                               gint start_index,
4253                               gint end_index)
4254 {
4255 	PangoAttribute *attr = pango_attr_foreground_new (color->color.red, color->color.green, color->color.blue);
4256 
4257 	attr->start_index = start_index;
4258 	attr->end_index = end_index;
4259 	pango_attr_list_change (text->attr_list, attr);
4260 }
4261 
4262 void
html_text_set_color(HTMLText * text,HTMLColor * color)4263 html_text_set_color (HTMLText *text,
4264                      HTMLColor *color)
4265 {
4266 	html_text_set_color_in_range (text, color, 0, text->text_bytes);
4267 }
4268 
4269 HTMLDirection
html_text_direction_pango_to_html(PangoDirection pdir)4270 html_text_direction_pango_to_html (PangoDirection pdir)
4271 {
4272 	switch (pdir) {
4273 	case PANGO_DIRECTION_RTL:
4274 		return HTML_DIRECTION_RTL;
4275 	case PANGO_DIRECTION_LTR:
4276 		return HTML_DIRECTION_LTR;
4277 	default:
4278 		return HTML_DIRECTION_DERIVED;
4279 	}
4280 }
4281 
4282 void
html_text_change_set(HTMLText * text,HTMLChangeFlags flags)4283 html_text_change_set (HTMLText *text,
4284                       HTMLChangeFlags flags)
4285 {
4286 	HTMLObject *slave = HTML_OBJECT (text)->next;
4287 
4288 	for (; slave && HTML_IS_TEXT_SLAVE (slave) && HTML_TEXT_SLAVE (slave)->owner == text; slave = slave->next)
4289 		slave->change |= flags;
4290 
4291 	html_object_change_set (HTML_OBJECT (text), flags);
4292 }
4293 
4294 void
html_text_set_link_visited(HTMLText * text,gint offset,HTMLEngine * engine,gboolean is_visited)4295 html_text_set_link_visited (HTMLText *text,
4296                             gint offset,
4297                             HTMLEngine *engine,
4298                             gboolean is_visited)
4299 {
4300 	HTMLEngine *object_engine = html_object_engine (HTML_OBJECT (text),engine);
4301 	Link *link = html_text_get_link_at_offset (text,offset);
4302 
4303 	if (link) {
4304 		link->is_visited  = is_visited;
4305 		html_text_change_set (text, HTML_CHANGE_RECALC_PI);
4306 		html_text_queue_draw (text, object_engine, offset, 1);
4307 		html_engine_flush_draw_queue (object_engine);
4308 	}
4309 }
4310