source: trunk/third/gtkhtml3/src/htmltext.c @ 21460

Revision 21460, 86.3 KB checked in by ghudson, 20 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r21459, which included commits to RCS files with non-trunk default branches.
Line 
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., 59 Temple Place - Suite 330,
21   Boston, MA 02111-1307, 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
30#include <pango/pango.h>
31
32#include "htmltext.h"
33#include "htmlcolor.h"
34#include "htmlcolorset.h"
35#include "htmlclueflow.h"
36#include "htmlcursor.h"
37#include "htmlgdkpainter.h"
38#include "htmlplainpainter.h"
39#include "htmlprinter.h"
40#include "htmlengine.h"
41#include "htmlengine-edit.h"
42#include "htmlengine-edit-cut-and-paste.h"
43#include "htmlengine-save.h"
44#include "htmlentity.h"
45#include "htmlsettings.h"
46#include "htmltextslave.h"
47#include "htmlundo.h"
48
49HTMLTextClass html_text_class;
50static HTMLObjectClass *parent_class = NULL;
51static const PangoAttrClass html_pango_attr_font_size_klass;
52
53#define HT_CLASS(x) HTML_TEXT_CLASS (HTML_OBJECT (x)->klass)
54
55static SpellError * spell_error_new         (guint off, guint len);
56static void         spell_error_destroy     (SpellError *se);
57static void         move_spell_errors       (GList *spell_errors, guint offset, gint delta);
58static GList *      remove_spell_errors     (GList *spell_errors, guint offset, guint len);
59static void         remove_text_slaves      (HTMLObject *self);
60
61/* void
62debug_spell_errors (GList *se)
63{
64        for (;se;se = se->next)
65                printf ("SE: %4d, %4d\n", ((SpellError *) se->data)->off, ((SpellError *) se->data)->len);
66} */
67
68static inline gboolean
69is_in_the_save_cluev (HTMLObject *text, HTMLObject *o)
70{
71        return html_object_nth_parent (o, 2) == html_object_nth_parent (text, 2);
72}
73
74/* HTMLObject methods.  */
75
76HTMLTextPangoInfo *
77html_text_pango_info_new (gint n)
78{
79        HTMLTextPangoInfo *pi;
80
81        pi = g_new (HTMLTextPangoInfo, 1);
82        pi->n = n;
83        pi->entries = g_new0 (HTMLTextPangoInfoEntry, n);
84        pi->attrs = NULL;
85
86        return pi;
87}
88
89void
90html_text_pango_info_destroy (HTMLTextPangoInfo *pi)
91{
92        gint i;
93
94        for (i = 0; i < pi->n; i ++) {
95                pango_item_free (pi->entries [i].item);
96                g_free (pi->entries [i].widths);
97        }
98        g_free (pi->entries);
99        g_free (pi->attrs);
100        g_free (pi);
101}
102
103static void
104pango_info_destroy (HTMLText *text)
105{
106        if (text->pi) {
107                html_text_pango_info_destroy (text->pi);
108                text->pi = NULL;
109        }
110}
111
112static void
113free_links (GSList *list)
114{
115        if (list) {
116                GSList *l;
117
118                for (l = list; l; l = l->next)
119                        html_link_free ((Link *) l->data);
120                g_slist_free (list);
121        }
122}
123
124void
125html_text_free_attrs (GSList *attrs)
126{
127        if (attrs) {
128                GSList *l;
129
130                for (l = attrs; l; l = l->next)
131                        pango_attribute_destroy ((PangoAttribute *) l->data);
132                g_slist_free (attrs);
133        }
134}
135
136static void
137copy (HTMLObject *s,
138      HTMLObject *d)
139{
140        HTMLText *src  = HTML_TEXT (s);
141        HTMLText *dest = HTML_TEXT (d);
142        GList *cur;
143        GSList *csl;
144
145        (* HTML_OBJECT_CLASS (parent_class)->copy) (s, d);
146
147        dest->text = g_strdup (src->text);
148        dest->text_len      = src->text_len;
149        dest->text_bytes    = src->text_bytes;
150        dest->font_style    = src->font_style;
151        dest->face          = g_strdup (src->face);
152        dest->color         = src->color;
153        dest->select_start  = 0;
154        dest->select_length = 0;
155        dest->attr_list     = pango_attr_list_copy (src->attr_list);
156        dest->extra_attr_list = src->extra_attr_list ? pango_attr_list_copy (src->extra_attr_list) : NULL;
157
158        html_color_ref (dest->color);
159
160        dest->spell_errors = g_list_copy (src->spell_errors);
161        cur = dest->spell_errors;
162        while (cur) {
163                SpellError *se = (SpellError *) cur->data;
164                cur->data = spell_error_new (se->off, se->len);
165                cur = cur->next;
166        }
167
168        dest->links = g_slist_copy (src->links);
169
170        for (csl = dest->links; csl; csl = csl->next)
171                csl->data = html_link_dup ((Link *) csl->data);
172
173        dest->pi = NULL;
174}
175
176/* static void
177debug_word_width (HTMLText *t)
178{
179        guint i;
180
181        printf ("words: %d | ", t->words);
182        for (i = 0; i < t->words; i ++)
183                printf ("%d ", t->word_width [i]);
184        printf ("\n");
185}
186
187static void
188word_get_position (HTMLText *text, guint off, guint *word_out, guint *left_out, guint *right_out)
189{
190        const gchar *s, *ls;
191        guint coff, loff;
192
193        coff      = 0;
194        *word_out = 0;
195        s         = text->text;
196        do {
197                ls    = s;
198                loff  = coff;
199                s     = strchr (s, ' ');
200                coff += s ? g_utf8_pointer_to_offset (ls, s) : g_utf8_strlen (ls, -1);
201                (*word_out) ++;
202                if (s)
203                        s ++;
204        } while (s && coff < off);
205
206        *left_out  = off - loff;
207        *right_out = coff - off;
208
209        printf ("get position w: %d l: %d r: %d\n", *word_out, *left_out, *right_out);
210} */
211
212static gboolean
213cut_attr_list_filter (PangoAttribute *attr, gpointer data)
214{
215        PangoAttribute *range = (PangoAttribute *) data;
216        gint delta;
217
218        if (attr->start_index >= range->start_index && attr->end_index <= range->end_index)
219                return TRUE;
220
221        delta = range->end_index - range->start_index;
222        if (attr->start_index > range->end_index) {
223                attr->start_index -= delta;
224                attr->end_index -= delta;
225        } else if (attr->start_index > range->start_index) {
226                attr->start_index = range->start_index;
227                attr->end_index -= delta;
228                if (attr->end_index <= attr->start_index)
229                        return TRUE;
230        } else if (attr->end_index >= range->end_index)
231                attr->end_index -= delta;
232        else if (attr->end_index >= range->start_index)
233                attr->end_index = range->start_index;
234
235        return FALSE;
236}
237
238static void
239cut_attr_list_list (PangoAttrList *attr_list, gint begin_index, gint end_index)
240{
241        PangoAttrList *removed;
242        PangoAttribute range;
243
244        range.start_index = begin_index;
245        range.end_index = end_index;
246
247        removed = pango_attr_list_filter (attr_list, cut_attr_list_filter, &range);
248        if (removed)
249                pango_attr_list_unref (removed);
250}
251
252static void
253cut_attr_list (HTMLText *text, gint begin_index, gint end_index)
254{
255        cut_attr_list_list (text->attr_list, begin_index, end_index);
256        if (text->extra_attr_list)
257                cut_attr_list_list (text->extra_attr_list, begin_index, end_index);
258}
259
260static void
261cut_links_full (HTMLText *text, int start_offset, int end_offset, int start_index, int end_index, int shift_offset, int shift_index)
262{
263        GSList *l, *next;
264        Link *link;
265
266        for (l = text->links; l; l = next) {
267                next = l->next;
268                link = (Link *) l->data;
269
270                if (start_offset <= link->start_offset && link->end_offset <= end_offset) {
271                        html_link_free (link);
272                        text->links = g_slist_delete_link (text->links, l);
273                } else if (end_offset <= link->start_offset) {
274                        link->start_offset -= shift_offset;
275                        link->start_index -= shift_index;
276                        link->end_offset -= shift_offset;
277                        link->end_index -= shift_index;
278                } else if (start_offset <= link->start_offset)  {
279                        link->start_offset = end_offset - shift_offset;
280                        link->end_offset -= shift_offset;
281                        link->start_index = end_index - shift_index;
282                        link->end_index -= shift_index;
283                } else if (end_offset <= link->end_offset) {
284                        if (shift_offset > 0) {
285                                link->end_offset -= shift_offset;
286                                link->end_index -= shift_index;
287                        } else {
288                                if (link->end_offset == end_offset) {
289                                        link->end_offset = start_offset;
290                                        link->end_index = start_index;
291                                } else if (link->start_offset == start_offset) {
292                                        link->start_offset = end_offset;
293                                        link->start_index = end_index;
294                                } else {
295                                        Link *dup = html_link_dup (link);
296
297                                        link->start_offset = end_offset;
298                                        link->start_index = end_index;
299                                        dup->end_offset = start_offset;
300                                        dup->end_index = start_index;
301
302                                        l = g_slist_prepend (l, dup);
303                                        next = l->next->next;
304                                }
305                        }
306                } else if (start_offset < link->end_offset) {
307                        link->end_offset = start_offset;
308                        link->end_index = start_index;
309                }
310        }
311}
312
313static void
314cut_links (HTMLText *text, int start_offset, int end_offset, int start_index, int end_index)
315{
316        cut_links_full (text, start_offset, end_offset, start_index, end_index, end_offset - start_offset, end_index - start_index);
317}
318
319HTMLObject *
320html_text_op_copy_helper (HTMLText *text, GList *from, GList *to, guint *len)
321{
322        HTMLObject *rv;
323        HTMLText *rvt;
324        gchar *tail, *nt;
325        gint begin, end, begin_index, end_index;
326
327        begin = (from) ? GPOINTER_TO_INT (from->data) : 0;
328        end   = (to)   ? GPOINTER_TO_INT (to->data)   : text->text_len;
329
330        tail = html_text_get_text (text, end);
331        begin_index = html_text_get_index (text, begin);
332        end_index = tail - text->text;
333
334        *len += end - begin;
335
336        rv = html_object_dup (HTML_OBJECT (text));
337        rvt = HTML_TEXT (rv);
338        rvt->text_len = end - begin;
339        rvt->text_bytes = end_index - begin_index;
340        nt = g_strndup (rvt->text + begin_index, rvt->text_bytes);
341        g_free (rvt->text);
342        rvt->text = nt;
343
344        rvt->spell_errors = remove_spell_errors (rvt->spell_errors, 0, begin);
345        rvt->spell_errors = remove_spell_errors (rvt->spell_errors, end, text->text_len - end);
346
347        if (end_index < text->text_bytes)
348                cut_attr_list (rvt, end_index, text->text_bytes);
349        if (begin_index > 0)
350                cut_attr_list (rvt, 0, begin_index);
351        if (end < text->text_len)
352                cut_links (rvt, end, text->text_len, end_index, text->text_bytes);
353        if (begin > 0)
354                cut_links (rvt, 0, begin, 0, begin_index);
355
356        return rv;
357}
358
359HTMLObject *
360html_text_op_cut_helper (HTMLText *text, HTMLEngine *e, GList *from, GList *to, GList *left, GList *right, guint *len)
361{
362        HTMLObject *rv;
363        HTMLText *rvt;
364        gint begin, end;
365
366        begin = (from) ? GPOINTER_TO_INT (from->data) : 0;
367        end   = (to)   ? GPOINTER_TO_INT (to->data)   : text->text_len;
368
369        g_assert (begin <= end);
370        g_assert (end <= text->text_len);
371
372        /* printf ("before cut '%s'\n", text->text);
373           debug_word_width (text); */
374
375        remove_text_slaves (HTML_OBJECT (text));
376        if (!html_object_could_remove_whole (HTML_OBJECT (text), from, to, left, right) || begin || end < text->text_len) {
377                gchar *nt, *tail;
378                gint begin_index, end_index;
379
380                if (begin == end)
381                        return HTML_OBJECT (html_text_new_with_len ("", 0, text->font_style, text->color));
382
383                rv = html_object_dup (HTML_OBJECT (text));
384                rvt = HTML_TEXT (rv);
385
386                tail = html_text_get_text (text, end);
387                begin_index = html_text_get_index (text, begin);
388                end_index = tail - text->text;
389                text->text_bytes -= tail - (text->text + begin_index);
390                text->text [begin_index] = 0;
391                cut_attr_list (text, begin_index, end_index);
392                if (end_index < rvt->text_bytes)
393                        cut_attr_list (rvt, end_index, rvt->text_bytes);
394                if (begin_index > 0)
395                        cut_attr_list (rvt, 0, begin_index);
396                cut_links (text, begin, end, begin_index, end_index);
397                if (end < rvt->text_len)
398                        cut_links (rvt, end, rvt->text_len, end_index, rvt->text_bytes);
399                if (begin > 0)
400                        cut_links (rvt, 0, begin, 0, begin_index);
401                nt = g_strconcat (text->text, tail, NULL);
402                g_free (text->text);
403
404                rvt->spell_errors = remove_spell_errors (rvt->spell_errors, 0, begin);
405                rvt->spell_errors = remove_spell_errors (rvt->spell_errors, end, text->text_len - end);
406                move_spell_errors (rvt->spell_errors, begin, -begin);
407
408                text->text = nt;
409                text->text_len -= end - begin;
410                *len           += end - begin;
411
412                nt = g_strndup (rvt->text + begin_index, end_index - begin_index);
413                g_free (rvt->text);
414                rvt->text = nt;
415                rvt->text_len = end - begin;
416                rvt->text_bytes = end_index - begin_index;
417
418                text->spell_errors = remove_spell_errors (text->spell_errors, begin, end - begin);
419                move_spell_errors (text->spell_errors, end, - (end - begin));
420
421                html_text_convert_nbsp (text, TRUE);
422                html_text_convert_nbsp (rvt, TRUE);
423                pango_info_destroy (text);
424        } else {
425                text->spell_errors = remove_spell_errors (text->spell_errors, 0, text->text_len);
426                html_object_move_cursor_before_remove (HTML_OBJECT (text), e);
427                html_object_change_set (HTML_OBJECT (text)->parent, HTML_CHANGE_ALL_CALC);
428                /* force parent redraw */
429                HTML_OBJECT (text)->parent->width = 0;
430                html_object_remove_child (HTML_OBJECT (text)->parent, HTML_OBJECT (text));
431
432                rv    = HTML_OBJECT (text);
433                *len += text->text_len;
434        }
435
436        html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL_CALC);
437
438        /* printf ("after cut '%s'\n", text->text);
439           debug_word_width (text); */
440
441        return rv;
442}
443
444static HTMLObject *
445op_copy (HTMLObject *self, HTMLObject *parent, HTMLEngine *e, GList *from, GList *to, guint *len)
446{
447        return html_text_op_copy_helper (HTML_TEXT (self), from, to, len);
448}
449
450static HTMLObject *
451op_cut (HTMLObject *self, HTMLEngine *e, GList *from, GList *to, GList *left, GList *right, guint *len)
452{
453        return html_text_op_cut_helper (HTML_TEXT (self), e, from, to, left, right, len);
454}
455
456static void
457merge_links (HTMLText *t1, HTMLText *t2)
458{
459        Link *tail, *head;
460        GSList *l;
461
462        if (t2->links) {
463                for (l = t2->links; l; l = l->next) {
464                        Link *link = (Link *) l->data;
465
466                        link->start_offset += t1->text_len;
467                        link->start_index += t1->text_bytes;
468                        link->end_offset += t1->text_len;
469                        link->end_index += t1->text_bytes;
470                }
471
472                if (t1->links) {
473                        head = (Link *) t1->links->data;
474                        tail = (Link *) g_slist_last (t2->links)->data;
475
476                        if (tail->start_offset == head->end_offset && html_link_equal (head, tail)) {
477                                tail->start_offset = head->start_offset;
478                                tail->start_index = head->start_index;
479                                html_link_free (head);
480                                t1->links = g_slist_delete_link (t1->links, t1->links);
481                        }
482                }
483
484                t1->links = g_slist_concat (t2->links, t1->links);
485                t2->links = NULL;
486        }
487}
488
489static gboolean
490object_merge (HTMLObject *self, HTMLObject *with, HTMLEngine *e, GList **left, GList **right, HTMLCursor *cursor)
491{
492        HTMLText *t1, *t2;
493        gchar *to_free;
494
495        t1 = HTML_TEXT (self);
496        t2 = HTML_TEXT (with);
497
498        /* printf ("merge '%s' '%s'\n", t1->text, t2->text); */
499
500        /* merge_word_width (t1, t2, e->painter); */
501
502        if (e->cursor->object == with) {
503                e->cursor->object  = self;
504                e->cursor->offset += t1->text_len;
505        }
506
507        /* printf ("--- before merge\n");
508           debug_spell_errors (t1->spell_errors);
509           printf ("---\n");
510           debug_spell_errors (t2->spell_errors);
511           printf ("---\n");
512        */
513        move_spell_errors (t2->spell_errors, 0, t1->text_len);
514        t1->spell_errors = g_list_concat (t1->spell_errors, t2->spell_errors);
515        t2->spell_errors = NULL;
516
517        pango_attr_list_splice (t1->attr_list, t2->attr_list, t1->text_bytes, t2->text_bytes);
518        if (t2->extra_attr_list) {
519                if (!t1->extra_attr_list)
520                        t1->extra_attr_list = pango_attr_list_new ();
521                pango_attr_list_splice (t1->extra_attr_list, t2->extra_attr_list, t1->text_bytes, t2->text_bytes);
522        }
523        merge_links (t1, t2);
524
525        to_free       = t1->text;
526        t1->text      = g_strconcat (t1->text, t2->text, NULL);
527        t1->text_len += t2->text_len;
528        t1->text_bytes += t2->text_bytes;
529        g_free (to_free);
530        html_text_convert_nbsp (t1, TRUE);
531        html_object_change_set (self, HTML_CHANGE_ALL_CALC);
532        pango_info_destroy (t1);
533        pango_info_destroy (t2);
534
535        /* html_text_request_word_width (t1, e->painter); */
536        /* printf ("merged '%s'\n", t1->text);
537           printf ("--- after merge\n");
538           debug_spell_errors (t1->spell_errors);
539           printf ("---\n"); */
540
541        return TRUE;
542}
543
544static gboolean
545split_attrs_filter_head (PangoAttribute *attr, gpointer data)
546{
547        gint index = GPOINTER_TO_INT (data);
548
549        if (attr->start_index >= index)
550                return TRUE;
551        else if (attr->end_index > index)
552                attr->end_index = index;
553
554        return FALSE;
555}
556
557static gboolean
558split_attrs_filter_tail (PangoAttribute *attr, gpointer data)
559{
560        gint index = GPOINTER_TO_INT (data);
561       
562        if (attr->end_index <= index)
563                return TRUE;
564
565        if (attr->start_index > index)
566                attr->start_index -= index;
567        else
568                attr->start_index = 0;
569        attr->end_index -= index;
570
571        return FALSE;
572}
573
574static void
575split_attrs (HTMLText *t1, HTMLText *t2, gint index)
576{
577        PangoAttrList *delete;
578
579        delete = pango_attr_list_filter (t1->attr_list, split_attrs_filter_head, GINT_TO_POINTER (index));
580        if (delete)
581                pango_attr_list_unref (delete);
582        if (t1->extra_attr_list) {
583                delete = pango_attr_list_filter (t1->extra_attr_list, split_attrs_filter_head, GINT_TO_POINTER (index));
584                if (delete)
585                        pango_attr_list_unref (delete);
586        }
587        delete = pango_attr_list_filter (t2->attr_list, split_attrs_filter_tail, GINT_TO_POINTER (index));
588        if (delete)
589                pango_attr_list_unref (delete);
590        if (t2->extra_attr_list) {
591                delete = pango_attr_list_filter (t2->extra_attr_list, split_attrs_filter_tail, GINT_TO_POINTER (index));
592                if (delete)
593                        pango_attr_list_unref (delete);
594        }
595}
596
597static void
598split_links (HTMLText *t1, HTMLText *t2, gint offset, gint index)
599{
600        GSList *l, *prev = NULL;
601
602        for (l = t1->links; l; l = l->next) {
603                Link *link = (Link *) l->data;
604
605                if (link->start_offset < offset) {
606                        if (link->end_offset > offset) {
607                                link->end_offset = offset;
608                                link->end_index = index;
609                        }
610
611                        if (prev) {
612                                prev->next = NULL;
613                                free_links (t1->links);
614                        }
615                        t1->links = l;
616                        break;
617                }
618                prev = l;
619
620                if (!l->next) {
621                        free_links (t1->links);
622                        t1->links = NULL;
623                        break;
624                }
625        }
626
627        prev = NULL;
628        for (l = t2->links; l; l = l->next) {
629                Link *link = (Link *) l->data;
630
631                if (link->start_offset < offset) {
632                        if (link->end_offset > offset) {
633                                link->start_offset = offset;
634                                link->start_index = index;
635                                prev = l;
636                                l = l->next;
637                        }
638                        if (prev) {
639                                prev->next = NULL;
640                                free_links (l);
641                        } else {
642                                free_links (t2->links);
643                                t2->links = NULL;
644                        }
645                        break;
646                }
647                prev = l;
648        }
649
650        for (l = t2->links; l; l = l->next) {
651                Link *link = (Link *) l->data;
652
653                link->start_offset -= offset;
654                link->start_index -= index;
655                link->end_offset -= offset;
656                link->end_index -= index;
657        }
658}
659
660static void
661object_split (HTMLObject *self, HTMLEngine *e, HTMLObject *child, gint offset, gint level, GList **left, GList **right)
662{
663        HTMLObject *dup, *prev;
664        HTMLText *t1, *t2;
665        gchar *tt;
666        gint split_index;
667
668        g_assert (self->parent);
669
670        html_clue_remove_text_slaves (HTML_CLUE (self->parent));
671
672        t1              = HTML_TEXT (self);
673        dup             = html_object_dup (self);
674        tt              = t1->text;
675        split_index     = html_text_get_index (t1, offset);
676        t1->text        = g_strndup (tt, split_index);
677        t1->text_len    = offset;
678        t1->text_bytes  = split_index;
679        g_free (tt);
680        html_text_convert_nbsp (t1, TRUE);
681
682        t2              = HTML_TEXT (dup);
683        tt              = t2->text;
684        t2->text        = html_text_get_text (t2, offset);
685        t2->text_len   -= offset;
686        t2->text_bytes -= split_index;
687        split_attrs (t1, t2, split_index);
688        split_links (t1, t2, offset, split_index);
689        if (!html_text_convert_nbsp (t2, FALSE))
690                t2->text = g_strdup (t2->text);
691        g_free (tt);
692
693        html_clue_append_after (HTML_CLUE (self->parent), dup, self);
694
695        prev = self->prev;
696        if (t1->text_len == 0 && prev && html_object_merge (prev, self, e, NULL, NULL, NULL))
697                self = prev;
698
699        if (t2->text_len == 0 && dup->next)
700                html_object_merge (dup, dup->next, e, NULL, NULL, NULL);
701
702        /* printf ("--- before split offset %d dup len %d\n", offset, HTML_TEXT (dup)->text_len);
703           debug_spell_errors (HTML_TEXT (self)->spell_errors); */
704
705        HTML_TEXT (self)->spell_errors = remove_spell_errors (HTML_TEXT (self)->spell_errors,
706                                                              offset, HTML_TEXT (dup)->text_len);
707        HTML_TEXT (dup)->spell_errors  = remove_spell_errors (HTML_TEXT (dup)->spell_errors,
708                                                              0, HTML_TEXT (self)->text_len);
709        move_spell_errors   (HTML_TEXT (dup)->spell_errors, 0, - HTML_TEXT (self)->text_len);
710
711        /* printf ("--- after split\n");
712           printf ("left\n");
713           debug_spell_errors (HTML_TEXT (self)->spell_errors);
714           printf ("right\n");
715           debug_spell_errors (HTML_TEXT (dup)->spell_errors);
716           printf ("---\n");
717        */
718
719        *left  = g_list_prepend (*left, self);
720        *right = g_list_prepend (*right, dup);
721
722        html_object_change_set (self, HTML_CHANGE_ALL_CALC);
723        html_object_change_set (dup,  HTML_CHANGE_ALL_CALC);
724
725        pango_info_destroy (HTML_TEXT (self));
726
727        level--;
728        if (level)
729                html_object_split (self->parent, e, dup, 0, level, left, right);
730}
731
732static gboolean
733html_text_real_calc_size (HTMLObject *self, HTMLPainter *painter, GList **changed_objs)
734{
735        self->width = 0;
736        html_object_calc_preferred_width (self, painter);
737
738        return FALSE;
739}
740
741static const gchar *
742html_utf8_strnchr (const gchar *s, gchar c, gint len, gint *offset)
743{
744        const gchar *res = NULL;
745
746        *offset = 0;
747        while (s && *s && *offset < len) {
748                if (*s == c) {
749                        res = s;
750                        break;
751                }
752                s = g_utf8_next_char (s);
753                (*offset) ++;
754        }
755
756        return res;
757}
758
759gint
760html_text_text_line_length (const gchar *text, gint *line_offset, guint len, gint *tabs)
761{
762        const gchar *tab, *found_tab;
763        gint cl, l, skip, sum_skip;
764
765        /* printf ("lo: %d len: %d t: '%s'\n", line_offset, len, text); */
766        if (tabs)
767                *tabs = 0;
768        l = 0;
769        sum_skip = skip = 0;
770        tab = text;
771        while (tab && (found_tab = html_utf8_strnchr (tab, '\t', len - l, &cl)) && l < len) {
772                l   += cl;
773                if (l >= len)
774                        break;
775                if (*line_offset != -1) {
776                        *line_offset  += cl;
777                        skip = 8 - (*line_offset % 8);
778                }
779                tab  = found_tab + 1;
780
781                *line_offset  += skip;
782                if (*line_offset != -1)
783                        sum_skip += skip - 1;
784                l ++;
785                if (tabs)
786                        (*tabs) ++;
787        }
788
789        if (*line_offset != -1)
790                (*line_offset) += len - l;
791        /* printf ("ll: %d\n", len + sum_skip); */
792
793        return len + sum_skip;
794}
795
796static guint
797get_line_length (HTMLObject *self, HTMLPainter *p, gint line_offset)
798{
799        return html_clueflow_tabs (HTML_CLUEFLOW (self->parent), p)
800                ? html_text_text_line_length (HTML_TEXT (self)->text, &line_offset, HTML_TEXT (self)->text_len, NULL)
801                : HTML_TEXT (self)->text_len;
802}
803
804gint
805html_text_get_line_offset (HTMLText *text, HTMLPainter *painter, gint offset)
806{
807        gint line_offset = -1;
808
809        if (html_clueflow_tabs (HTML_CLUEFLOW (HTML_OBJECT (text)->parent), painter)) {
810                line_offset = html_clueflow_get_line_offset (HTML_CLUEFLOW (HTML_OBJECT (text)->parent),
811                                                             painter, HTML_OBJECT (text));
812                if (offset) {
813                        gchar *s = text->text;
814
815                        while (offset > 0 && s && *s) {
816                                if (*s == '\t')
817                                        line_offset += 8 - (line_offset % 8);
818                                else
819                                        line_offset ++;
820                                s = g_utf8_next_char (s);
821                                offset --;
822                        }
823                }
824        }
825
826        return line_offset;
827}
828
829gint
830html_text_get_item_index (HTMLText *text, HTMLPainter *painter, gint offset, gint *item_offset)
831{
832        HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
833        gint idx = 0;
834
835        while (idx < pi->n - 1 && offset >= pi->entries [idx].item->num_chars) {
836                offset -= pi->entries [idx].item->num_chars;
837                idx ++;
838        }
839
840        *item_offset = offset;
841
842        return idx;
843}
844
845static void
846update_asc_dsc (HTMLPainter *painter, PangoItem *item, gint *asc, gint *dsc)
847{
848        PangoFontMetrics *pfm;
849
850        if (!HTML_IS_GDK_PAINTER (painter) && !HTML_IS_PLAIN_PAINTER (painter))
851                return;
852
853        pfm = pango_font_get_metrics (item->analysis.font, item->analysis.language);
854        if (asc)
855                *asc = MAX (*asc, PANGO_PIXELS (pango_font_metrics_get_ascent (pfm)));
856        if (dsc)
857                *dsc = MAX (*dsc, PANGO_PIXELS (pango_font_metrics_get_descent (pfm)));
858        pango_font_metrics_unref (pfm);
859}
860
861static void
862html_text_get_attr_list_list (PangoAttrList *get_attrs, PangoAttrList *attr_list, gint start_index, gint end_index)
863{
864        PangoAttrIterator *iter = pango_attr_list_get_iterator (attr_list);
865
866        if (iter) {
867                do {
868                        gint begin, end;
869
870                        pango_attr_iterator_range (iter, &begin, &end);
871
872                        if (MAX (begin, start_index) < MIN (end, end_index)) {
873                                GSList *c, *l = pango_attr_iterator_get_attrs (iter);
874
875                                for (c = l; c; c = c->next) {
876                                        PangoAttribute *attr = (PangoAttribute *) c->data;
877
878                                        if (attr->start_index < start_index)
879                                                attr->start_index = 0;
880                                        else
881                                                attr->start_index -= start_index;
882
883                                        if (attr->end_index > end_index)
884                                                attr->end_index = end_index - start_index;
885                                        else
886                                                attr->end_index -= start_index;
887
888                                        c->data = NULL;
889                                        pango_attr_list_insert (get_attrs, attr);
890                                }
891                                g_slist_free (l);
892                        }
893                } while (pango_attr_iterator_next (iter));
894        }
895}
896
897PangoAttrList *
898html_text_get_attr_list (HTMLText *text, gint start_index, gint end_index)
899{
900        PangoAttrList *attrs = pango_attr_list_new ();
901
902        html_text_get_attr_list_list (attrs, text->attr_list, start_index, end_index);
903        if (text->extra_attr_list)
904                html_text_get_attr_list_list (attrs, text->extra_attr_list, start_index, end_index);
905
906        return attrs;
907}
908
909void
910html_text_calc_text_size (HTMLText *t, HTMLPainter *painter,
911                          gint start_byte_offset,
912                          guint len, HTMLTextPangoInfo *pi, GList *glyphs, gint *line_offset,
913                          GtkHTMLFontStyle font_style,
914                          HTMLFontFace *face,
915                          gint *width, gint *asc, gint *dsc)
916{
917                PangoAttrList *attrs = NULL;
918                char *text = t->text + start_byte_offset;
919
920                if (HTML_IS_PRINTER (painter)) {
921                        HTMLClueFlow *flow = NULL;
922                        HTMLEngine *e = NULL;
923
924                        attrs = html_text_get_attr_list (t, start_byte_offset, start_byte_offset + (g_utf8_offset_to_pointer (text, len) - text));
925
926                        if (painter->widget && GTK_IS_HTML (painter->widget))
927                                e = GTK_HTML (painter->widget)->engine;
928
929                        if (HTML_OBJECT (t)->parent && HTML_IS_CLUEFLOW (HTML_OBJECT (t)->parent))
930                                flow = HTML_CLUEFLOW (HTML_OBJECT (t)->parent);
931
932                        if (flow && e)
933                                html_text_change_attrs (attrs, html_clueflow_get_default_font_style (flow), GTK_HTML (painter->widget)->engine, 0, t->text_bytes, TRUE);
934                }
935               
936                html_painter_calc_text_size (painter, text, len, pi, attrs, glyphs,
937                                             start_byte_offset, line_offset, font_style, face, width, asc, dsc);
938
939                if (attrs)
940                        pango_attr_list_unref (attrs);
941}
942
943gint
944html_text_calc_part_width (HTMLText *text, HTMLPainter *painter, char *start, gint offset, gint len, gint *asc, gint *dsc)
945{
946        gint idx, width = 0, line_offset;
947
948        g_return_val_if_fail (offset >= 0, 0);
949        g_return_val_if_fail (offset + len <= text->text_len, 0);
950
951        if (asc)
952                *asc = html_painter_get_space_asc (painter, html_text_get_font_style (text), text->face);
953        if (dsc)
954                *dsc = html_painter_get_space_dsc (painter, html_text_get_font_style (text), text->face);
955
956        if (text->text_len == 0 || len == 0)
957                return 0;
958
959        line_offset = html_text_get_line_offset (text, painter, offset);
960
961        if (start == NULL)
962                start = html_text_get_text (text, offset);
963
964        if (HTML_IS_GDK_PAINTER (painter) || HTML_IS_PLAIN_PAINTER (painter)) {
965                HTMLTextPangoInfo *pi;
966                PangoLanguage *language = NULL;
967                PangoFont *font = NULL;
968                gchar *s = start;
969
970                pi = html_text_get_pango_info (text, painter);
971
972                idx = html_text_get_item_index (text, painter, offset, &offset);
973                if (asc || dsc) {
974                        update_asc_dsc (painter, pi->entries [idx].item, asc, dsc);
975                        font = pi->entries [idx].item->analysis.font;
976                        language = pi->entries [idx].item->analysis.language;
977                }
978                while (len > 0) {
979                        if (*s == '\t') {
980                                gint skip = 8 - (line_offset % 8);
981                                width += skip*pi->entries [idx].widths [offset];
982                                line_offset += skip;
983                        } else {
984                                width += pi->entries [idx].widths [offset];
985                                line_offset ++;
986                        }
987                        len --;
988                        if (offset >= pi->entries [idx].item->num_chars - 1) {
989                                idx ++;
990                                offset = 0;
991                                if (len > 0 && (asc || dsc) && (pi->entries [idx].item->analysis.font != font || pi->entries [idx].item->analysis.language != language)) {
992                                        update_asc_dsc (painter, pi->entries [idx].item, asc, dsc);
993                                }
994                        } else
995                                offset ++;
996                        s = g_utf8_next_char (s);
997                }
998                width = PANGO_PIXELS (width);
999        } else {
1000                html_text_calc_text_size (text, painter, start - text->text, len, NULL, NULL, &line_offset,
1001                                          html_text_get_font_style (text), text->face, &width, asc, dsc);
1002        }
1003
1004        return width;
1005}
1006
1007static gint
1008calc_preferred_width (HTMLObject *self,
1009                      HTMLPainter *painter)
1010{
1011        HTMLText *text;
1012        gint width;
1013
1014        text = HTML_TEXT (self);
1015
1016        width = html_text_calc_part_width (text, painter, text->text, 0, text->text_len, &self->ascent, &self->descent);
1017        self->y = self->ascent;
1018        if (html_clueflow_tabs (HTML_CLUEFLOW (self->parent), painter)) {
1019                gint line_offset;
1020                gint tabs;
1021
1022                line_offset = html_text_get_line_offset (text, painter, 0);
1023                width += (html_text_text_line_length (text->text, &line_offset, text->text_len, &tabs) - text->text_len)*
1024                        html_painter_get_space_width (painter, html_text_get_font_style (text), text->face);
1025        }
1026
1027        return MAX (1, width);
1028}
1029
1030static void
1031remove_text_slaves (HTMLObject *self)
1032{
1033        HTMLObject *next_obj;
1034
1035        /* Remove existing slaves */
1036        next_obj = self->next;
1037        while (next_obj != NULL
1038               && (HTML_OBJECT_TYPE (next_obj) == HTML_TYPE_TEXTSLAVE)) {
1039                self->next = next_obj->next;
1040                html_clue_remove (HTML_CLUE (next_obj->parent), next_obj);
1041                html_object_destroy (next_obj);
1042                next_obj = self->next;
1043        }
1044}
1045
1046static HTMLFitType
1047ht_fit_line (HTMLObject *o,
1048             HTMLPainter *painter,
1049             gboolean startOfLine,
1050             gboolean firstRun,
1051             gboolean next_to_floating,
1052             gint widthLeft)
1053{
1054        HTMLText *text;
1055        HTMLObject *text_slave;
1056
1057        text = HTML_TEXT (o);
1058
1059        remove_text_slaves (o);
1060
1061        /* Turn all text over to our slaves */
1062        text_slave = html_text_slave_new (text, 0, HTML_TEXT (text)->text_len);
1063        html_clue_append_after (HTML_CLUE (o->parent), text_slave, o);
1064
1065        return HTML_FIT_COMPLETE;
1066}
1067
1068static gint
1069min_word_width_calc_tabs (HTMLText *text, HTMLPainter *p, gint idx, gint *len)
1070{
1071        gchar *str, *end;
1072        gint rv = 0, line_offset, wt, wl, i;
1073        gint epos;
1074        gboolean tab = FALSE;
1075       
1076        if (!html_clueflow_tabs (HTML_CLUEFLOW (HTML_OBJECT (text)->parent), p))
1077                return 0;
1078
1079        /* printf ("tabs %d\n", idx); */
1080
1081        str = text->text;
1082        i = idx;
1083        while (i > 0 && *str) {
1084                if (*str == ' ')
1085                        i--;
1086
1087                str = g_utf8_next_char (str);
1088        }
1089
1090        if (!*str)
1091                return 0;
1092
1093        epos = 0;
1094        end = str;
1095        while (*end && *end != ' ') {
1096                tab |= *end == '\t';
1097
1098                end = g_utf8_next_char (end);
1099                epos++;
1100        }
1101       
1102
1103        if (tab) {
1104                line_offset = 0;
1105               
1106                if (idx == 0) {
1107                        HTMLObject *prev;
1108                       
1109                        prev = html_object_prev_not_slave (HTML_OBJECT (text));
1110                        if (prev && html_object_is_text (prev) /* FIXME-words && HTML_TEXT (prev)->words > 0 */) {
1111                                min_word_width_calc_tabs (HTML_TEXT (prev), p, /* FIXME-words HTML_TEXT (prev)->words - 1 */ HTML_TEXT (prev)->text_len - 1, &line_offset);
1112                                /* printf ("lo: %d\n", line_offset); */
1113                        }
1114                }
1115
1116                wl = html_text_text_line_length (str, &line_offset, epos, &wt);
1117        } else {
1118                wl = epos;
1119        }
1120       
1121        rv = wl - epos;
1122               
1123        if (len)
1124                *len = wl;
1125
1126        /* printf ("tabs delta %d\n", rv); */
1127        return rv;
1128}
1129
1130gint
1131html_text_pango_info_get_index (HTMLTextPangoInfo *pi, gint byte_offset, gint idx)
1132{
1133        while (idx < pi->n && pi->entries [idx].item->offset + pi->entries [idx].item->length <= byte_offset)
1134                idx ++;
1135
1136        return idx;
1137}
1138
1139
1140static void
1141html_text_add_cite_color (PangoAttrList *attrs, HTMLText *text, HTMLClueFlow *flow, HTMLEngine *e)
1142{
1143        HTMLColor *cite_color = html_colorset_get_color (e->settings->color_set, HTMLCiteColor);
1144
1145        if (cite_color && flow->levels->len > 0 && flow->levels->data[0] == HTML_LIST_TYPE_BLOCKQUOTE_CITE) {
1146                PangoAttribute *attr;
1147
1148                attr = pango_attr_foreground_new (cite_color->color.red, cite_color->color.green, cite_color->color.blue);
1149                attr->start_index = 0;
1150                attr->end_index = text->text_bytes;
1151                pango_attr_list_change (attrs, attr);
1152        }
1153}
1154
1155void
1156html_text_remove_unwanted_line_breaks (char *s, int len, PangoLogAttr *attrs)
1157{
1158        int i;
1159        gunichar last_uc = 0;
1160
1161        for (i = 0; i < len; i ++) {
1162                gunichar uc = g_utf8_get_char (s);
1163
1164                if (attrs [i].is_line_break) {
1165                        if (last_uc == '.' || last_uc == '/' ||
1166                            last_uc == '-' || last_uc == '$' ||
1167                            last_uc == '+' || last_uc == '?' ||
1168                            last_uc == ')' ||
1169                            last_uc == '}' ||
1170                            last_uc == ']' ||
1171                            last_uc == '>')
1172                                attrs [i].is_line_break = 0;
1173                        else if ((uc == '(' ||
1174                                  uc == '{' ||
1175                                  uc == '[' ||
1176                                  uc == '<'
1177                                  )
1178                                 && i > 0 && !attrs [i - 1].is_white)
1179                                attrs [i].is_line_break = 0;
1180                }
1181                s = g_utf8_next_char (s);
1182                last_uc = uc;
1183        }
1184}
1185
1186HTMLTextPangoInfo *
1187html_text_get_pango_info (HTMLText *text, HTMLPainter *painter)
1188{
1189        /*if (!HTML_IS_GDK_PAINTER (painter) && !HTML_IS_PLAIN_PAINTER (painter))
1190          return NULL; */
1191        if (HTML_OBJECT (text)->change & HTML_CHANGE_RECALC_PI) {
1192                pango_info_destroy (text);
1193                HTML_OBJECT (text)->change &= ~HTML_CHANGE_RECALC_PI;
1194        }
1195        if (!text->pi) {
1196                PangoContext *pc = gtk_widget_get_pango_context (painter->widget);
1197                GList *items, *cur;
1198                PangoAttrList *attrs;
1199                PangoAttribute *attr;
1200                HTMLClueFlow *flow = NULL;
1201                HTMLEngine *e = NULL;
1202                gchar *translated, *heap = NULL;
1203                int i, offset;
1204
1205                if (text->text_bytes > HTML_ALLOCA_MAX)
1206                        heap = translated = g_malloc (text->text_bytes);
1207                else
1208                        translated = alloca (text->text_bytes);
1209
1210                html_replace_tabs (text->text, translated, text->text_bytes);
1211                attrs = pango_attr_list_new ();
1212
1213                if (HTML_OBJECT (text)->parent && HTML_IS_CLUEFLOW (HTML_OBJECT (text)->parent))
1214                        flow = HTML_CLUEFLOW (HTML_OBJECT (text)->parent);
1215               
1216                if (painter->widget && GTK_IS_HTML (painter->widget))
1217                        e = GTK_HTML (painter->widget)->engine;
1218
1219                if (flow && e)
1220                        html_text_add_cite_color (attrs, text, flow, e);
1221
1222                if (HTML_IS_PLAIN_PAINTER (painter)) {
1223                        attr = pango_attr_family_new (painter->font_manager.fixed.face);
1224                        attr->start_index = 0;
1225                        attr->end_index = text->text_bytes;
1226                        pango_attr_list_insert (attrs, attr);
1227                        if (painter->font_manager.fix_size != painter->font_manager.var_size) {
1228                                attr = pango_attr_size_new (painter->font_manager.fix_size);
1229                                attr->start_index = 0;
1230                                attr->end_index = text->text_bytes;
1231                                pango_attr_list_insert (attrs, attr);
1232                        }
1233                } else
1234                        pango_attr_list_splice (attrs, text->attr_list, 0, 0);
1235
1236                if (text->extra_attr_list)
1237                        pango_attr_list_splice (attrs, text->extra_attr_list, 0, 0);
1238                if (!HTML_IS_PLAIN_PAINTER (painter)) {
1239                        if (flow && e)
1240                                html_text_change_attrs (attrs, html_clueflow_get_default_font_style (flow), GTK_HTML (painter->widget)->engine, 0, text->text_bytes, TRUE);
1241                }
1242
1243                if (text->links && e) {
1244                        HTMLColor *link_color = html_colorset_get_color (e->settings->color_set, HTMLLinkColor);
1245                        GSList *l;
1246
1247                        for (l = text->links; l; l = l->next) {
1248                                PangoAttribute *attr;
1249                                Link *link;
1250
1251                                link = (Link *) l->data;
1252                                attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
1253                                attr->start_index = link->start_index;
1254                                attr->end_index = link->end_index;
1255                                pango_attr_list_change (attrs, attr);
1256
1257                                attr = pango_attr_foreground_new (link_color->color.red, link_color->color.green, link_color->color.blue);
1258                                attr->start_index = link->start_index;
1259                                attr->end_index = link->end_index;
1260                                pango_attr_list_change (attrs, attr);
1261                        }
1262                }
1263
1264                if (e && text->select_length) {
1265                        gchar *end;
1266                        gchar *start;
1267                        GdkColor fg = html_colorset_get_color_allocated
1268                                (e->settings->color_set, painter,
1269                                 painter->focus ? HTMLHighlightTextColor : HTMLHighlightTextNFColor)->color;
1270                        GdkColor bg = html_colorset_get_color_allocated
1271                                (e->settings->color_set, painter,
1272                                 painter->focus ? HTMLHighlightColor : HTMLHighlightNFColor)->color;
1273                       
1274                        start = html_text_get_text (text,  text->select_start);
1275                        end = g_utf8_offset_to_pointer (start, text->select_length);
1276                       
1277                        attr = pango_attr_background_new (bg.red, bg.green, bg.blue);
1278                        attr->start_index = start - text->text;
1279                        attr->end_index = end - text->text;
1280                        pango_attr_list_change (attrs, attr);
1281                       
1282                        attr = pango_attr_foreground_new (fg.red, fg.green, fg.blue);
1283                        attr->start_index = start - text->text;
1284                        attr->end_index = end - text->text;
1285                        pango_attr_list_change (attrs, attr);
1286                }
1287
1288                items = pango_itemize (pc, translated, 0, text->text_bytes, attrs, NULL);
1289                pango_attr_list_unref (attrs);
1290
1291                text->pi = html_text_pango_info_new (g_list_length (items));
1292
1293                for (i = 0, cur = items; i < text->pi->n; i ++, cur = cur->next)
1294                        text->pi->entries [i].item = (PangoItem *) cur->data;
1295
1296                offset = 0;
1297                text->pi->attrs = g_new (PangoLogAttr, text->text_len + 1);
1298                for (i = 0; i < text->pi->n; i ++) {
1299                        PangoItem tmp_item;
1300                        int start_i, start_offset;
1301
1302                        start_i = i;
1303                        start_offset = offset;
1304                        offset += text->pi->entries [i].item->num_chars;
1305                        tmp_item = *text->pi->entries [i].item;
1306                        while (i < text->pi->n - 1) {
1307                                if (tmp_item.analysis.lang_engine == text->pi->entries [i + 1].item->analysis.lang_engine) {
1308                                        tmp_item.length += text->pi->entries [i + 1].item->length;
1309                                        tmp_item.num_chars += text->pi->entries [i + 1].item->num_chars;
1310                                        offset += text->pi->entries [i + 1].item->num_chars;
1311                                        i ++;
1312                                } else
1313                                        break;
1314                        }
1315
1316                        pango_break (translated + tmp_item.offset, tmp_item.length, &tmp_item.analysis, text->pi->attrs + start_offset, tmp_item.num_chars + 1);
1317                }
1318
1319                if (text->pi && text->pi->attrs)
1320                        html_text_remove_unwanted_line_breaks (text->text, text->text_len, text->pi->attrs);
1321
1322                for (i = 0; i < text->pi->n; i ++) {
1323                        PangoGlyphString *glyphs;
1324                        PangoItem *item;
1325
1326                        item = text->pi->entries [i].item;
1327
1328                        /* printf ("item pos %d len %d\n", item->offset, item->length); */
1329
1330                        glyphs = pango_glyph_string_new ();
1331                        text->pi->entries [i].widths = g_new (PangoGlyphUnit, item->num_chars);
1332                        pango_shape (translated + item->offset, item->length, &item->analysis, glyphs);
1333                        pango_glyph_string_get_logical_widths (glyphs, translated + item->offset, item->length, item->analysis.level, text->pi->entries [i].widths);
1334                        pango_glyph_string_free (glyphs);
1335                }
1336                g_free (heap);
1337                g_list_free (items);
1338        }
1339        return text->pi;
1340}
1341
1342gboolean
1343html_text_pi_backward (HTMLTextPangoInfo *pi, gint *ii, gint *io)
1344{
1345        if (*io <= 0) {
1346                if (*ii <= 0)
1347                        return FALSE;
1348                (*ii) --;
1349                *io = pi->entries [*ii].item->num_chars - 1;
1350        } else
1351                (*io) --;
1352
1353        return TRUE;
1354}
1355
1356gboolean
1357html_text_pi_forward (HTMLTextPangoInfo *pi, gint *ii, gint *io)
1358{
1359        if (*io >= pi->entries [*ii].item->num_chars - 1) {
1360                if (*ii >= pi->n -1)
1361                        return FALSE;
1362                (*ii) ++;
1363                *io = 0;
1364        } else
1365                (*io) ++;
1366
1367        return TRUE;
1368}
1369
1370gint
1371html_text_tail_white_space (HTMLText *text, HTMLPainter *painter, gint offset, gint ii, gint io, gint *white_len, gint line_offset, gchar *s)
1372{
1373        HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
1374        int wl = 0;
1375        int ww = 0;
1376        int current_offset = offset;
1377
1378        if (html_text_pi_backward (pi, &ii, &io)) {
1379                s = g_utf8_prev_char (s);
1380                current_offset --;
1381                if (pi->attrs [current_offset].is_white) {
1382                        if (HTML_IS_GDK_PAINTER (painter) || HTML_IS_PLAIN_PAINTER (painter)) {
1383                                if (*s == '\t' && offset > 1) {
1384                                        gint skip = 8, co = offset - 1;
1385
1386                                        do {
1387                                                s = g_utf8_prev_char (s);
1388                                                co --;
1389                                                if (*s != '\t')
1390                                                        skip --;
1391                                        } while (s && co > 0 && *s != '\t');
1392
1393                                        ww += skip*(PANGO_PIXELS (pi->entries [ii].widths [io]));
1394                                } else {
1395                                        ww += PANGO_PIXELS (pi->entries [ii].widths [io]);
1396                                }
1397                        }
1398                        wl ++;
1399                }
1400        }
1401
1402        if (!HTML_IS_GDK_PAINTER (painter) && !HTML_IS_PLAIN_PAINTER (painter) && wl) {
1403                html_text_calc_text_size (text, painter, html_text_get_text (text, offset - wl) - text->text,
1404                                          wl, NULL, NULL, &line_offset, html_text_get_font_style (text), text->face,
1405                                          &ww, NULL, NULL);
1406        }
1407
1408        if (white_len)
1409                *white_len = wl;
1410
1411        return ww;
1412}
1413
1414static void
1415update_mw (HTMLText *text, HTMLPainter *painter, gint offset, gint *last_offset, gint *ww, gint *mw, gint ii, gint io, gchar *s, gint line_offset) {
1416        if (!HTML_IS_GDK_PAINTER (painter) && !HTML_IS_PLAIN_PAINTER (painter)) {
1417                gint w;
1418                html_text_calc_text_size (text, painter, html_text_get_text (text, *last_offset) - text->text,
1419                                          offset - *last_offset, NULL, NULL, NULL, html_text_get_font_style (text), text->face,
1420                                          &w, NULL, NULL);
1421                *ww += w;
1422        }
1423        *ww -= html_text_tail_white_space (text, painter, offset, ii, io, NULL, line_offset, s);
1424        if (*ww > *mw)
1425                *mw = *ww;
1426        *ww = 0;
1427
1428        *last_offset = offset;
1429}
1430
1431gboolean
1432html_text_is_line_break (PangoLogAttr attr)
1433{
1434        return attr.is_line_break;
1435}
1436
1437static gint
1438calc_min_width (HTMLObject *self, HTMLPainter *painter)
1439{
1440        HTMLText *text = HTML_TEXT (self);
1441        HTMLTextPangoInfo *pi = html_text_get_pango_info (text, painter);
1442        gint mw = 0, ww;
1443        gint ii, io, offset, last_offset, line_offset;
1444        gchar *s;
1445
1446        ww = 0;
1447
1448        last_offset = offset = 0;
1449        ii = io = 0;
1450        line_offset = html_text_get_line_offset (text, painter, 0);
1451        s = text->text;
1452        while (offset < text->text_len) {
1453                if (offset > 0 && html_text_is_line_break (pi->attrs [offset]))
1454                        update_mw (text, painter, offset, &last_offset, &ww, &mw, ii, io, s, line_offset);
1455
1456                if (*s == '\t') {
1457                        gint skip = 8 - (line_offset % 8);
1458                        if (HTML_IS_GDK_PAINTER (painter) || HTML_IS_PLAIN_PAINTER (painter))
1459                                ww += skip*(PANGO_PIXELS (pi->entries [ii].widths [io]));
1460                        line_offset += skip;
1461                } else {
1462                        if (HTML_IS_GDK_PAINTER (painter) || HTML_IS_PLAIN_PAINTER (painter))
1463                                ww += PANGO_PIXELS (pi->entries [ii].widths [io]);
1464                        line_offset ++;
1465                }
1466
1467                s = g_utf8_next_char (s);
1468                offset ++;
1469
1470                html_text_pi_forward (pi, &ii, &io);
1471        }
1472
1473       
1474        if (!HTML_IS_GDK_PAINTER (painter) && !HTML_IS_PLAIN_PAINTER (painter))
1475                html_text_calc_text_size (text, painter, html_text_get_text (text, last_offset) - text->text,
1476                                          offset - last_offset, NULL, NULL, NULL, html_text_get_font_style (text), text->face,
1477                                          &ww, NULL, NULL);
1478
1479        if (ww > mw)
1480                mw = ww;
1481
1482        return MAX (1, mw);
1483}
1484
1485static void
1486draw (HTMLObject *o,
1487      HTMLPainter *p,
1488      gint x, gint y,
1489      gint width, gint height,
1490      gint tx, gint ty)
1491{
1492}
1493
1494static gboolean
1495accepts_cursor (HTMLObject *object)
1496{
1497        return TRUE;
1498}
1499
1500static gboolean
1501save_open_attrs (HTMLEngineSaveState *state, GSList *attrs)
1502{
1503        gboolean rv = TRUE;
1504
1505        for (; attrs; attrs = attrs->next) {
1506                PangoAttribute *attr = (PangoAttribute *) attrs->data;
1507                HTMLEngine *e = state->engine;
1508                gchar *tag = NULL;
1509                gboolean free_tag = FALSE;
1510
1511                switch (attr->klass->type) {
1512                case PANGO_ATTR_WEIGHT:
1513                        tag = "<B>";
1514                        break;
1515                case PANGO_ATTR_STYLE:
1516                        tag = "<I>";
1517                        break;
1518                case PANGO_ATTR_UNDERLINE:
1519                        tag = "<U>";
1520                        break;
1521                case PANGO_ATTR_STRIKETHROUGH:
1522                        tag = "<S>";
1523                        break;
1524                case PANGO_ATTR_SIZE:
1525                        if (attr->klass == &html_pango_attr_font_size_klass) {
1526                                HTMLPangoAttrFontSize *size = (HTMLPangoAttrFontSize *) attr;
1527                                if ((size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3 && (size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0) {
1528                                        tag = g_strdup_printf ("<FONT SIZE=\"%d\">", size->style & GTK_HTML_FONT_STYLE_SIZE_MASK);
1529                                        free_tag = TRUE;
1530                                }
1531                        }
1532                        break;
1533                case PANGO_ATTR_FAMILY: {
1534                        PangoAttrString *family_attr = (PangoAttrString *) attr;
1535
1536                        if (!strcasecmp (e->painter->font_manager.fixed.face
1537                                        ? e->painter->font_manager.fixed.face : "Monospace",
1538                                        family_attr->value))
1539                                tag = "<TT>";
1540                }
1541                break;
1542                case PANGO_ATTR_FOREGROUND: {
1543                        PangoAttrColor *color = (PangoAttrColor *) attr;
1544                        tag = g_strdup_printf ("<FONT COLOR=\"#%02x%02x%02x\">",
1545                                               (color->color.red >> 8) & 0xff, (color->color.green >> 8) & 0xff, (color->color.blue >> 8) & 0xff);
1546                        free_tag = TRUE;
1547                }
1548                        break;
1549                default:
1550                        break;
1551                }
1552
1553                if (tag) {
1554                        if (!html_engine_save_output_string (state, "%s", tag))
1555                                rv = FALSE;
1556                        if (free_tag)
1557                                g_free (tag);
1558                        if (!rv)
1559                                break;
1560                }
1561        }
1562
1563        return TRUE;
1564}
1565
1566static gboolean
1567save_close_attrs (HTMLEngineSaveState *state, GSList *attrs)
1568{
1569        for (; attrs; attrs = attrs->next) {
1570                PangoAttribute *attr = (PangoAttribute *) attrs->data;
1571                HTMLEngine *e = state->engine;
1572                gchar *tag = NULL;
1573
1574                switch (attr->klass->type) {
1575                case PANGO_ATTR_WEIGHT:
1576                        tag = "</B>";
1577                        break;
1578                case PANGO_ATTR_STYLE:
1579                        tag = "</I>";
1580                        break;
1581                case PANGO_ATTR_UNDERLINE:
1582                        tag = "</U>";
1583                        break;
1584                case PANGO_ATTR_STRIKETHROUGH:
1585                        tag = "</S>";
1586                        break;
1587                case PANGO_ATTR_SIZE:
1588                        if (attr->klass == &html_pango_attr_font_size_klass) {
1589                                HTMLPangoAttrFontSize *size = (HTMLPangoAttrFontSize *) attr;
1590                                if ((size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3 && (size->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0)
1591                                        tag = "</FONT>";
1592                        }
1593                        break;
1594                case PANGO_ATTR_FOREGROUND:
1595                        tag = "</FONT>";
1596                        break;
1597                case PANGO_ATTR_FAMILY: {
1598                        PangoAttrString *family_attr = (PangoAttrString *) attr;
1599
1600                        if (!strcasecmp (e->painter->font_manager.fixed.face
1601                                        ? e->painter->font_manager.fixed.face : "Monospace",
1602                                        family_attr->value))
1603                                tag = "</TT>";
1604                }
1605                break;
1606                default:
1607                        break;
1608                }
1609
1610                if (tag)
1611                        if (!html_engine_save_output_string (state, "%s", tag))
1612                                return FALSE;
1613        }
1614
1615        return TRUE;
1616}
1617
1618static gboolean
1619save_text_part (HTMLText *text, HTMLEngineSaveState *state, guint start_index, guint end_index)
1620{
1621        gchar *str;
1622        gint len;
1623        gboolean rv;
1624
1625        str = g_strndup (text->text + start_index, end_index - start_index);
1626        len = g_utf8_pointer_to_offset (text->text + start_index, text->text + end_index);
1627
1628        rv = html_engine_save_encode (state, str, len);
1629        g_free (str);
1630        return rv;
1631}
1632
1633static gboolean
1634save_link_open (Link *link, HTMLEngineSaveState *state)
1635{
1636        return html_engine_save_output_string (state, "<A HREF=\"%s\">", link->url);
1637}
1638
1639static gboolean
1640save_link_close (Link *link, HTMLEngineSaveState *state)
1641{
1642        return html_engine_save_output_string (state, "%s", "</A>");
1643}
1644
1645static gboolean
1646save_text (HTMLText *text, HTMLEngineSaveState *state, guint start_index, guint end_index, GSList **l, gboolean *link_started)
1647{
1648        if (*l) {
1649                Link *link;
1650
1651                link = (Link *) (*l)->data;
1652
1653                while (*l && ((!*link_started && start_index <= link->start_index && link->start_index < end_index)
1654                              || (*link_started && link->end_index <= end_index))) {
1655                        if (!*link_started && start_index <= link->start_index && link->start_index < end_index) {
1656                                if (!save_text_part (text, state, start_index, link->start_index))
1657                                        return FALSE;
1658                                *link_started = TRUE;
1659                                save_link_open (link, state);
1660                                start_index = link->start_index;
1661                        }
1662                        if (*link_started && link->end_index <= end_index) {
1663                                if (!save_text_part (text, state, start_index, link->end_index))
1664                                        return FALSE;
1665                                save_link_close (link, state);
1666                                *link_started = FALSE;
1667                                (*l) = (*l)->next;
1668                                start_index = link->end_index;
1669                                if (*l)
1670                                        link = (Link *) (*l)->data;
1671                        }
1672                }
1673
1674        }
1675
1676        if (start_index < end_index)
1677                return save_text_part (text, state, start_index, end_index);
1678
1679        return TRUE;
1680}
1681
1682static gboolean
1683save (HTMLObject *self, HTMLEngineSaveState *state)
1684{
1685        HTMLText *text = HTML_TEXT (self);
1686        PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
1687
1688        if (iter) {
1689                GSList *l, *links = g_slist_reverse (g_slist_copy (text->links));
1690                gboolean link_started = FALSE;
1691
1692                l = links;
1693
1694                do {
1695                        GSList *attrs;
1696                        guint start_index, end_index;
1697
1698                        attrs = pango_attr_iterator_get_attrs (iter);
1699                        pango_attr_iterator_range (iter, &start_index, &end_index);
1700                        if (end_index > text->text_bytes)
1701                                end_index = text->text_bytes;
1702
1703                        if (attrs)
1704                                save_open_attrs (state, attrs);
1705                        save_text (text, state, start_index, end_index, &l, &link_started);
1706                        if (attrs) {
1707                                attrs = g_slist_reverse (attrs);
1708                                save_close_attrs (state, attrs);
1709                                html_text_free_attrs (attrs);
1710                        }
1711                } while (pango_attr_iterator_next (iter));
1712                g_slist_free (links);
1713        }
1714
1715        return TRUE;
1716}
1717
1718static gboolean
1719save_plain (HTMLObject *self,
1720            HTMLEngineSaveState *state,
1721            gint requested_width)
1722{
1723        HTMLText *text;
1724
1725        text = HTML_TEXT (self);
1726
1727        return html_engine_save_output_string (state, "%s", text->text);
1728}
1729
1730static guint
1731get_length (HTMLObject *self)
1732{
1733        return HTML_TEXT (self)->text_len;
1734}
1735
1736/* #define DEBUG_NBSP */
1737
1738struct TmpDeltaRecord
1739{
1740        int index;              /* Byte index within original string  */
1741        int delta;              /* New delta (character at index was modified,
1742                                 * new delta applies to characters afterwards)
1743                                 */
1744};
1745
1746/* Called when current character is not white space or at end of string */
1747static gboolean
1748check_last_white (gint white_space, gunichar last_white, gint *delta_out)
1749{
1750        if (white_space > 0 && last_white == ENTITY_NBSP) {
1751                (*delta_out) --; /* &nbsp; => &sp; is one byte shorter in UTF-8 */
1752                return TRUE;
1753        }
1754
1755        return FALSE;
1756}
1757
1758/* Called when current character is white space */
1759static gboolean
1760check_prev_white (gint white_space, gunichar last_white, gint *delta_out)
1761{
1762        if (white_space > 0 && last_white == ' ') {
1763                (*delta_out) ++; /* &sp; => &nbsp; is one byte longer in UTF-8 */
1764                return TRUE;
1765        }
1766
1767        return FALSE;
1768}
1769
1770static GSList *
1771add_change (GSList *list, int index, int delta)
1772{
1773        struct TmpDeltaRecord *rec = g_new (struct TmpDeltaRecord, 1);
1774
1775        rec->index = index;
1776        rec->delta = delta;
1777
1778        return g_slist_prepend (list, rec);
1779}
1780
1781/* This function does a pre-scan for the transformation in convert_nbsp,
1782 * which converts a sequence of N white space characters (&sp; or &nbsp;)
1783 * into N-1 &nbsp and 1 &sp;.
1784 *
1785 * delta_out: total change in byte length of string
1786 * changes_out: location to store series of records for each change in offset
1787 *              between the original string and the new string.
1788 * returns: %TRUE if any records were stored in changes_out
1789 */
1790static gboolean
1791is_convert_nbsp_needed (const gchar *s, gint *delta_out, GSList **changes_out)
1792{
1793        gunichar uc, last_white = 0;
1794        gboolean change;
1795        gint white_space;
1796        const gchar *p, *last_p;
1797
1798        *delta_out = 0;
1799
1800        last_p = NULL;          /* Quiet GCC */
1801        white_space = 0;
1802        for (p = s; *p; p = g_utf8_next_char (p)) {
1803                uc = g_utf8_get_char (p);
1804               
1805                if (uc == ENTITY_NBSP || uc == ' ') {
1806                        change = check_prev_white (white_space, last_white, delta_out);
1807                        white_space ++;
1808                        last_white = uc;
1809                } else {
1810                        change = check_last_white (white_space, last_white, delta_out);
1811                        white_space = 0;
1812                }
1813                if (change)
1814                        *changes_out = add_change (*changes_out, last_p - s, *delta_out);
1815                last_p = p;
1816        }
1817
1818        if (check_last_white (white_space, last_white, delta_out))
1819                *changes_out = add_change (*changes_out, last_p - s, *delta_out);
1820
1821
1822        *changes_out = g_slist_reverse (*changes_out);
1823
1824        return *changes_out != NULL;
1825}
1826
1827/* Called when current character is white space */
1828static void
1829write_prev_white_space (gint white_space, gchar **fill)
1830{
1831        if (white_space > 0) {
1832#ifdef DEBUG_NBSP
1833                printf ("&nbsp;");
1834#endif
1835                **fill = 0xc2; (*fill) ++;
1836                **fill = 0xa0; (*fill) ++;
1837        }
1838}
1839
1840/* Called when current character is not white space or at end of string */
1841static void
1842write_last_white_space (gint white_space, gchar **fill)
1843{
1844        if (white_space > 0) {
1845#ifdef DEBUG_NBSP
1846                printf (" ");
1847#endif
1848                **fill = ' '; (*fill) ++;
1849        }
1850}
1851
1852/* converts a sequence of N white space characters (&sp; or &nbsp;)
1853 * into N-1 &nbsp and 1 &sp;.
1854 */
1855static void
1856convert_nbsp (gchar *fill, const gchar *text)
1857{
1858        gint white_space;
1859        gunichar uc;
1860        const gchar *this_p, *p;
1861
1862#ifdef DEBUG_NBSP
1863        printf ("convert_nbsp: %s --> \"", p);
1864#endif
1865        p = text;
1866        white_space = 0;
1867
1868        while (*p) {
1869                this_p = p;
1870                uc = g_utf8_get_char (p);
1871                p = g_utf8_next_char (p);
1872
1873                if (uc == ENTITY_NBSP || uc == ' ') {
1874                        write_prev_white_space (white_space, &fill);
1875                        white_space ++;
1876                } else {
1877                        write_last_white_space (white_space, &fill);
1878                        white_space = 0;
1879#ifdef DEBUG_NBSP
1880                        printf ("*");
1881#endif
1882                        strncpy (fill, this_p, p - this_p);
1883                        fill += p - this_p;
1884                }
1885        }
1886
1887        write_last_white_space (white_space, &fill);
1888        *fill = 0;
1889
1890#ifdef DEBUG_NBSP
1891        printf ("\"\n");
1892#endif
1893}
1894
1895static void
1896update_index_interval (int *start_index, int *end_index, GSList *changes)
1897{
1898        GSList *c;
1899        int index, delta;
1900
1901        index = delta = 0;
1902
1903        for (c = changes; c && *start_index > index; c = c->next) {
1904                struct TmpDeltaRecord *rec = c->data;
1905
1906                if (*start_index > index && *start_index <= rec->index) {
1907                        (*start_index) += delta;
1908                        break;
1909                }
1910                index = rec->index;
1911                delta = rec->delta;
1912        }
1913
1914        if (c == NULL && *start_index > index) {
1915                (*start_index) += delta;
1916                (*end_index) += delta;
1917                return;
1918        }
1919
1920        for (; c && *end_index > index; c = c->next) {
1921                struct TmpDeltaRecord *rec = c->data;
1922
1923                if (*end_index > index && *end_index <= rec->index) {
1924                        (*start_index) += delta;
1925                        break;
1926                }
1927                index = rec->index;
1928                delta = rec->delta;
1929        }
1930
1931        if (c == NULL && *end_index > index)
1932                (*end_index) += delta;
1933}
1934
1935static gboolean
1936update_attributes_filter (PangoAttribute *attr, gpointer data)
1937{
1938        update_index_interval (&attr->start_index, &attr->end_index, (GSList *) data);
1939
1940        return FALSE;
1941}
1942
1943static void
1944update_attributes (PangoAttrList *attrs, GSList *changes)
1945{
1946        pango_attr_list_filter (attrs, update_attributes_filter, changes);
1947}
1948
1949static void
1950update_links (GSList *links, GSList *changes)
1951{
1952        GSList *cl;
1953
1954        for (cl = links; cl; cl = cl->next) {
1955                Link *link = (Link *) cl->data;
1956                update_index_interval (&link->start_index, &link->end_index, changes);
1957        }
1958}
1959
1960static void
1961free_changes (GSList *changes)
1962{
1963        GSList *c;
1964
1965        for (c = changes; c; c = c->next)
1966                g_free (c->data);
1967        g_slist_free (changes);
1968}
1969
1970gboolean
1971html_text_convert_nbsp (HTMLText *text, gboolean free_text)
1972{
1973        GSList *changes = NULL;
1974        gint delta;
1975
1976        if (is_convert_nbsp_needed (text->text, &delta, &changes)) {
1977                gchar *to_free;
1978
1979                to_free = text->text;
1980                text->text = g_malloc (strlen (to_free) + delta + 1);
1981                text->text_bytes += delta;
1982                convert_nbsp (text->text, to_free);
1983                if (free_text)
1984                        g_free (to_free);
1985                if (changes) {
1986                        if (text->attr_list)
1987                                update_attributes (text->attr_list, changes);
1988                        if (text->extra_attr_list)
1989                                update_attributes (text->extra_attr_list, changes);
1990                        if (text->links)
1991                                update_links (text->links, changes);
1992                        free_changes (changes);
1993                }
1994                html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
1995                return TRUE;
1996        }
1997        return FALSE;
1998}
1999
2000static void
2001move_spell_errors (GList *spell_errors, guint offset, gint delta)
2002{
2003        SpellError *se;
2004
2005        if (!delta)
2006                return;
2007
2008        while (spell_errors) {
2009                se = (SpellError *) spell_errors->data;
2010                if (se->off >= offset)
2011                        se->off += delta;
2012                spell_errors = spell_errors->next;
2013        }
2014}
2015
2016static GList *
2017remove_one (GList *list, GList *link)
2018{
2019        spell_error_destroy ((SpellError *) link->data);
2020        return g_list_remove_link (list, link);
2021}
2022
2023static GList *
2024remove_spell_errors (GList *spell_errors, guint offset, guint len)
2025{
2026        SpellError *se;
2027        GList *cur, *cnext;
2028
2029        cur = spell_errors;
2030        while (cur) {
2031                cnext = cur->next;
2032                se = (SpellError *) cur->data;
2033                if (se->off < offset) {
2034                        if (se->off + se->len > offset) {
2035                                if (se->off + se->len <= offset + len)
2036                                        se->len = offset - se->off;
2037                                else
2038                                        se->len -= len;
2039                                if (se->len < 2)
2040                                        spell_errors = remove_one (spell_errors, cur);
2041                        }
2042                } else if (se->off < offset + len) {
2043                        if (se->off + se->len <= offset + len)
2044                                spell_errors = remove_one (spell_errors, cur);
2045                        else {
2046                                se->len -= offset + len - se->off;
2047                                se->off  = offset + len;
2048                                if (se->len < 2)
2049                                        spell_errors = remove_one (spell_errors, cur);
2050                        }
2051                }
2052                cur = cnext;
2053        }
2054        return spell_errors;
2055}
2056
2057static HTMLObject *
2058check_point (HTMLObject *self,
2059             HTMLPainter *painter,
2060             gint x, gint y,
2061             guint *offset_return,
2062             gboolean for_cursor)
2063{
2064        return NULL;
2065}
2066
2067static void
2068queue_draw (HTMLText *text,
2069            HTMLEngine *engine,
2070            guint offset,
2071            guint len)
2072{
2073        HTMLObject *obj;
2074
2075        for (obj = HTML_OBJECT (text)->next; obj != NULL; obj = obj->next) {
2076                HTMLTextSlave *slave;
2077
2078                if (HTML_OBJECT_TYPE (obj) != HTML_TYPE_TEXTSLAVE)
2079                        continue;
2080
2081                slave = HTML_TEXT_SLAVE (obj);
2082
2083                if (offset < slave->posStart + slave->posLen
2084                    && (len == 0 || offset + len >= slave->posStart)) {
2085                        html_engine_queue_draw (engine, obj);
2086                        if (len != 0 && slave->posStart + slave->posLen > offset + len)
2087                                break;
2088                }
2089        }
2090}
2091
2092/* This is necessary to merge the text-specified font style with that of the
2093   HTMLClueFlow parent.  */
2094static GtkHTMLFontStyle
2095get_font_style (const HTMLText *text)
2096{
2097        HTMLObject *parent;
2098        GtkHTMLFontStyle font_style;
2099
2100        parent = HTML_OBJECT (text)->parent;
2101
2102        if (HTML_OBJECT_TYPE (parent) == HTML_TYPE_CLUEFLOW) {
2103                GtkHTMLFontStyle parent_style;
2104
2105                parent_style = html_clueflow_get_default_font_style (HTML_CLUEFLOW (parent));
2106                font_style = gtk_html_font_style_merge (parent_style, text->font_style);
2107        } else {
2108                font_style = gtk_html_font_style_merge (GTK_HTML_FONT_STYLE_SIZE_3, text->font_style);
2109        }
2110
2111        return font_style;
2112}
2113
2114static void
2115set_font_style (HTMLText *text,
2116                HTMLEngine *engine,
2117                GtkHTMLFontStyle style)
2118{
2119        if (text->font_style == style)
2120                return;
2121
2122        text->font_style = style;
2123
2124        html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL_CALC);
2125
2126        if (engine != NULL) {
2127                html_object_relayout (HTML_OBJECT (text)->parent, engine, HTML_OBJECT (text));
2128                html_engine_queue_draw (engine, HTML_OBJECT (text));
2129        }
2130}
2131
2132static void
2133destroy (HTMLObject *obj)
2134{
2135        HTMLText *text = HTML_TEXT (obj);
2136        html_color_unref (text->color);
2137        html_text_spell_errors_clear (text);
2138        g_free (text->text);
2139        g_free (text->face);
2140        pango_info_destroy (text);
2141        pango_attr_list_unref (text->attr_list);
2142        text->attr_list = NULL;
2143        if (text->extra_attr_list) {
2144                pango_attr_list_unref (text->extra_attr_list);
2145                text->extra_attr_list = NULL;
2146        }
2147        free_links (text->links);
2148        text->links = NULL;
2149
2150        HTML_OBJECT_CLASS (parent_class)->destroy (obj);
2151}
2152
2153
2154static gboolean
2155select_range (HTMLObject *self,
2156              HTMLEngine *engine,
2157              guint offset,
2158              gint length,
2159              gboolean queue_draw)
2160{
2161        HTMLText *text;
2162        HTMLObject *p;
2163        gboolean changed;
2164
2165        text = HTML_TEXT (self);
2166
2167        if (length < 0 || length + offset > HTML_TEXT (self)->text_len)
2168                length = HTML_TEXT (self)->text_len - offset;
2169
2170        if (offset != text->select_start || length != text->select_length) {
2171                HTMLObject *slave;
2172                changed = TRUE;
2173                html_object_change_set (self, HTML_CHANGE_RECALC_PI);
2174                slave = self->next;
2175                while (slave && HTML_IS_TEXT_SLAVE (slave)) {
2176                        html_object_change_set (slave, HTML_CHANGE_RECALC_PI);
2177                        slave = slave->next;
2178                }
2179        } else
2180                changed = FALSE;
2181
2182        /* printf ("select range %d, %d\n", offset, length); */
2183        if (queue_draw) {
2184                for (p = self->next;
2185                     p != NULL && HTML_OBJECT_TYPE (p) == HTML_TYPE_TEXTSLAVE;
2186                     p = p->next) {
2187                        HTMLTextSlave *slave;
2188                        gboolean was_selected, is_selected;
2189                        guint max;
2190
2191                        slave = HTML_TEXT_SLAVE (p);
2192
2193                        max = slave->posStart + slave->posLen;
2194
2195                        if (text->select_start + text->select_length > slave->posStart
2196                            && text->select_start < max)
2197                                was_selected = TRUE;
2198                        else
2199                                was_selected = FALSE;
2200
2201                        if (offset + length > slave->posStart && offset < max)
2202                                is_selected = TRUE;
2203                        else
2204                                is_selected = FALSE;
2205
2206                        if (was_selected && is_selected) {
2207                                gint diff1, diff2;
2208
2209                                diff1 = offset - slave->posStart;
2210                                diff2 = text->select_start - slave->posStart;
2211
2212                                /* printf ("offsets diff 1: %d 2: %d\n", diff1, diff2); */
2213                                if (diff1 != diff2) {
2214                                        html_engine_queue_draw (engine, p);
2215                                } else {
2216                                        diff1 = offset + length - slave->posStart;
2217                                        diff2 = (text->select_start + text->select_length
2218                                                 - slave->posStart);
2219
2220                                        /* printf ("lens diff 1: %d 2: %d\n", diff1, diff2); */
2221                                        if (diff1 != diff2)
2222                                                html_engine_queue_draw (engine, p);
2223                                }
2224                        } else {
2225                                if ((! was_selected && is_selected) || (was_selected && ! is_selected))
2226                                        html_engine_queue_draw (engine, p);
2227                        }
2228                }
2229        }
2230
2231        text->select_start = offset;
2232        text->select_length = length;
2233
2234        if (length == 0)
2235                self->selected = FALSE;
2236        else
2237                self->selected = TRUE;
2238
2239        return changed;
2240}
2241
2242static HTMLObject *
2243set_link (HTMLObject *self, HTMLColor *color, const gchar *url, const gchar *target)
2244{
2245        /* HTMLText *text = HTML_TEXT (self); */
2246
2247        /* FIXME-link return url ? html_link_text_new_with_len (text->text, text->text_len, text->font_style, color, url, target) : NULL; */
2248        return NULL;
2249}
2250
2251static void
2252append_selection_string (HTMLObject *self,
2253                         GString *buffer)
2254{
2255        HTMLText *text;
2256        const gchar *p, *last;
2257
2258        text = HTML_TEXT (self);
2259        if (text->select_length == 0)
2260                return;
2261
2262        p    = html_text_get_text (text, text->select_start);
2263        last = g_utf8_offset_to_pointer (p, text->select_length);
2264       
2265        /* OPTIMIZED
2266        last = html_text_get_text (text,
2267                                   text->select_start + text->select_length);
2268        */
2269        html_engine_save_string_append_nonbsp (buffer, p, last - p);
2270
2271}
2272
2273static void
2274get_cursor (HTMLObject *self,
2275            HTMLPainter *painter,
2276            guint offset,
2277            gint *x1, gint *y1,
2278            gint *x2, gint *y2)
2279{
2280        HTMLObject *slave;
2281        guint ascent, descent;
2282
2283        html_object_get_cursor_base (self, painter, offset, x2, y2);
2284
2285        slave = self->next;
2286        if (slave == NULL || HTML_OBJECT_TYPE (slave) != HTML_TYPE_TEXTSLAVE) {
2287                ascent = self->ascent;
2288                descent = self->descent;
2289        } else {
2290                ascent = slave->ascent;
2291                descent = slave->descent;
2292        }
2293
2294        *x1 = *x2;
2295        *y1 = *y2 - ascent;
2296        *y2 += descent - 1;
2297}
2298
2299static void
2300get_cursor_base (HTMLObject *self,
2301                 HTMLPainter *painter,
2302                 guint offset,
2303                 gint *x, gint *y)
2304{
2305        HTMLObject *obj;
2306
2307        for (obj = self->next; obj != NULL; obj = obj->next) {
2308                HTMLTextSlave *slave;
2309
2310                if (HTML_OBJECT_TYPE (obj) != HTML_TYPE_TEXTSLAVE)
2311                        break;
2312
2313                slave = HTML_TEXT_SLAVE (obj);
2314
2315                if (offset <= slave->posStart + slave->posLen
2316                    || obj->next == NULL
2317                    || HTML_OBJECT_TYPE (obj->next) != HTML_TYPE_TEXTSLAVE) {
2318                        html_object_calc_abs_position (obj, x, y);
2319                        if (offset > slave->posStart)
2320                                *x += html_text_calc_part_width (HTML_TEXT (self), painter, html_text_slave_get_text (slave),
2321                                                                 slave->posStart, offset - slave->posStart, NULL, NULL);
2322
2323                        return;
2324                }
2325        }
2326
2327        g_warning ("Getting cursor base for an HTMLText with no slaves -- %p\n",
2328                   self);
2329        html_object_calc_abs_position (self, x, y);
2330}
2331
2332Link *
2333html_text_get_link_at_offset (HTMLText *text, gint offset)
2334{
2335        GSList *l;
2336
2337        for (l = text->links; l; l = l->next) {
2338                Link *link = (Link *) l->data;
2339
2340                if (link->start_offset <= offset && offset <= link->end_offset)
2341                        return link;
2342        }
2343
2344        return NULL;
2345}
2346
2347static const gchar *
2348get_url (HTMLObject *object, gint offset)
2349{
2350        Link *link = html_text_get_link_at_offset (HTML_TEXT (object), offset);
2351
2352        return link ? link->url : NULL;
2353}
2354
2355static const gchar *
2356get_target (HTMLObject *object, gint offset)
2357{
2358        Link *link = html_text_get_link_at_offset (HTML_TEXT (object), offset);
2359
2360        return link ? link->target : NULL;
2361}
2362
2363void
2364html_text_type_init (void)
2365{
2366        html_text_class_init (&html_text_class, HTML_TYPE_TEXT, sizeof (HTMLText));
2367}
2368
2369void
2370html_text_class_init (HTMLTextClass *klass,
2371                      HTMLType type,
2372                      guint object_size)
2373{
2374        HTMLObjectClass *object_class;
2375
2376        object_class = HTML_OBJECT_CLASS (klass);
2377
2378        html_object_class_init (object_class, type, object_size);
2379
2380        object_class->destroy = destroy;
2381        object_class->copy = copy;
2382        object_class->op_copy = op_copy;
2383        object_class->op_cut = op_cut;
2384        object_class->merge = object_merge;
2385        object_class->split = object_split;
2386        object_class->draw = draw;
2387        object_class->accepts_cursor = accepts_cursor;
2388        object_class->calc_size = html_text_real_calc_size;
2389        object_class->calc_preferred_width = calc_preferred_width;
2390        object_class->calc_min_width = calc_min_width;
2391        object_class->fit_line = ht_fit_line;
2392        object_class->get_cursor = get_cursor;
2393        object_class->get_cursor_base = get_cursor_base;
2394        object_class->save = save;
2395        object_class->save_plain = save_plain;
2396        object_class->check_point = check_point;
2397        object_class->select_range = select_range;
2398        object_class->get_length = get_length;
2399        object_class->get_line_length = get_line_length;
2400        object_class->set_link = set_link;
2401        object_class->append_selection_string = append_selection_string;
2402        object_class->get_url = get_url;
2403        object_class->get_target = get_target;
2404
2405        /* HTMLText methods.  */
2406
2407        klass->queue_draw = queue_draw;
2408        klass->get_font_style = get_font_style;
2409        klass->set_font_style = set_font_style;
2410
2411        parent_class = &html_object_class;
2412}
2413
2414static gint
2415text_len (const gchar **str, gint len)
2416{
2417        if (g_utf8_validate (*str, -1, NULL))
2418                return len != -1 ? len : g_utf8_strlen (*str, -1);
2419        else {
2420                *str = "[?]";
2421                return 3;
2422        }
2423}
2424
2425void
2426html_text_init (HTMLText *text,
2427                HTMLTextClass *klass,
2428                const gchar *str,
2429                gint len,
2430                GtkHTMLFontStyle font_style,
2431                HTMLColor *color)
2432{
2433        g_assert (color);
2434
2435        html_object_init (HTML_OBJECT (text), HTML_OBJECT_CLASS (klass));
2436
2437        text->text_len      = text_len (&str, len);
2438        text->text_bytes    = g_utf8_offset_to_pointer (str, text->text_len) - str;
2439        text->text          = g_strndup (str, text->text_bytes);
2440        text->font_style    = font_style;
2441        text->face          = NULL;
2442        text->color         = color;
2443        text->spell_errors  = NULL;
2444        text->select_start  = 0;
2445        text->select_length = 0;
2446        text->pi            = NULL;
2447        text->attr_list     = pango_attr_list_new ();
2448        text->extra_attr_list = NULL;
2449        text->links         = NULL;
2450
2451        html_color_ref (color);
2452}
2453
2454HTMLObject *
2455html_text_new_with_len (const gchar *str, gint len, GtkHTMLFontStyle font, HTMLColor *color)
2456{
2457        HTMLText *text;
2458
2459        text = g_new (HTMLText, 1);
2460
2461        html_text_init (text, &html_text_class, str, len, font, color);
2462
2463        return HTML_OBJECT (text);
2464}
2465
2466HTMLObject *
2467html_text_new (const gchar *text,
2468               GtkHTMLFontStyle font,
2469               HTMLColor *color)
2470{
2471        return html_text_new_with_len (text, -1, font, color);
2472}
2473
2474void
2475html_text_queue_draw (HTMLText *text,
2476                      HTMLEngine *engine,
2477                      guint offset,
2478                      guint len)
2479{
2480        g_return_if_fail (text != NULL);
2481        g_return_if_fail (engine != NULL);
2482
2483        (* HT_CLASS (text)->queue_draw) (text, engine, offset, len);
2484}
2485
2486
2487GtkHTMLFontStyle
2488html_text_get_font_style (const HTMLText *text)
2489{
2490        g_return_val_if_fail (text != NULL, GTK_HTML_FONT_STYLE_DEFAULT);
2491
2492        return (* HT_CLASS (text)->get_font_style) (text);
2493}
2494
2495void
2496html_text_set_font_style (HTMLText *text,
2497                          HTMLEngine *engine,
2498                          GtkHTMLFontStyle style)
2499{
2500        g_return_if_fail (text != NULL);
2501
2502        (* HT_CLASS (text)->set_font_style) (text, engine, style);
2503}
2504
2505void
2506html_text_set_font_face (HTMLText *text, HTMLFontFace *face)
2507{
2508        if (text->face)
2509                g_free (text->face);
2510        text->face = g_strdup (face);
2511}
2512
2513void
2514html_text_set_text (HTMLText *text, const gchar *new_text)
2515{
2516        g_free (text->text);
2517        text->text_len = text_len (&new_text, -1);
2518        text->text = g_strdup (new_text);
2519        text->text_bytes = strlen (text->text);
2520        html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
2521}
2522
2523/* spell checking */
2524
2525#include "htmlinterval.h"
2526
2527static SpellError *
2528spell_error_new (guint off, guint len)
2529{
2530        SpellError *se = g_new (SpellError, 1);
2531
2532        se->off = off;
2533        se->len = len;
2534
2535        return se;
2536}
2537
2538static void
2539spell_error_destroy (SpellError *se)
2540{
2541        g_free (se);
2542}
2543
2544void
2545html_text_spell_errors_clear (HTMLText *text)
2546{
2547        g_list_foreach (text->spell_errors, (GFunc) spell_error_destroy, NULL);
2548        g_list_free    (text->spell_errors);
2549        text->spell_errors = NULL;
2550}
2551
2552void
2553html_text_spell_errors_clear_interval (HTMLText *text, HTMLInterval *i)
2554{
2555        GList *cur, *cnext;
2556        SpellError *se;
2557        guint offset, len;
2558
2559        offset = html_interval_get_start  (i, HTML_OBJECT (text));
2560        len    = html_interval_get_length (i, HTML_OBJECT (text));
2561        cur    = text->spell_errors;
2562
2563        /* printf ("html_text_spell_errors_clear_interval %s %d %d\n", text->text, offset, len); */
2564
2565        while (cur) {
2566                cnext = cur->next;
2567                se    = (SpellError *) cur->data;
2568                /* test overlap */
2569                if (MAX (offset, se->off) <= MIN (se->off + se->len, offset + len)) {
2570                        text->spell_errors = g_list_remove_link (text->spell_errors, cur);
2571                        spell_error_destroy (se);
2572                        g_list_free (cur);
2573                }
2574                cur = cnext;
2575        }
2576}
2577
2578static gint
2579se_cmp (gconstpointer a, gconstpointer b)
2580{
2581        guint o1, o2;
2582
2583        o1 = ((SpellError *) a)->off;
2584        o2 = ((SpellError *) b)->off;
2585
2586        if (o1 < o2)  return -1;
2587        if (o1 == o2) return 0;
2588        return 1;
2589}
2590
2591void
2592html_text_spell_errors_add (HTMLText *text, guint off, guint len)
2593{
2594        /* GList *cur;
2595        SpellError *se;
2596        cur = */
2597
2598        text->spell_errors = g_list_insert_sorted (text->spell_errors, spell_error_new (off, len), se_cmp);
2599
2600        /* printf ("---------------------------------------\n");
2601        while (cur) {
2602                se = (SpellError *) cur->data;
2603                printf ("off: %d len: %d\n", se->off, se->len);
2604                cur = cur->next;
2605        }
2606        printf ("---------------------------------------\n"); */
2607}
2608
2609guint
2610html_text_get_bytes (HTMLText *text)
2611{
2612        return strlen (text->text);
2613}
2614
2615gchar *
2616html_text_get_text (HTMLText *text, guint offset)
2617{
2618        gchar *s = text->text;
2619
2620        while (offset--)
2621                s = g_utf8_next_char (s);
2622
2623        return s;
2624}
2625
2626guint
2627html_text_get_index (HTMLText *text, guint offset)
2628{
2629        return html_text_get_text (text, offset) - text->text;
2630}
2631
2632gunichar
2633html_text_get_char (HTMLText *text, guint offset)
2634{
2635        gunichar uc;
2636
2637        uc = g_utf8_get_char (html_text_get_text (text, offset));
2638        return uc;
2639}
2640
2641/* magic links */
2642
2643struct _HTMLMagicInsertMatch
2644{
2645        gchar *regex;
2646        regex_t *preg;
2647        gchar *prefix;
2648};
2649
2650typedef struct _HTMLMagicInsertMatch HTMLMagicInsertMatch;
2651
2652static HTMLMagicInsertMatch mim [] = {
2653        { "(news|telnet|nttp|file|http|ftp|https)://([-a-z0-9]+(:[-a-z0-9]+)?@)?[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-a-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, NULL },
2654        { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, "http://" },
2655        { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) ,?!;:\"]?)?", NULL, "ftp://" },
2656        { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+", NULL, "mailto:" }
2657};
2658
2659#define MIM_N (sizeof (mim) / sizeof (mim [0]))
2660
2661void
2662html_engine_init_magic_links (void)
2663{
2664        gint i;
2665
2666        for (i=0; i<MIM_N; i++) {
2667                mim [i].preg = g_new0 (regex_t, 1);
2668                if (regcomp (mim [i].preg, mim [i].regex, REG_EXTENDED | REG_ICASE)) {
2669                        /* error */
2670                        g_free (mim [i].preg);
2671                        mim [i].preg = 0;
2672                }
2673        }
2674}
2675
2676static void
2677paste_link (HTMLEngine *engine, HTMLText *text, gint so, gint eo, gchar *prefix)
2678{
2679        gchar *href;
2680        gchar *base;
2681
2682        base = g_strndup (html_text_get_text (text, so), html_text_get_index (text, eo) - html_text_get_index (text, so));
2683        href = (prefix) ? g_strconcat (prefix, base, NULL) : g_strdup (base);
2684        g_free (base);
2685
2686        html_text_add_link (text, engine, href, NULL, so, eo);
2687        g_free (href);
2688}
2689
2690gboolean
2691html_text_magic_link (HTMLText *text, HTMLEngine *engine, guint offset)
2692{
2693        regmatch_t pmatch [2];
2694        gint i;
2695        gboolean rv = FALSE, exec = TRUE;
2696        gint saved_position;
2697        gunichar uc;
2698        char *str, *cur;
2699
2700        if (!offset)
2701                return FALSE;
2702        offset--;
2703
2704        /* printf ("html_text_magic_link\n"); */
2705
2706        html_undo_level_begin (engine->undo, "Magic link", "Remove magic link");
2707        saved_position = engine->cursor->position;
2708
2709        cur = str = html_text_get_text (text, offset);
2710
2711        /* check forward to ensure chars are < 0x80, could be removed once we have utf8 regex */
2712        while (TRUE) {
2713                cur = g_utf8_next_char (cur);
2714                if (!*cur)
2715                        break;
2716                uc = g_utf8_get_char (cur);
2717                if (uc >= 0x80) {
2718                        exec = FALSE;
2719                        break;
2720                } else if (uc == ' ' || uc == ENTITY_NBSP) {
2721                        break;
2722                }
2723        }
2724
2725        uc = g_utf8_get_char (str);
2726        if (uc >= 0x80)
2727                exec = FALSE;
2728        while (exec && uc != ' ' && uc != ENTITY_NBSP && offset) {
2729                str = g_utf8_prev_char (str);
2730                uc = g_utf8_get_char (str);
2731                if (uc >= 0x80)
2732                        exec = FALSE;
2733                offset--;
2734        }
2735
2736        if (uc == ' ' || uc == ENTITY_NBSP) {
2737                str = g_utf8_next_char (str);
2738                offset++;
2739        }
2740
2741        if (exec) {
2742                for (i=0; i<MIM_N; i++) {
2743                        if (mim [i].preg && !regexec (mim [i].preg, str, 2, pmatch, 0)) {
2744                                paste_link (engine, text,
2745                                            g_utf8_pointer_to_offset (text->text, str + pmatch [0].rm_so),
2746                                            g_utf8_pointer_to_offset (text->text, str + pmatch [0].rm_eo), mim [i].prefix);
2747                                        rv = TRUE;
2748                                        break;
2749                        }
2750                }
2751        }
2752
2753        html_undo_level_end (engine->undo);
2754        html_cursor_jump_to_position_no_spell (engine->cursor, engine, saved_position);
2755
2756        return rv;
2757}
2758
2759/*
2760 * magic links end
2761 */
2762
2763gint
2764html_text_trail_space_width (HTMLText *text, HTMLPainter *painter)
2765{
2766        return text->text_len > 0 && html_text_get_char (text, text->text_len - 1) == ' '
2767                ? html_painter_get_space_width (painter, html_text_get_font_style (text), text->face) : 0;
2768}
2769
2770void
2771html_text_append (HTMLText *text, const gchar *str, gint len)
2772{
2773        gchar *to_delete;
2774
2775        to_delete       = text->text;
2776        text->text_len += text_len (&str, len);
2777        text->text_bytes += strlen (str);
2778        text->text      = g_strconcat (to_delete, str, NULL);
2779
2780        g_free (to_delete);
2781
2782        html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_ALL);
2783}
2784
2785void
2786html_text_append_link_full (HTMLText *text, gchar *url, gchar *target, gint start_index, gint end_index, gint start_offset, gint end_offset)
2787{
2788        text->links = g_slist_prepend (text->links, html_link_new (url, target, start_index, end_index, start_offset, end_offset));
2789}
2790
2791static void
2792html_text_offsets_to_indexes (HTMLText *text, gint so, gint eo, gint *si, gint *ei)
2793{
2794        *si = html_text_get_index (text, so);
2795        *ei = g_utf8_offset_to_pointer (text->text + *si, eo - so) - text->text;
2796}
2797
2798void
2799html_text_append_link (HTMLText *text, gchar *url, gchar *target, gint start_offset, gint end_offset)
2800{
2801        gint start_index, end_index;
2802
2803        html_text_offsets_to_indexes (text, start_offset, end_offset, &start_index, &end_index);
2804        html_text_append_link_full (text, url, target, start_index, end_index, start_offset, end_offset);
2805}
2806
2807void
2808html_text_add_link_full (HTMLText *text, HTMLEngine *e, gchar *url, gchar *target, gint start_index, gint end_index, gint start_offset, gint end_offset)
2809{
2810        GSList *l, *prev = NULL;
2811        Link *link;
2812
2813        cut_links_full (text, start_offset, end_offset, start_index, end_index, 0, 0);
2814
2815        if (text->links == NULL)
2816                html_text_append_link_full (text, url, target, start_index, end_index, start_offset, end_offset);
2817        else {
2818                Link *plink = NULL, *new_link = html_link_new (url, target, start_index, end_index, start_offset, end_offset);
2819
2820                for (l = text->links; new_link && l; l = l->next) {
2821                        link = (Link *) l->data;
2822                        if (new_link->start_offset >= link->end_offset) {
2823                                if (new_link->start_offset == link->end_offset && html_link_equal (link, new_link)) {
2824                                        link->end_offset = end_offset;
2825                                        link->end_index = end_index;
2826                                        html_link_free (new_link);
2827                                        new_link = NULL;
2828                                } else {
2829                                        l = g_slist_prepend (l, new_link);
2830                                        if (prev)
2831                                                prev->next = l;
2832                                        else
2833                                                text->links = l;
2834                                        link = new_link;
2835                                        new_link = NULL;
2836                                }
2837                                if (plink && html_link_equal (plink, link) && plink->start_offset == link->end_offset) {
2838                                        plink->start_offset = link->start_offset;
2839                                        plink->start_index = link->start_index;
2840                                        prev->next = g_slist_remove (prev->next, link);
2841                                        html_link_free (link);
2842                                        link = plink;
2843                                }
2844                                plink = link;
2845                                prev = l;
2846                        }
2847                }
2848
2849                if (new_link && prev)
2850                        prev->next = g_slist_prepend (NULL, new_link);
2851        }
2852}
2853
2854void
2855html_text_add_link (HTMLText *text, HTMLEngine *e, gchar *url, gchar *target, gint start_offset, gint end_offset)
2856{
2857        gint start_index, end_index;
2858
2859        html_text_offsets_to_indexes (text, start_offset, end_offset, &start_index, &end_index);
2860        html_text_add_link_full (text, e, url, target, start_index, end_index, start_offset, end_offset);
2861}
2862
2863void
2864html_text_remove_links (HTMLText *text)
2865{
2866        if (text->links) {
2867                free_links (text->links);
2868                text->links = NULL;
2869                html_object_change_set (HTML_OBJECT (text), HTML_CHANGE_RECALC_PI);
2870        }
2871}
2872
2873HTMLTextSlave *
2874html_text_get_slave_at_offset (HTMLObject *o, gint offset)
2875{
2876        if (!o || (!HTML_IS_TEXT (o) && !HTML_IS_TEXT_SLAVE (o)))
2877                return NULL;
2878
2879        if (HTML_IS_TEXT (o))
2880                o = o->next;
2881
2882        while (o && HTML_IS_TEXT_SLAVE (o)) {
2883                if (HTML_IS_TEXT_SLAVE (o) && HTML_TEXT_SLAVE (o)->posStart <= offset
2884                    && (offset < HTML_TEXT_SLAVE (o)->posStart + HTML_TEXT_SLAVE (o)->posLen
2885                        || (offset == HTML_TEXT_SLAVE (o)->posStart + HTML_TEXT_SLAVE (o)->posLen && HTML_TEXT_SLAVE (o)->owner->text_len == offset)))
2886                        return HTML_TEXT_SLAVE (o);
2887                o = o->next;
2888        }
2889
2890        return NULL;
2891}
2892
2893Link *
2894html_text_get_link_slaves_at_offset (HTMLText *text, gint offset, HTMLTextSlave **start, HTMLTextSlave **end)
2895{
2896        Link *link = html_text_get_link_at_offset (text, offset);
2897
2898        if (link) {
2899                *start = html_text_get_slave_at_offset (HTML_OBJECT (text), link->start_offset);
2900                *end = html_text_get_slave_at_offset (HTML_OBJECT (*start), link->end_offset);
2901
2902                if (*start && *end)
2903                        return link;
2904        }
2905
2906        return NULL;
2907}
2908
2909gboolean
2910html_text_get_link_rectangle (HTMLText *text, HTMLPainter *painter, gint offset, gint *x1, gint *y1, gint *x2, gint *y2)
2911{
2912        HTMLTextSlave *start;
2913        HTMLTextSlave *end;
2914        Link *link;
2915
2916        link = html_text_get_link_slaves_at_offset (text, offset, &start, &end);
2917        if (link) {
2918                gint xs, ys, xe, ye;
2919
2920                html_object_calc_abs_position (HTML_OBJECT (start), &xs, &ys);
2921                xs += html_text_calc_part_width (text, painter, html_text_slave_get_text (start), start->posStart, link->start_offset - start->posStart, NULL, NULL);
2922                ys -= HTML_OBJECT (start)->ascent;
2923
2924                html_object_calc_abs_position (HTML_OBJECT (end), &xe, &ye);
2925                xe += HTML_OBJECT (end)->width;
2926                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);
2927                ye += HTML_OBJECT (end)->descent;
2928
2929                *x1 = MIN (xs, xe);
2930                *y1 = MIN (ys, ye);
2931                *x2 = MAX (xs, xe);
2932                *y2 = MAX (ys, ye);
2933
2934                return TRUE;
2935        }
2936
2937        return FALSE;
2938}
2939
2940gboolean
2941html_text_prev_link_offset (HTMLText *text, gint *offset)
2942{
2943        GSList *l;
2944
2945        for (l = text->links; l; l = l->next) {
2946                Link *link = (Link *) l->data;
2947
2948                if (link->start_offset <= *offset && *offset <= link->end_offset) {
2949                        if (l->next) {
2950                                *offset = ((Link *) l->next->data)->end_offset - 1;
2951                                return TRUE;
2952                        }
2953                        break;
2954                }
2955        }
2956
2957        return FALSE;
2958}
2959
2960gboolean
2961html_text_next_link_offset (HTMLText *text, gint *offset)
2962{
2963        GSList *l, *prev = NULL;
2964
2965        for (l = text->links; l; l = l->next) {
2966                Link *link = (Link *) l->data;
2967
2968                if (link->start_offset <= *offset && *offset <= link->end_offset) {
2969                        if (prev) {
2970                                *offset = ((Link *) prev->data)->start_offset + 1;
2971                                return TRUE;
2972                        }
2973                        break;
2974                }
2975                prev = l;
2976        }
2977
2978        return FALSE;
2979}
2980
2981gboolean
2982html_text_first_link_offset (HTMLText *text, gint *offset)
2983{
2984        if (text->links)
2985                *offset = ((Link *) g_slist_last (text->links)->data)->start_offset + 1;
2986
2987        return text->links != NULL;
2988}
2989
2990gboolean
2991html_text_last_link_offset (HTMLText *text, gint *offset)
2992{
2993        if (text->links)
2994                *offset = ((Link *) text->links->data)->end_offset - 1;
2995
2996        return text->links != NULL;
2997}
2998
2999gchar *
3000html_text_get_link_text (HTMLText *text, gint offset)
3001{
3002        Link *link = html_text_get_link_at_offset (text, offset);
3003        gchar *start;
3004
3005        start = html_text_get_text (text, link->start_offset);
3006
3007        return g_strndup (start, g_utf8_offset_to_pointer (start, link->end_offset - link->start_offset) - start);
3008}
3009
3010void
3011html_link_set_url_and_target (Link *link, gchar *url, gchar *target)
3012{
3013        if (!link)
3014                return;
3015
3016        g_free (link->url);
3017        g_free (link->target);
3018
3019        link->url = g_strdup (url);
3020        link->target = g_strdup (target);
3021}
3022
3023Link *
3024html_link_dup (Link *l)
3025{
3026        Link *nl = g_new (Link, 1);
3027
3028        nl->url = g_strdup (l->url);
3029        nl->target = g_strdup (l->target);
3030        nl->start_offset = l->start_offset;
3031        nl->end_offset = l->end_offset;
3032        nl->start_index = l->start_index;
3033        nl->end_index = l->end_index;
3034
3035        return nl;
3036}
3037
3038void
3039html_link_free (Link *link)
3040{
3041        g_return_if_fail (link != NULL);
3042
3043        g_free (link->url);
3044        g_free (link->target);
3045        g_free (link);
3046}
3047
3048gboolean
3049html_link_equal (Link *l1, Link *l2)
3050{
3051        return l1->url && l2->url && !strcasecmp (l1->url, l2->url)
3052                && (l1->target == l2->target || (l1->target && l2->target && !strcasecmp (l1->target, l2->target)));
3053}
3054
3055Link *
3056html_link_new (gchar *url, gchar *target, guint start_index, guint end_index, gint start_offset, gint end_offset)
3057{
3058        Link *link = g_new0 (Link, 1);
3059
3060        link->url = g_strdup (url);
3061        link->target = g_strdup (target);
3062        link->start_offset = start_offset;
3063        link->end_offset = end_offset;
3064        link->start_index = start_index;
3065        link->end_index = end_index;
3066
3067        return link;
3068}
3069
3070/* extended pango attributes */
3071
3072static PangoAttribute *
3073html_pango_attr_font_size_copy (const PangoAttribute *attr)
3074{
3075        HTMLPangoAttrFontSize *font_size_attr = (HTMLPangoAttrFontSize *) attr, *new_attr;
3076
3077        new_attr = (HTMLPangoAttrFontSize *) html_pango_attr_font_size_new (font_size_attr->style);
3078        new_attr->attr_int.value = font_size_attr->attr_int.value;
3079
3080        return (PangoAttribute *) new_attr;
3081}
3082
3083static void
3084html_pango_attr_font_size_destroy (PangoAttribute *attr)
3085{
3086        g_free (attr);
3087}
3088
3089static gboolean
3090html_pango_attr_font_size_equal (const PangoAttribute *attr1, const PangoAttribute *attr2)
3091{
3092        const HTMLPangoAttrFontSize *font_size_attr1 = (const HTMLPangoAttrFontSize *) attr1;
3093        const HTMLPangoAttrFontSize *font_size_attr2 = (const HTMLPangoAttrFontSize *) attr2;
3094 
3095        return (font_size_attr1->style == font_size_attr2->style);
3096}
3097
3098void
3099html_pango_attr_font_size_calc (HTMLPangoAttrFontSize *attr, HTMLEngine *e)
3100{
3101        gint size, base_size, real_size;
3102
3103        base_size = (attr->style & GTK_HTML_FONT_STYLE_FIXED) ? e->painter->font_manager.fix_size : e->painter->font_manager.var_size;
3104        if ((attr->style & GTK_HTML_FONT_STYLE_SIZE_MASK) != 0)
3105                size = (attr->style & GTK_HTML_FONT_STYLE_SIZE_MASK) - GTK_HTML_FONT_STYLE_SIZE_3;
3106        else
3107                size = 0;
3108        real_size = e->painter->font_manager.magnification * ((gdouble) base_size + (size > 0 ? (1 << size) : size) * base_size/8.0);
3109
3110        attr->attr_int.value = real_size;
3111}
3112
3113static const PangoAttrClass html_pango_attr_font_size_klass = {
3114        PANGO_ATTR_SIZE,
3115        html_pango_attr_font_size_copy,
3116        html_pango_attr_font_size_destroy,
3117        html_pango_attr_font_size_equal
3118};
3119
3120PangoAttribute *
3121html_pango_attr_font_size_new (GtkHTMLFontStyle style)
3122{
3123        HTMLPangoAttrFontSize *result = g_new (HTMLPangoAttrFontSize, 1);
3124        result->attr_int.attr.klass = &html_pango_attr_font_size_klass;
3125        result->style = style;
3126
3127        return (PangoAttribute *) result;
3128}
3129
3130static gboolean
3131calc_font_size_filter (PangoAttribute *attr, gpointer data)
3132{
3133        HTMLEngine *e = HTML_ENGINE (data);
3134
3135        if (attr->klass->type == PANGO_ATTR_SIZE)
3136                html_pango_attr_font_size_calc ((HTMLPangoAttrFontSize *) attr, e);
3137        else if (attr->klass->type == PANGO_ATTR_FAMILY) {
3138                /* FIXME: this is not very nice. we set it here as it's only used when fonts changed.
3139                   once family in style is used again, that code must be updated */
3140                PangoAttrString *sa = (PangoAttrString *) attr;
3141                g_free (sa->value);
3142                sa->value = g_strdup (e->painter->font_manager.fixed.face);
3143        }
3144
3145        return FALSE;
3146}
3147
3148void
3149html_text_calc_font_size (HTMLText *text, HTMLEngine *e)
3150{
3151        pango_attr_list_filter (text->attr_list, calc_font_size_filter, e);
3152}
3153
3154static GtkHTMLFontStyle
3155style_from_attrs (PangoAttrIterator *iter)
3156{
3157        GtkHTMLFontStyle style = GTK_HTML_FONT_STYLE_DEFAULT;
3158        GSList *list, *l;
3159
3160        list = pango_attr_iterator_get_attrs (iter);
3161        for (l = list; l; l = l->next) {
3162                PangoAttribute *attr = (PangoAttribute *) l->data;
3163
3164                switch (attr->klass->type) {
3165                case PANGO_ATTR_WEIGHT:
3166                        style |= GTK_HTML_FONT_STYLE_BOLD;
3167                        break;
3168                case PANGO_ATTR_UNDERLINE:
3169                        style |= GTK_HTML_FONT_STYLE_UNDERLINE;
3170                        break;
3171                case PANGO_ATTR_STRIKETHROUGH:
3172                        style |= GTK_HTML_FONT_STYLE_STRIKEOUT;
3173                        break;
3174                case PANGO_ATTR_STYLE:
3175                        style |= GTK_HTML_FONT_STYLE_ITALIC;
3176                        break;
3177                case PANGO_ATTR_SIZE:
3178                        style |= ((HTMLPangoAttrFontSize *) attr)->style;
3179                        break;
3180                case PANGO_ATTR_FAMILY:
3181                        style |= GTK_HTML_FONT_STYLE_FIXED;
3182                        break;
3183                default:
3184                        break;
3185                }
3186        }
3187
3188        html_text_free_attrs (list);
3189
3190        return style;
3191}
3192
3193GtkHTMLFontStyle
3194html_text_get_fontstyle_at_index (HTMLText *text, gint index)
3195{
3196        GtkHTMLFontStyle style = GTK_HTML_FONT_STYLE_DEFAULT;
3197        PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
3198
3199        if (iter) {
3200                do {
3201                        gint start_index, end_index;
3202
3203                        pango_attr_iterator_range (iter, &start_index, &end_index);
3204                        if (start_index <= index && index <= end_index) {
3205                                style |= style_from_attrs (iter);
3206                                break;
3207                        }
3208                } while (pango_attr_iterator_next (iter));
3209
3210                pango_attr_iterator_destroy (iter);
3211        }
3212
3213        return style;
3214}
3215
3216GtkHTMLFontStyle
3217html_text_get_style_conflicts (HTMLText *text, GtkHTMLFontStyle style, gint start_index, gint end_index)
3218{
3219        GtkHTMLFontStyle conflicts = GTK_HTML_FONT_STYLE_DEFAULT;
3220        PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
3221
3222        if (iter) {
3223                do {
3224                        gint iter_start_index, iter_end_index;
3225
3226                        pango_attr_iterator_range (iter, &iter_start_index, &iter_end_index);
3227                        if (MAX (start_index, iter_start_index)  < MIN (end_index, iter_end_index))
3228                                conflicts |= style_from_attrs (iter) ^ style;
3229                        if (iter_start_index > end_index)
3230                                break;
3231                } while (pango_attr_iterator_next (iter));
3232
3233                pango_attr_iterator_destroy (iter);
3234        }
3235
3236        return conflicts;
3237}
3238
3239void
3240html_text_change_attrs (PangoAttrList *attr_list, GtkHTMLFontStyle style, HTMLEngine *e, gint start_index, gint end_index, gboolean avoid_default_size)
3241{
3242        PangoAttribute *attr;
3243
3244        /* style */
3245        if (style & GTK_HTML_FONT_STYLE_BOLD) {
3246                attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
3247                attr->start_index = start_index;
3248                attr->end_index = end_index;
3249                pango_attr_list_change (attr_list, attr);
3250        }
3251
3252        if (style & GTK_HTML_FONT_STYLE_ITALIC) {
3253                attr = pango_attr_style_new (PANGO_STYLE_ITALIC);
3254                attr->start_index = start_index;
3255                attr->end_index = end_index;
3256                pango_attr_list_change (attr_list, attr);
3257        }
3258
3259        if (style & GTK_HTML_FONT_STYLE_UNDERLINE) {
3260                attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
3261                attr->start_index = start_index;
3262                attr->end_index = end_index;
3263                pango_attr_list_change (attr_list, attr);
3264        }
3265
3266        if (style & GTK_HTML_FONT_STYLE_STRIKEOUT) {
3267                attr = pango_attr_strikethrough_new (TRUE);
3268                attr->start_index = start_index;
3269                attr->end_index = end_index;
3270                pango_attr_list_change (attr_list, attr);
3271        }
3272
3273        if (style & GTK_HTML_FONT_STYLE_FIXED) {
3274                attr = pango_attr_family_new (e->painter->font_manager.fixed.face ? e->painter->font_manager.fixed.face : "Monospace");
3275                attr->start_index = start_index;
3276                attr->end_index = end_index;
3277                pango_attr_list_change (attr_list, attr);
3278        }
3279
3280        if (!avoid_default_size
3281            || (((style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_DEFAULT)
3282                && ((style & GTK_HTML_FONT_STYLE_SIZE_MASK) != GTK_HTML_FONT_STYLE_SIZE_3))) {
3283                attr = html_pango_attr_font_size_new (style);
3284                html_pango_attr_font_size_calc ((HTMLPangoAttrFontSize *) attr, e);
3285                attr->start_index = start_index;
3286                attr->end_index = end_index;
3287                pango_attr_list_change (attr_list, attr);
3288        }
3289}
3290
3291void
3292html_text_set_style_in_range (HTMLText *text, GtkHTMLFontStyle style, HTMLEngine *e, gint start_index, gint end_index)
3293{
3294        html_text_change_attrs (text->attr_list, style, e, start_index, end_index, TRUE);
3295}
3296
3297void
3298html_text_set_style (HTMLText *text, GtkHTMLFontStyle style, HTMLEngine *e)
3299{
3300        html_text_set_style_in_range (text, style, e, 0, text->text_bytes);
3301}
3302
3303static gboolean
3304unset_style_filter (PangoAttribute *attr, gpointer data)
3305{
3306        GtkHTMLFontStyle style = GPOINTER_TO_INT (data);
3307
3308        switch (attr->klass->type) {
3309        case PANGO_ATTR_WEIGHT:
3310                if (style & GTK_HTML_FONT_STYLE_BOLD)
3311                        return TRUE;
3312                break;
3313        case PANGO_ATTR_STYLE:
3314                if (style & GTK_HTML_FONT_STYLE_ITALIC)
3315                        return TRUE;
3316                break;
3317        case PANGO_ATTR_UNDERLINE:
3318                if (style & GTK_HTML_FONT_STYLE_UNDERLINE)
3319                        return TRUE;
3320                break;
3321        case PANGO_ATTR_STRIKETHROUGH:
3322                if (style & GTK_HTML_FONT_STYLE_STRIKEOUT)
3323                        return TRUE;
3324                break;
3325        case PANGO_ATTR_SIZE:
3326                if (((HTMLPangoAttrFontSize *) attr)->style & style)
3327                        return TRUE;
3328                break;
3329        case PANGO_ATTR_FAMILY:
3330                if (style & GTK_HTML_FONT_STYLE_FIXED)
3331                        return TRUE;
3332                break;
3333        default:
3334                break;
3335        }
3336
3337        return FALSE;
3338}
3339
3340void
3341html_text_unset_style (HTMLText *text, GtkHTMLFontStyle style)
3342{
3343        pango_attr_list_filter (text->attr_list, unset_style_filter, GINT_TO_POINTER (style));
3344}
3345
3346static HTMLColor *
3347color_from_attrs (PangoAttrIterator *iter)
3348{
3349        HTMLColor *color = NULL;
3350        GSList *list, *l;
3351
3352        list = pango_attr_iterator_get_attrs (iter);
3353        for (l = list; l; l = l->next) {
3354                PangoAttribute *attr = (PangoAttribute *) l->data;
3355                PangoAttrColor *ca;
3356
3357                switch (attr->klass->type) {
3358                case PANGO_ATTR_FOREGROUND:
3359                        ca = (PangoAttrColor *) attr;
3360                        color = html_color_new_from_rgb (ca->color.red, ca->color.green, ca->color.blue);
3361                        break;
3362                default:
3363                        break;
3364                }
3365        }
3366
3367        html_text_free_attrs (list);
3368
3369        return color;
3370}
3371
3372static HTMLColor *
3373html_text_get_first_color_in_range (HTMLText *text, HTMLEngine *e, gint start_index, gint end_index)
3374{
3375        HTMLColor *color = NULL;
3376        PangoAttrIterator *iter = pango_attr_list_get_iterator (text->attr_list);
3377
3378        if (iter) {
3379                do {
3380                        gint iter_start_index, iter_end_index;
3381
3382                        pango_attr_iterator_range (iter, &iter_start_index, &iter_end_index);
3383                        if (MAX (iter_start_index, start_index) <= MIN (iter_end_index, end_index)) {
3384                                color = color_from_attrs (iter);
3385                                break;
3386                        }
3387                } while (pango_attr_iterator_next (iter));
3388
3389                pango_attr_iterator_destroy (iter);
3390        }
3391
3392        if (!color) {
3393                color = html_colorset_get_color (e->settings->color_set, HTMLTextColor);
3394                html_color_ref (color);
3395        }
3396
3397        return color;
3398}
3399
3400HTMLColor *
3401html_text_get_color_at_index (HTMLText *text, HTMLEngine *e, gint index)
3402{
3403        return html_text_get_first_color_in_range (text, e, index, index);
3404}
3405
3406HTMLColor *
3407html_text_get_color (HTMLText *text, HTMLEngine *e, gint start_index)
3408{
3409        return html_text_get_first_color_in_range (text, e, start_index, text->text_bytes);
3410}
3411
3412void
3413html_text_set_color_in_range (HTMLText *text, HTMLColor *color, gint start_index, gint end_index)
3414{
3415        PangoAttribute *attr = pango_attr_foreground_new (color->color.red, color->color.green, color->color.blue);
3416
3417        attr->start_index = start_index;
3418        attr->end_index = end_index;
3419        pango_attr_list_change (text->attr_list, attr);
3420}
3421
3422void
3423html_text_set_color (HTMLText *text, HTMLColor *color)
3424{
3425        html_text_set_color_in_range (text, color, 0, text->text_bytes);
3426}
Note: See TracBrowser for help on using the repository browser.