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