1 /*
2  * go-pango-extras.c: Various utility routines that should have been in pango.
3  *
4  * Authors:
5  *    Morten Welinder (terra@gnome.org)
6  *    Andreas J. Guelzow (aguelzow@pyrshep.ca)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of the
11  * License, or (at your option) version 3.
12  *
13  * This program 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
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
21  * USA
22  */
23 
24 #include <goffice/goffice-config.h>
25 #include "go-pango-extras.h"
26 #include "go-glib-extras.h"
27 #include <string.h>
28 
29 struct cb_splice {
30 	guint pos, len;
31 	PangoAttrList *result;
32 };
33 
34 static gboolean
cb_splice(PangoAttribute * attr,gpointer _data)35 cb_splice (PangoAttribute *attr, gpointer _data)
36 {
37 	struct cb_splice *data = _data;
38 
39 	if (attr->start_index >= data->pos) {
40 		PangoAttribute *new_attr = pango_attribute_copy (attr);
41 		new_attr->start_index += data->len;
42 		new_attr->end_index += data->len;
43 		pango_attr_list_insert (data->result, new_attr);
44 	} else if (attr->end_index <= data->pos) {
45 		PangoAttribute *new_attr = pango_attribute_copy (attr);
46 		pango_attr_list_insert (data->result, new_attr);
47 	} else {
48 		PangoAttribute *new_attr = pango_attribute_copy (attr);
49 		new_attr->end_index = data->pos;
50 		pango_attr_list_insert (data->result, new_attr);
51 
52 		new_attr = pango_attribute_copy (attr);
53 		new_attr->start_index = data->pos + data->len;
54 		new_attr->end_index += data->len;
55 		pango_attr_list_insert (data->result, new_attr);
56 	}
57 
58 	return FALSE;
59 }
60 
61 static gboolean
cb_splice_true(G_GNUC_UNUSED PangoAttribute * attr,G_GNUC_UNUSED gpointer data)62 cb_splice_true (G_GNUC_UNUSED PangoAttribute *attr, G_GNUC_UNUSED gpointer data)
63 {
64 	return TRUE;
65 }
66 
67 /**
68  * go_pango_attr_list_open_hole:
69  * @tape: An attribute list
70  * @pos: a text position in bytes
71  * @len: length of segment in bytes
72  *
73  * This function opens up a blank segment of attributes.  This is what to
74  * call after inserting a segment into the text described by the attributes.
75  */
76 void
go_pango_attr_list_open_hole(PangoAttrList * tape,guint pos,guint len)77 go_pango_attr_list_open_hole (PangoAttrList *tape, guint pos, guint len)
78 {
79 	/* Clean out the tape.  */
80 	PangoAttrList *tape2 =
81 		pango_attr_list_filter (tape, cb_splice_true, NULL);
82 
83 	if (tape2) {
84 		struct cb_splice data;
85 		data.result = tape;
86 		data.pos = pos;
87 		data.len = len;
88 
89 		(void)pango_attr_list_filter (tape2, cb_splice, &data);
90 		pango_attr_list_unref (tape2);
91 	}
92 }
93 
94 struct cb_erase {
95 	unsigned start_pos, end_pos, len; /* in bytes not chars */
96 };
97 
98 
99 /*
100  *
101  * |       +-------------------+            The attribute
102  *
103  *                                +----+    (1)
104  *                  +------------------+    (2)
105  *                  +------+                (3)
106  *   +--+                                   (4)
107  *   +--------------+                       (5)
108  *   +---------------------------------+    (6)
109  *
110  */
111 static gboolean
cb_delete_filter(PangoAttribute * a,struct cb_erase * change)112 cb_delete_filter (PangoAttribute *a, struct cb_erase *change)
113 {
114 	if (change->start_pos >= a->end_index)
115 		return FALSE;  /* (1) */
116 
117 	if (change->start_pos <= a->start_index) {
118 		if (change->end_pos >= a->end_index)
119 			return TRUE; /* (6) */
120 
121 		a->end_index -= change->len;
122 		if (change->end_pos >= a->start_index)
123 			a->start_index = change->start_pos; /* (5) */
124 		else
125 			a->start_index -= change->len; /* (4) */
126 	} else {
127 		if (change->end_pos >= a->end_index)
128 			a->end_index = change->start_pos;  /* (2) */
129 		else
130 			a->end_index -= change->len; /* (3) */
131 	}
132 
133 	return FALSE;
134 }
135 
136 /**
137  * go_pango_attr_list_erase:
138  * @attrs: An attribute list
139  * @pos: a text position in bytes
140  * @len: length of segment in bytes
141  *
142  * This function erases a segment of attributes.  This is what to call
143  * after deleting a segment from the text described by the attributes.
144  */
145 void
go_pango_attr_list_erase(PangoAttrList * attrs,guint pos,guint len)146 go_pango_attr_list_erase (PangoAttrList *attrs, guint pos, guint len)
147 {
148 	PangoAttrList *gunk;
149 	struct cb_erase data;
150 
151 	if (attrs == NULL)
152 		return;
153 
154 	data.start_pos = pos;
155 	data.end_pos = pos + len;
156 	data.len = len;
157 
158 	gunk = pango_attr_list_filter (attrs,
159 				       (PangoAttrFilterFunc)cb_delete_filter,
160 				       &data);
161 	if (gunk != NULL)
162 		pango_attr_list_unref (gunk);
163 }
164 
165 
166 
167 struct cb_unset {
168 	PangoAttrList *list;
169 	guint start_index, end_index;
170 };
171 
172 static gboolean
cb_unset1(PangoAttribute * attr,gpointer _data)173 cb_unset1 (PangoAttribute *attr, gpointer _data)
174 {
175 	const PangoAttrType *ptype = _data;
176 	return *ptype == PANGO_ATTR_INVALID || *ptype == attr->klass->type;
177 }
178 
179 static gboolean
cb_unset2(PangoAttribute * attr,gpointer _data)180 cb_unset2 (PangoAttribute *attr, gpointer _data)
181 {
182 	struct cb_unset *data = _data;
183 
184 	/* All inside cleared area.  */
185 	if (attr->start_index >= data->start_index &&
186 	    attr->end_index <= data->end_index)
187 		return FALSE;
188 
189 	attr = pango_attribute_copy (attr);
190 
191 	if (attr->end_index > data->start_index && attr->end_index <= data->end_index)
192 		/* Ends inside cleared area.  */
193 		attr->end_index = data->start_index;
194 	else if (attr->start_index >= data->start_index && attr->start_index < data->end_index)
195 		/* Starts inside cleared area.  */
196 		attr->start_index = data->end_index;
197 	else if (attr->start_index < data->start_index && attr->end_index > data->end_index) {
198 		/* Starts before, ends after.  */
199 		PangoAttribute *attr2 = pango_attribute_copy (attr);
200 		attr2->start_index = data->end_index;
201 		pango_attr_list_insert (data->list, attr2);
202 		attr->end_index = data->start_index;
203 	}
204 
205 	pango_attr_list_insert (data->list, attr);
206 
207 	return FALSE;
208 }
209 
210 /**
211  * go_pango_attr_list_unset:
212  * @list: #PangoAttrList
213  * @start: starting character index
214  * @end: last character index
215  * @type: #PangoAttrType
216  *
217  * See http://bugzilla.gnome.org/show_bug.cgi?id=163679
218  **/
219 void
go_pango_attr_list_unset(PangoAttrList * list,gint start,gint end,PangoAttrType type)220 go_pango_attr_list_unset (PangoAttrList  *list,
221 			  gint            start,
222 			  gint            end,
223 			  PangoAttrType   type)
224 {
225 	PangoAttrList *matches;
226 	struct cb_unset data;
227 
228 	g_return_if_fail (list != NULL);
229 
230 	if (start >= end || end < 0)
231 		return;
232 
233 	/*
234 	 * Pull out the attribute subset we even care about.  Notice that this
235 	 * is all-or-nothing for a given type so any reordering will not
236 	 * matter.
237 	 */
238 	matches = pango_attr_list_filter (list, cb_unset1, &type);
239 	if (!matches)
240 		return;
241 
242 	/* Get rid of whole segments and adjust end_index of rest.  */
243 	data.list = list;
244 	data.start_index = start;
245 	data.end_index = end;
246 	(void)pango_attr_list_filter (matches, cb_unset2, &data);
247 
248 	pango_attr_list_unref (matches);
249 }
250 
251 static gboolean
cb_empty(G_GNUC_UNUSED PangoAttribute * attr,gpointer data)252 cb_empty (G_GNUC_UNUSED PangoAttribute *attr, gpointer data)
253 {
254 	gboolean *pempty = data;
255 	*pempty = FALSE;
256 	return FALSE;
257 }
258 
259 gboolean
go_pango_attr_list_is_empty(const PangoAttrList * attrs)260 go_pango_attr_list_is_empty (const PangoAttrList *attrs)
261 {
262 	gboolean empty = TRUE;
263 	if (attrs)
264 		(void)pango_attr_list_filter ((PangoAttrList *)attrs,
265 					      cb_empty, &empty);
266 	return empty;
267 }
268 
269 #ifdef GOFFICE_WITH_GTK
270 static gboolean
go_load_pango_attributes_into_buffer_filter(PangoAttribute * attribute,G_GNUC_UNUSED gpointer data)271 go_load_pango_attributes_into_buffer_filter (PangoAttribute *attribute,
272 					  G_GNUC_UNUSED gpointer data)
273 {
274 	return ((PANGO_ATTR_FOREGROUND == attribute->klass->type) ||
275 		(PANGO_ATTR_RISE == attribute->klass->type));
276 }
277 
278 static gboolean
go_load_pango_attributes_into_buffer_named_filter(PangoAttribute * attribute,G_GNUC_UNUSED gpointer data)279 go_load_pango_attributes_into_buffer_named_filter (PangoAttribute *attribute,
280 						    G_GNUC_UNUSED gpointer data)
281 {
282 	return ((PANGO_ATTR_STYLE == attribute->klass->type) ||
283 		(PANGO_ATTR_WEIGHT == attribute->klass->type) ||
284 		(PANGO_ATTR_UNDERLINE == attribute->klass->type) ||
285 		(PANGO_ATTR_STRIKETHROUGH == attribute->klass->type));
286 }
287 
288 void
go_create_std_tags_for_buffer(GtkTextBuffer * buffer)289 go_create_std_tags_for_buffer (GtkTextBuffer *buffer)
290 {
291 	gtk_text_buffer_create_tag (buffer, "PANGO_STYLE_NORMAL", "style", PANGO_STYLE_NORMAL,
292 				    "style-set", TRUE, NULL);
293 	gtk_text_buffer_create_tag (buffer, "PANGO_STYLE_ITALIC", "style", PANGO_STYLE_ITALIC,
294 				    "style-set", TRUE, NULL);
295 	gtk_text_buffer_create_tag (buffer, "PANGO_STRIKETHROUGH_TRUE", "strikethrough", TRUE,
296 				    "strikethrough-set", TRUE, NULL);
297 	gtk_text_buffer_create_tag (buffer, "PANGO_STRIKETHROUGH_FALSE", "strikethrough", FALSE,
298 				    "strikethrough-set", TRUE, NULL);
299 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_THIN", "weight", 100,
300 				    "weight-set", TRUE, NULL);
301 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_ULTRALIGHT", "weight", PANGO_WEIGHT_ULTRALIGHT,
302 				    "weight-set", TRUE, NULL);
303 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_LIGHT", "weight", PANGO_WEIGHT_LIGHT,
304 				    "weight-set", TRUE, NULL);
305 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_BOOK", "weight", 380,
306 				    "weight-set", TRUE, NULL);
307 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_NORMAL", "weight", PANGO_WEIGHT_NORMAL,
308 				    "weight-set", TRUE, NULL);
309 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_MEDIUM", "weight", 500,
310 				    "weight-set", TRUE, NULL);
311 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_SEMIBOLD", "weight", PANGO_WEIGHT_SEMIBOLD,
312 				    "weight-set", TRUE, NULL);
313 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_BOLD", "weight", PANGO_WEIGHT_BOLD,
314 				    "weight-set", TRUE, NULL);
315 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_ULTRABOLD", "weight", PANGO_WEIGHT_ULTRABOLD,
316 				    "weight-set", TRUE, NULL);
317 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_HEAVY", "weight", PANGO_WEIGHT_HEAVY,
318 				    "weight-set", TRUE, NULL);
319 	gtk_text_buffer_create_tag (buffer, "PANGO_WEIGHT_ULTRAHEAVY", "weight", 1000,
320 				    "weight-set", TRUE, NULL);
321 	gtk_text_buffer_create_tag (buffer, "PANGO_UNDERLINE_NONE", "underline", PANGO_UNDERLINE_NONE,
322 				    "underline-set", TRUE, NULL);
323 	gtk_text_buffer_create_tag (buffer, "PANGO_UNDERLINE_SINGLE", "underline", PANGO_UNDERLINE_SINGLE,
324 				    "underline-set", TRUE, NULL);
325 	gtk_text_buffer_create_tag (buffer, "PANGO_UNDERLINE_DOUBLE", "underline", PANGO_UNDERLINE_DOUBLE,
326 				    "underline-set", TRUE, NULL);
327 	gtk_text_buffer_create_tag (buffer, "PANGO_UNDERLINE_LOW", "underline", PANGO_UNDERLINE_LOW,
328 				    "underline-set", TRUE, NULL);
329 	gtk_text_buffer_create_tag (buffer, "PANGO_UNDERLINE_ERROR", "underline", PANGO_UNDERLINE_ERROR,
330 				    "underline-set", TRUE, NULL);
331 }
332 
333 static gint
go_load_pango_byte_to_char(gchar const * str,gint byte)334 go_load_pango_byte_to_char (gchar const *str, gint byte)
335 {
336 	if (byte >= (gint)strlen (str))
337 		return g_utf8_strlen (str, -1);
338 	return g_utf8_pointer_to_offset (str, g_utf8_prev_char (str + byte + 1));
339 }
340 
341 void
go_load_pango_attributes_into_buffer(PangoAttrList * markup,GtkTextBuffer * buffer,gchar const * str)342 go_load_pango_attributes_into_buffer (PangoAttrList  *markup, GtkTextBuffer *buffer, gchar const *str)
343 {
344 	PangoAttrIterator * iter;
345 	PangoAttrList  *copied_markup;
346 	PangoAttrList  *our_markup;
347 
348 	if (markup == NULL)
349 		return;
350 
351 /* For some styles we create named tags. The names are taken from the Pango enums */
352 
353 	copied_markup = pango_attr_list_copy (markup);
354 	our_markup = pango_attr_list_filter (copied_markup,
355 					     go_load_pango_attributes_into_buffer_named_filter,
356 					     NULL);
357 	pango_attr_list_unref (copied_markup);
358 	if (our_markup != NULL) {
359 		iter = pango_attr_list_get_iterator (our_markup);
360 
361 		do {
362 			GSList *attr = pango_attr_iterator_get_attrs (iter);
363 			if (attr != NULL) {
364 				GSList *ptr;
365 				gint start, end;
366 				GtkTextIter start_iter, end_iter;
367 				char const *name;
368 
369 				pango_attr_iterator_range (iter, &start, &end);
370 				start = go_load_pango_byte_to_char (str, start);
371 				end = go_load_pango_byte_to_char (str, end);
372 				gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
373 				gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
374 
375 				for (ptr = attr; ptr != NULL; ptr = ptr->next) {
376 					PangoAttribute *attribute = ptr->data;
377 					GtkTextTag *tag;
378 					int val;
379 
380 					switch (attribute->klass->type) {
381 					case PANGO_ATTR_STYLE:
382 						name = (((PangoAttrInt *)attribute)->value
383 							== PANGO_STYLE_NORMAL)
384 							? "PANGO_STYLE_NORMAL" :
385 							"PANGO_STYLE_ITALIC";
386 						tag = gtk_text_tag_table_lookup
387 							(gtk_text_buffer_get_tag_table (buffer),
388 							 name);
389 						gtk_text_buffer_apply_tag (buffer, tag,
390 									   &start_iter, &end_iter);
391 						break;
392 					case PANGO_ATTR_STRIKETHROUGH:
393 						name = (((PangoAttrInt *)attribute)->value) ?
394 							"PANGO_STRIKETHROUGH_TRUE" :
395 							"PANGO_STRIKETHROUGH_FALSE";
396 						tag = gtk_text_tag_table_lookup
397 							(gtk_text_buffer_get_tag_table (buffer),
398 							 name);
399 						gtk_text_buffer_apply_tag (buffer, tag,
400 									   &start_iter, &end_iter);
401 						break;
402 					case PANGO_ATTR_UNDERLINE:
403 						val = ((PangoAttrInt *)attribute)->value;
404 						if (val == PANGO_UNDERLINE_NONE)
405 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_UNDERLINE_NONE",
406 											   &start_iter, &end_iter);
407 						else if (val == PANGO_UNDERLINE_SINGLE)
408 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_UNDERLINE_SINGLE",
409 											   &start_iter, &end_iter);
410 						else if (val == PANGO_UNDERLINE_DOUBLE)
411 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_UNDERLINE_DOUBLE",
412 											   &start_iter, &end_iter);
413 						else if (val == PANGO_UNDERLINE_LOW)
414 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_UNDERLINE_LOW",
415 											   &start_iter, &end_iter);
416 						else if (val == PANGO_UNDERLINE_ERROR)
417 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_UNDERLINE_ERROR",
418 											   &start_iter, &end_iter);
419 						break;
420 					case PANGO_ATTR_WEIGHT:
421 						val = ((PangoAttrInt *)attribute)->value;
422 						if (val < (PANGO_WEIGHT_THIN + PANGO_WEIGHT_ULTRALIGHT)/2)
423 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_THIN",
424 											   &start_iter, &end_iter);
425 						else if (val < (PANGO_WEIGHT_ULTRALIGHT + PANGO_WEIGHT_LIGHT)/2)
426 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_ULTRALIGHT",
427 											   &start_iter, &end_iter);
428 						else if (val < (PANGO_WEIGHT_LIGHT + PANGO_WEIGHT_BOOK)/2)
429 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_LIGHT",
430 											   &start_iter, &end_iter);
431 						else if (val < (PANGO_WEIGHT_BOOK + PANGO_WEIGHT_NORMAL)/2)
432 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_BOOK",
433 											   &start_iter, &end_iter);
434 						else if (val < (PANGO_WEIGHT_NORMAL + PANGO_WEIGHT_MEDIUM)/2)
435 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_NORMAL",
436 											   &start_iter, &end_iter);
437 						else if (val < (PANGO_WEIGHT_MEDIUM + PANGO_WEIGHT_SEMIBOLD)/2)
438 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_MEDIUM",
439 											   &start_iter, &end_iter);
440 						else if (val < (PANGO_WEIGHT_SEMIBOLD + PANGO_WEIGHT_BOLD)/2)
441 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_SEMIBOLD",
442 											   &start_iter, &end_iter);
443 						else if (val < (PANGO_WEIGHT_BOLD + PANGO_WEIGHT_ULTRABOLD)/2)
444 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_BOLD",
445 											   &start_iter, &end_iter);
446 						else if (val < (PANGO_WEIGHT_ULTRABOLD + PANGO_WEIGHT_HEAVY)/2)
447 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_ULTRABOLD",
448 											   &start_iter, &end_iter);
449 						else if (val < (PANGO_WEIGHT_HEAVY + PANGO_WEIGHT_ULTRAHEAVY)/2)
450 							gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_HEAVY",
451 											   &start_iter, &end_iter);
452 						else gtk_text_buffer_apply_tag_by_name (buffer,"PANGO_WEIGHT_ULTRAHEAVY",
453 											&start_iter, &end_iter);
454 						break;
455 					default:
456 						break;
457 					}
458 				}
459 				g_slist_free_full (attr, (GDestroyNotify)pango_attribute_destroy);
460 			}
461 		} while (pango_attr_iterator_next (iter));
462 		pango_attr_iterator_destroy (iter);
463 		pango_attr_list_unref (our_markup);
464 	}
465 
466 /* For other styles (that are not at true/false type styles) we use unnamed styles */
467 
468 	copied_markup = pango_attr_list_copy (markup);
469 	our_markup = pango_attr_list_filter (copied_markup,
470 					     go_load_pango_attributes_into_buffer_filter,
471 					     NULL);
472 	pango_attr_list_unref (copied_markup);
473 	if (our_markup != NULL) {
474 		iter = pango_attr_list_get_iterator (our_markup);
475 
476 		do {
477 			GSList *attr = pango_attr_iterator_get_attrs (iter);
478 			if (attr != NULL) {
479 				char *string;
480 				GSList *ptr;
481 				gint start, end;
482 				GtkTextIter start_iter, end_iter;
483 				GtkTextTag *tag = gtk_text_buffer_create_tag (buffer, NULL, NULL);
484 				for (ptr = attr; ptr != NULL; ptr = ptr->next) {
485 					PangoAttribute *attribute = ptr->data;
486 					switch (attribute->klass->type) {
487 					case PANGO_ATTR_FOREGROUND:
488 						string = pango_color_to_string
489 							(&((PangoAttrColor *)attribute)->color);
490 						g_object_set (G_OBJECT (tag),
491 							      "foreground", string,
492 							      "foreground-set", TRUE,
493 							      NULL);
494 						g_free (string);
495 						break;
496 					case PANGO_ATTR_RISE:
497 						g_object_set (G_OBJECT (tag),
498 							      "rise",
499 							      ((PangoAttrInt *)attribute)->value,
500 							      "rise-set", TRUE,
501 							      NULL);
502 						break;
503 					default:
504 						break;
505 					}
506 				}
507 				pango_attr_iterator_range (iter, &start, &end);
508 				start = go_load_pango_byte_to_char (str, start);
509 				end = go_load_pango_byte_to_char (str, end);
510 				gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
511 				gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
512 				gtk_text_buffer_apply_tag (buffer, tag, &start_iter, &end_iter);
513 				g_slist_free_full (attr, (GDestroyNotify)pango_attribute_destroy);
514 			}
515 		} while (pango_attr_iterator_next (iter));
516 		pango_attr_iterator_destroy (iter);
517 		pango_attr_list_unref (our_markup);
518 	}
519 }
520 #endif
521 
522 static gboolean
filter_func(PangoAttribute * attribute,G_GNUC_UNUSED gpointer data)523 filter_func (PangoAttribute *attribute, G_GNUC_UNUSED gpointer data)
524 {
525 	PangoAttrType type = attribute->klass->type;
526 	return (type == go_pango_attr_superscript_get_attr_type () ||
527 		type == go_pango_attr_subscript_get_attr_type ());
528 }
529 
530 static void
go_pango_translate_here(PangoAttrIterator * state_iter,PangoAttrIterator * attr_iter,PangoAttrList * attrs)531 go_pango_translate_here (PangoAttrIterator *state_iter,
532 			 PangoAttrIterator *attr_iter, PangoAttrList *attrs)
533 {
534 	double font_scale = 1.;
535 	double scale = 1.;
536 	int rise = 0;
537 	PangoAttribute *pa;
538 	PangoFontDescription *desc;
539 	GSList *the_attrs, *l;
540 	gint range_start, range_end;
541 
542 	pango_attr_iterator_range (attr_iter, &range_start, &range_end);
543 
544 	if (range_start == range_end)
545 		return;
546 
547 	desc = pango_font_description_new ();
548 	pango_attr_iterator_get_font (state_iter, desc, NULL, NULL);
549 	font_scale = pango_font_description_get_size (desc)/
550 		(double)PANGO_SCALE/10.;
551 	pango_font_description_free (desc);
552 
553 	pa = pango_attr_iterator_get
554 		(state_iter, PANGO_ATTR_SCALE);
555 	if (pa)
556 		scale = ((PangoAttrFloat *)pa)->value;
557 	pa = pango_attr_iterator_get
558 		(state_iter, PANGO_ATTR_RISE);
559 	if (pa)
560 		rise = ((PangoAttrInt *)pa)->value;
561 
562 	/* We should probably figured out the default font size used */
563 	/* rather than assuming it is 10 pts * scale */
564 	if (font_scale == 0)
565 		font_scale = scale;
566 
567 	the_attrs = pango_attr_iterator_get_attrs (attr_iter);
568 	for (l = the_attrs; l != NULL; l = l->next) {
569 		PangoAttribute *attribute = l->data;
570 		PangoAttrType type = attribute->klass->type;
571 		if (type == go_pango_attr_superscript_get_attr_type ()) {
572 			GOPangoAttrSuperscript *attr = (GOPangoAttrSuperscript *)attribute;
573 			if (attr->val) {
574 				scale *= GO_SUPERSCRIPT_SCALE;
575 				rise += GO_SUPERSCRIPT_RISE * font_scale;
576 				font_scale *= GO_SUPERSCRIPT_SCALE;
577 			}
578 		} else { /* go_pango_attr_subscript_type */
579 			GOPangoAttrSubscript *attr = (GOPangoAttrSubscript *)attribute;
580 			if (attr->val) {
581 				scale *= GO_SUBSCRIPT_SCALE;
582 				rise += GO_SUBSCRIPT_RISE * font_scale;
583 				font_scale *= GO_SUBSCRIPT_SCALE;
584 			}
585 		}
586 	}
587 
588 	if (the_attrs != NULL) {
589 		PangoAttribute *attr = pango_attr_scale_new (scale);
590 		attr->start_index = range_start;
591 		attr->end_index = range_end;
592 		pango_attr_list_insert (attrs, attr);
593 		attr = pango_attr_rise_new (rise);
594 		attr->start_index = range_start;
595 		attr->end_index = range_end;
596 		pango_attr_list_insert (attrs, attr);
597 	}
598 	g_slist_free_full (the_attrs, (GDestroyNotify)pango_attribute_destroy);
599 }
600 
601 
602 PangoAttrList *
go_pango_translate_attributes(PangoAttrList * attrs)603 go_pango_translate_attributes (PangoAttrList *attrs)
604 {
605 	PangoAttrList *n_attrs, *filtered;
606 
607 	if (attrs == NULL)
608 		return NULL;
609 
610 	n_attrs = pango_attr_list_copy (attrs);
611 	filtered = pango_attr_list_filter (n_attrs, filter_func, NULL);
612 
613 	if (filtered == NULL) {
614 		pango_attr_list_unref (n_attrs);
615 		return attrs;
616 	} else {
617 		PangoAttrIterator *iter, *f_iter;
618 		f_iter = pango_attr_list_get_iterator (filtered);
619 		do {
620 			gint f_range_start, f_range_end;
621 			gint range_start, range_end;
622 			/* We need to restart everytime since we are changing n_attrs */
623 			iter = pango_attr_list_get_iterator (n_attrs);
624 			pango_attr_iterator_range (f_iter, &f_range_start,
625 						   &f_range_end);
626 			pango_attr_iterator_range (iter, &range_start,
627 						   &range_end);
628 			while (range_end <= f_range_start) {
629 				if (!pango_attr_iterator_next (iter))
630 					break;
631 				pango_attr_iterator_range (iter, &range_start,
632 						   &range_end);
633 			}
634 			/* Now range_end > f_range_start >= range_start */
635 			go_pango_translate_here (iter, f_iter, n_attrs);
636 			pango_attr_iterator_destroy (iter);
637 		} while (pango_attr_iterator_next (f_iter));
638 		pango_attr_iterator_destroy (f_iter);
639 	}
640 	pango_attr_list_unref (filtered);
641 	return n_attrs;
642 }
643 
644 void
go_pango_translate_layout(PangoLayout * layout)645 go_pango_translate_layout (PangoLayout *layout)
646 {
647 	PangoAttrList *attrs, *n_attrs;
648 
649 	g_return_if_fail (layout != NULL);
650 
651 	attrs = pango_layout_get_attributes (layout);
652 	n_attrs = go_pango_translate_attributes (attrs);
653 	if (attrs != n_attrs) {
654 		pango_layout_set_attributes (layout, n_attrs);
655 		pango_attr_list_unref (n_attrs);
656 	}
657 
658 }
659 
660 PangoAttrType
go_pango_attr_subscript_get_attr_type(void)661 go_pango_attr_subscript_get_attr_type (void)
662 {
663 	static PangoAttrType go_pango_attr_subscript_type = PANGO_ATTR_INVALID;
664 
665 	if(go_pango_attr_subscript_type == PANGO_ATTR_INVALID)
666 		go_pango_attr_subscript_type =
667 			pango_attr_type_register ("GOSubscript");
668 
669 	return go_pango_attr_subscript_type;
670 }
671 
672 PangoAttrType
go_pango_attr_subscript_get_type(void)673 go_pango_attr_subscript_get_type (void)
674 {
675 	return go_pango_attr_subscript_get_attr_type ();
676 }
677 
678 PangoAttrType
go_pango_attr_superscript_get_attr_type(void)679 go_pango_attr_superscript_get_attr_type (void)
680 {
681 	static PangoAttrType go_pango_attr_superscript_type = PANGO_ATTR_INVALID;
682 
683 	if(go_pango_attr_superscript_type == PANGO_ATTR_INVALID)
684 		go_pango_attr_superscript_type =
685 			pango_attr_type_register ("GOSuperscript");
686 
687 	return go_pango_attr_superscript_type;
688 }
689 
690 PangoAttrType
go_pango_attr_superscript_get_type(void)691 go_pango_attr_superscript_get_type (void)
692 {
693 	return go_pango_attr_superscript_get_attr_type ();
694 }
695 
696 static PangoAttribute *
go_pango_attr_subscript_copy(PangoAttribute const * attr)697 go_pango_attr_subscript_copy (PangoAttribute const *attr)
698 {
699 	GOPangoAttrSubscript *at = (GOPangoAttrSubscript *)attr;
700 	return go_pango_attr_subscript_new (at->val);
701 }
702 
703 static PangoAttribute *
go_pango_attr_superscript_copy(PangoAttribute const * attr)704 go_pango_attr_superscript_copy (PangoAttribute const *attr)
705 {
706 	GOPangoAttrSuperscript *at = (GOPangoAttrSuperscript *)attr;
707 	return go_pango_attr_superscript_new (at->val);
708 }
709 
710 static void
go_pango_attr_destroy(PangoAttribute * attr)711 go_pango_attr_destroy (PangoAttribute *attr)
712 {
713 	g_free (attr);
714 }
715 
716 static gboolean
go_pango_attr_superscript_compare(PangoAttribute const * attr1,PangoAttribute const * attr2)717 go_pango_attr_superscript_compare (PangoAttribute const *attr1,
718 				   PangoAttribute const *attr2)
719 {
720 	GOPangoAttrSuperscript *at1 = (GOPangoAttrSuperscript *)attr1;
721 	GOPangoAttrSuperscript *at2 = (GOPangoAttrSuperscript *)attr2;
722 	return (at1->val == at2->val);
723 }
724 
725 static gboolean
go_pango_attr_subscript_compare(PangoAttribute const * attr1,PangoAttribute const * attr2)726 go_pango_attr_subscript_compare (PangoAttribute const *attr1,
727 				 PangoAttribute const *attr2)
728 {
729 	GOPangoAttrSubscript *at1 = (GOPangoAttrSubscript *)attr1;
730 	GOPangoAttrSubscript *at2 = (GOPangoAttrSubscript *)attr2;
731 	return (at1->val == at2->val);
732 }
733 
734 /**
735  * go_pango_attr_subscript_new: (skip)
736  * @val: %TRUE if the characters must be lowered.
737  *
738  * Returns: (transfer full): a subscript attribute.
739  **/
740 PangoAttribute *
go_pango_attr_subscript_new(gboolean val)741 go_pango_attr_subscript_new (gboolean val)
742 {
743 	GOPangoAttrSubscript *result;
744 
745 	static PangoAttrClass klass = {
746 		0,
747 		go_pango_attr_subscript_copy,
748 		go_pango_attr_destroy,
749 		go_pango_attr_subscript_compare
750 	};
751 
752 	if (!klass.type)
753 		klass.type = go_pango_attr_subscript_get_attr_type ();
754 
755 	result = g_new (GOPangoAttrSubscript, 1);
756 	result->attr.klass = &klass;
757 	result->val = val;
758 
759 	return (PangoAttribute *) result;
760 }
761 
762 /**
763  * go_pango_attr_superscript_new: (skip)
764  * @val: %TRUE if the characters must be raised.
765  *
766  * Returns: (transfer full): a superscript attribute.
767  **/
768 PangoAttribute *
go_pango_attr_superscript_new(gboolean val)769 go_pango_attr_superscript_new (gboolean val)
770 {
771 	GOPangoAttrSuperscript *result;
772 
773 	static PangoAttrClass klass = {
774 		0,
775 		go_pango_attr_superscript_copy,
776 		go_pango_attr_destroy,
777 		go_pango_attr_superscript_compare
778 	};
779 
780 	if (!klass.type)
781 		klass.type = go_pango_attr_superscript_get_attr_type ();
782 
783 	result = g_new (GOPangoAttrSuperscript, 1);
784 	result->attr.klass = &klass;
785 	result->val = val;
786 
787 	return (PangoAttribute *) result;
788 }
789 
790 static int
go_pango_attr_as_markup_string(PangoAttribute * a,GString * gstr)791 go_pango_attr_as_markup_string (PangoAttribute *a, GString *gstr)
792 {
793 	int spans = 0;
794 
795 	switch (a->klass->type) {
796 	case PANGO_ATTR_FONT_DESC :
797 		{
798 			char *str = pango_font_description_to_string
799 				(((PangoAttrFontDesc *)a)->desc);
800 			spans += 1;
801 			g_string_append_printf (gstr, "<span font_desc=\"%s\">", str);
802 			g_free (str);
803 		}
804 		break;
805 	case PANGO_ATTR_FAMILY :
806 		spans += 1;
807 		g_string_append_printf (gstr, "<span font_family=\"%s\">",
808 					((PangoAttrString *)a)->value);
809 		break;
810 	case PANGO_ATTR_ABSOLUTE_SIZE :
811 	case PANGO_ATTR_SIZE :
812 		spans += 1;
813 		g_string_append_printf (gstr, "<span font_size=\"%i\">",
814 					((PangoAttrSize *)a)->size);
815 		break;
816 	case PANGO_ATTR_RISE:
817 		spans += 1;
818 		g_string_append_printf (gstr, "<span rise=\"%i\">",
819 					((PangoAttrInt *)a)->value);
820 		break;
821 	case PANGO_ATTR_STYLE :
822 		spans += 1;
823 		switch (((PangoAttrInt *)a)->value) {
824 		case PANGO_STYLE_ITALIC:
825 			g_string_append (gstr, "<span font_style=\"italic\">");
826 			break;
827 		case PANGO_STYLE_OBLIQUE:
828 			g_string_append (gstr, "<span font_style=\"oblique\">");
829 			break;
830 		case PANGO_STYLE_NORMAL:
831 		default:
832 			g_string_append (gstr, "<span font_style=\"normal\">");
833 			break;
834 		}
835 		break;
836 	case PANGO_ATTR_WEIGHT :
837 		spans += 1;
838 		g_string_append_printf (gstr, "<span font_weight=\"%i\">",
839 					((PangoAttrInt *)a)->value);
840 	break;
841 	case PANGO_ATTR_STRIKETHROUGH :
842 		spans += 1;
843 		if (((PangoAttrInt *)a)->value)
844 			g_string_append (gstr, "<span strikethrough=\"true\">");
845 		else
846 			g_string_append (gstr, "<span strikethrough=\"false\">");
847 		break;
848 	case PANGO_ATTR_UNDERLINE :
849 		spans += 1;
850 		switch (((PangoAttrInt *)a)->value) {
851 		case PANGO_UNDERLINE_SINGLE:
852 			g_string_append (gstr, "<span underline=\"single\">");
853 			break;
854 		case PANGO_UNDERLINE_DOUBLE:
855 			g_string_append (gstr, "<span underline=\"double\">");
856 			break;
857 		case PANGO_UNDERLINE_LOW:
858 			g_string_append (gstr, "<span underline=\"low\">");
859 			break;
860 		case PANGO_UNDERLINE_ERROR:
861 			g_string_append (gstr, "<span underline=\"error\">");
862 			break;
863 		case PANGO_UNDERLINE_NONE:
864 		default:
865 			g_string_append (gstr, "<span underline=\"none\">");
866 			break;
867 		}
868 		break;
869 	case PANGO_ATTR_LANGUAGE :
870 		spans += 1;
871 		g_string_append_printf (gstr, "<span lang=\"%s\">",
872 					pango_language_to_string (((PangoAttrLanguage *)a)->value));
873 		break;
874 	case PANGO_ATTR_VARIANT :
875 		spans += 1;
876 		if (((PangoAttrInt *)a)->value == PANGO_VARIANT_NORMAL)
877 			g_string_append (gstr, "<span font_variant=\"normal\">");
878 		else
879 			g_string_append (gstr, "<span font_variant=\"smallcaps\">");
880 		break;
881 	case PANGO_ATTR_LETTER_SPACING :
882 		spans += 1;
883 		g_string_append_printf (gstr, "<span letter_spacing=\"%i\">",
884 					((PangoAttrInt *)a)->value);
885 		break;
886 	case PANGO_ATTR_FALLBACK :
887 		spans += 1;
888 		if (((PangoAttrInt *)a)->value)
889 			g_string_append (gstr, "<span fallback=\"true\">");
890 		else
891 			g_string_append (gstr, "<span fallback=\"false\">");
892 		break;
893 	case PANGO_ATTR_STRETCH :
894 		spans += 1;
895 		switch (((PangoAttrInt *)a)->value) {
896 		case PANGO_STRETCH_ULTRA_CONDENSED:
897 			g_string_append (gstr, "<span font_stretch=\"ultracondensed\">");
898 			break;
899 		case PANGO_STRETCH_EXTRA_CONDENSED:
900 			g_string_append (gstr, "<span font_stretch=\"extracondensed\">");
901 			break;
902 		case PANGO_STRETCH_CONDENSED:
903 			g_string_append (gstr, "<span font_stretch=\"condensed\">");
904 			break;
905 		case PANGO_STRETCH_SEMI_CONDENSED:
906 			g_string_append (gstr, "<span font_stretch=\"semicondensed\">");
907 			break;
908 		case PANGO_STRETCH_SEMI_EXPANDED:
909 			g_string_append (gstr, "<span font_stretch=\"semiexpanded\">");
910 			break;
911 		case PANGO_STRETCH_EXPANDED:
912 			g_string_append (gstr, "<span font_stretch=\"expanded\">");
913 			break;
914 		case PANGO_STRETCH_EXTRA_EXPANDED:
915 			g_string_append (gstr, "<span font_stretch=\"extraexpanded\">");
916 			break;
917 		case PANGO_STRETCH_ULTRA_EXPANDED:
918 			g_string_append (gstr, "<span font_stretch=\"ultraexpanded\">");
919 			break;
920 		case PANGO_STRETCH_NORMAL:
921 		default:
922 			g_string_append (gstr, "<span font_stretch=\"normal\">");
923 			break;
924 		}
925 		break;
926 	case PANGO_ATTR_GRAVITY :
927 		spans += 1;
928 		switch (((PangoAttrInt *)a)->value) {
929 		case PANGO_GRAVITY_SOUTH:
930 			g_string_append (gstr, "<span gravity=\"south\">");
931 			break;
932 		case PANGO_GRAVITY_EAST:
933 			g_string_append (gstr, "<span gravity=\"east\">");
934 			break;
935 		case PANGO_GRAVITY_NORTH:
936 			g_string_append (gstr, "<span gravity=\"north\">");
937 			break;
938 		case PANGO_GRAVITY_WEST:
939 			g_string_append (gstr, "<span gravity=\"west\">");
940 			break;
941 		case PANGO_GRAVITY_AUTO:
942 		default:
943 			g_string_append (gstr, "<span gravity=\"auto\">");
944 			break;
945 		}
946 		break;
947 	case PANGO_ATTR_GRAVITY_HINT :
948 		spans += 1;
949 		switch (((PangoAttrInt *)a)->value) {
950 		case PANGO_GRAVITY_HINT_LINE:
951 			g_string_append (gstr, "<span gravity_hint=\"line\">");
952 			break;
953 		case PANGO_GRAVITY_HINT_STRONG:
954 			g_string_append (gstr, "<span gravity_hint=\"strong\">");
955 			break;
956 		case PANGO_GRAVITY_HINT_NATURAL:
957 		default:
958 			g_string_append (gstr, "<span gravity_hint=\"natural\">");
959 			break;
960 		}
961 		break;
962 
963 	case PANGO_ATTR_FOREGROUND :
964 		{
965 			PangoColor *color = &((PangoAttrColor *)a)->color;
966 			spans += 1;
967 			g_string_append_printf (gstr, "<span foreground=\"#%02X%02X%02X\">",
968 						color->red, color->green, color->blue);
969 		}
970 		break;
971 	case PANGO_ATTR_BACKGROUND :
972 		{
973 			PangoColor *color = &((PangoAttrColor *)a)->color;
974 			spans += 1;
975 			g_string_append_printf (gstr, "<span background=\"#%02X%02X%02X\">",
976 						color->red, color->green, color->blue);
977 		}
978 		break;
979 	case PANGO_ATTR_UNDERLINE_COLOR :
980 		{
981 			PangoColor *color = &((PangoAttrColor *)a)->color;
982 			spans += 1;
983 			g_string_append_printf (gstr, "<span underline_color=\"#%02X%02X%02X\">",
984 						color->red, color->green, color->blue);
985 		}
986 		break;
987 	case PANGO_ATTR_STRIKETHROUGH_COLOR :
988 		{
989 			PangoColor *color = &((PangoAttrColor *)a)->color;
990 			spans += 1;
991 			g_string_append_printf (gstr, "<span strikethrough_color=\"#%02X%02X%02X\">",
992 						color->red, color->green, color->blue);
993 		}
994 		break;
995 
996 	case PANGO_ATTR_SCALE :
997 	case PANGO_ATTR_SHAPE :
998 	default :
999 		break; /* ignored */
1000 	}
1001 
1002 	return spans;
1003 }
1004 
1005 char *
go_pango_attrs_to_markup(PangoAttrList * attrs,char const * text)1006 go_pango_attrs_to_markup (PangoAttrList *attrs, char const *text)
1007 {
1008 	PangoAttrIterator * iter;
1009 	int handled = 0;
1010 	int from, to;
1011 	int len;
1012 	GString *gstr;
1013 
1014 	if (text == NULL)
1015 		return NULL;
1016 	if (attrs == NULL || go_pango_attr_list_is_empty (attrs))
1017 		return g_strdup (text);
1018 
1019 	len = strlen (text);
1020 	gstr = g_string_sized_new (len + 1);
1021 
1022 	iter = pango_attr_list_get_iterator (attrs);
1023 	do {
1024 		GSList *list, *l;
1025 		int spans = 0;
1026 
1027 		pango_attr_iterator_range (iter, &from, &to);
1028 		to = (to > len) ? len : to;       /* Since "to" can be really big! */
1029 		from = (from > len) ? len : from; /* Since "from" can also be really big! */
1030 		if (from > handled)
1031 			g_string_append_len (gstr, text + handled, from - handled);
1032 		list = pango_attr_iterator_get_attrs (iter);
1033 		for (l = list; l != NULL; l = l->next)
1034 			spans += go_pango_attr_as_markup_string (l->data, gstr);
1035 		g_slist_free (list);
1036 		if (to > from)
1037 			g_string_append_len (gstr, text + from, to - from);
1038 		while (spans-- > 0)
1039 			g_string_append (gstr, "</span>");
1040 		handled = to;
1041 	} while (pango_attr_iterator_next (iter));
1042 
1043 	pango_attr_iterator_destroy (iter);
1044 
1045 	return g_string_free (gstr, FALSE);
1046 }
1047 
1048 
1049 /**
1050  * go_pango_measure_string:
1051  * @context: #PangoContext
1052  * @font_desc: #PangoFontDescription
1053  * @str: The text to measure.
1054  *
1055  * A utility function to measure text.
1056  *
1057  * Returns: the pixel length of @str according to @context.
1058  **/
1059 int
go_pango_measure_string(PangoContext * context,PangoFontDescription const * font_desc,char const * str)1060 go_pango_measure_string (PangoContext *context,
1061 			 PangoFontDescription const *font_desc,
1062 			 char const *str)
1063 {
1064 	PangoLayout *layout = pango_layout_new (context);
1065 	int width;
1066 
1067 	pango_layout_set_text (layout, str, -1);
1068 	pango_layout_set_font_description (layout, font_desc);
1069 	pango_layout_get_pixel_size (layout, &width, NULL);
1070 
1071 	g_object_unref (layout);
1072 
1073 	return width;
1074 }
1075