changeset 16083:f2a4b05407d7

Patch from shlomil in ticket #78. This fixes RTL text support in MSN, and lays the framework so it could be supported in other prpls as well. As added pluses, shlomil removed some duplicate code and fixed some small related bugs. committer: Richard Laager <rlaager@wiktel.com>
author Shlomi Loubaton <shlomister@gmail.com>
date Fri, 13 Apr 2007 04:13:24 +0000
parents 7a7377a86ad1
children a5a831a5f186
files libpurple/protocols/msn/msn-utils.c libpurple/protocols/oscar/oscar.c libpurple/util.c libpurple/util.h pidgin/gtkconv.c pidgin/gtkimhtml.c
diffstat 6 files changed, 335 insertions(+), 162 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/msn/msn-utils.c	Fri Apr 13 02:43:11 2007 +0000
+++ b/libpurple/protocols/msn/msn-utils.c	Fri Apr 13 04:13:24 2007 +0000
@@ -110,6 +110,17 @@
 		}
 	}
 
+	cur = strstr(mime, "RL=");
+
+	if (cur && (*(cur = cur + 3) != ';'))
+	{
+		if(*cur == '1') {
+			/* RTL text was received */
+			pre = g_string_append(pre, "<SPAN style=\"direction:rtl;text-align:right;\">");
+			post = g_string_prepend(post, "</SPAN>");
+		}
+	}
+
 	cur = g_strdup(purple_url_decode(pre->str));
 	g_string_free(pre, TRUE);
 
@@ -161,7 +172,7 @@
  * and converts it to msn formatting. It doesn't deal with the tag closing,
  * but gtkimhtml widgets give valid html.
  * It currently deals properly with <b>, <u>, <i>, <font face=...>,
- * <font color=...>.
+ * <font color=...>, <span dir=...>, <span style="direction: ...">.
  * It ignores <font back=...> and <font size=...>
  */
 void
@@ -173,6 +184,7 @@
 	char *fontface = NULL;
 	char fonteffect[4];
 	char fontcolor[7];
+	char direction = '0';
 
 	gboolean has_bold = FALSE;
 	gboolean has_italic = FALSE;
@@ -256,6 +268,50 @@
 				if (*c != '\0')
 					c += 4;
 			}
+			else if (!g_ascii_strncasecmp(c + 1, "span", 4))
+			{
+				/* Bi-directional text support using CSS properties in span tags */
+				c += 5;
+
+				while (*c != '\0' && *c != '>')
+				{
+					while (*c == ' ')
+						c++;
+					if (!g_ascii_strncasecmp(c, "dir=\"rtl\"", 9))
+					{
+						c += 9;
+						direction = '1';
+					}
+					else if (!g_ascii_strncasecmp(c, "style=\"", 7))
+					{
+						/* Parse inline CSS attributes */
+						char* attributes;
+						int attr_len = 0;
+						c += 7;
+						while (*(c + attr_len) != '\0' && *(c + attr_len) != '"')
+							attr_len++;
+						if(*(c + attr_len) == '"')
+						{
+							char *attr_dir;
+							attributes = g_strndup(c, attr_len);
+							attr_dir = purple_markup_get_css_property(attributes, "direction");
+							if(attr_dir && (!strncasecmp(attr_dir, "RTL", 3)))
+								direction = '1';
+							if(attr_dir)
+								g_free(attr_dir);
+							if(attributes)
+								g_free(attributes);
+						}
+
+					}
+					else
+					{
+						c++;
+					}
+				}
+				if (*c == '>')
+					c++;
+			}
 			else if (!g_ascii_strncasecmp(c + 1, "font", 4))
 			{
 				c += 5;
@@ -354,9 +410,9 @@
 	if (fontface == NULL)
 		fontface = g_strdup("MS Sans Serif");
 
-	*attributes = g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0",
+	*attributes = g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c",
 								  encode_spaces(fontface),
-								  fonteffect, fontcolor);
+								  fonteffect, fontcolor, direction);
 	*message = g_strdup(msg);
 
 	g_free(fontface);
--- a/libpurple/protocols/oscar/oscar.c	Fri Apr 13 02:43:11 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Fri Apr 13 04:13:24 2007 +0000
@@ -4298,11 +4298,11 @@
 		 */
 		if (aim_sn_is_sms(name)) {
 			/* Messaging an SMS (mobile) user */
-			tmp2 = purple_unescape_html(tmp1);
+			tmp2 = purple_markup_strip_html(tmp1);
 		} else if (aim_sn_is_icq(purple_account_get_username(account))) {
 			if (aim_sn_is_icq(name))
 				/* From ICQ to ICQ */
-				tmp2 = purple_unescape_html(tmp1);
+				tmp2 = purple_markup_strip_html(tmp1);
 			else
 				/* From ICQ to AIM */
 				tmp2 = g_strdup(tmp1);
--- a/libpurple/util.c	Fri Apr 13 02:43:11 2007 +0000
+++ b/libpurple/util.c	Fri Apr 13 04:13:24 2007 +0000
@@ -863,8 +863,8 @@
 
 /* Returns a NULL-terminated string after unescaping an entity
  * (eg. &amp;, &lt; &#38 etc.) starting at s. Returns NULL on failure.*/
-static const char *
-detect_entity(const char *text, int *length)
+const char *
+purple_markup_detect_entity(const char *text, int *length)
 {
 	const char *pln;
 	int len, pound;
@@ -909,6 +909,67 @@
 	return pln;
 }
 
+gchar*
+purple_markup_get_css_property(const gchar	*style,
+				const gchar	*opt)
+{
+	const gchar *css_str = style;
+	const gchar *css_value_start;
+	const gchar *css_value_end;
+	gchar *tmp;
+	gchar *ret;
+
+	if(!css_str)
+		return NULL;
+
+	/* find the CSS property */
+	while(1){
+		/* skip widespace characters */
+		while(*css_str && g_ascii_isspace(*css_str))
+			css_str++;
+		if(!g_ascii_isalpha(*css_str))
+			return NULL;
+		if(g_ascii_strncasecmp(css_str, opt, strlen(opt)))
+		{
+			/* go to next css property positioned after the next ';' */
+			while(*css_str && *css_str != '"' && *css_str != ';')
+				css_str++;
+			if(*css_str != ';')
+				return NULL;
+			css_str++;
+		}
+		else break;
+	}
+
+	/* find the CSS value position in the string */
+	css_str += strlen(opt);
+	while(*css_str && g_ascii_isspace(*css_str))
+		css_str++;
+	if(*css_str != ':')
+		return NULL;
+	css_str++;
+	while(*css_str && g_ascii_isspace(*css_str))
+		css_str++;
+	if(*css_str == '\0' || *css_str == '"' || *css_str == ';')
+		return NULL;
+
+	/* mark the CSS value */
+	css_value_start = css_str;
+	while(*css_str && *css_str != '"' && *css_str != ';')
+		css_str++;
+	css_value_end = css_str - 1;
+
+	/* Removes trailing whitespace */
+	while(css_value_end > css_value_start && g_ascii_isspace(*css_value_end))
+		css_value_end--;
+
+	tmp = g_strndup(css_value_start, css_value_end - css_value_start + 1);
+	ret = purple_unescape_html(tmp);
+	g_free(tmp);
+
+	return ret;
+}
+
 gboolean
 purple_markup_find_tag(const char *needle, const char *haystack,
 					 const char **start, const char **end, GData **attributes)
@@ -1509,7 +1570,7 @@
 			const char *pln;
 			int len;
 
-			if ((pln = detect_entity(c, &len)) == NULL) {
+			if ((pln = purple_markup_detect_entity(c, &len)) == NULL) {
 				len = 1;
 				g_snprintf(buf, sizeof(buf), "%c", *c);
 				pln = buf;
@@ -1714,7 +1775,7 @@
 			visible = TRUE;
 		}
 
-		if (str2[i] == '&' && (ent = detect_entity(str2 + i, &entlen)) != NULL)
+		if (str2[i] == '&' && (ent = purple_markup_detect_entity(str2 + i, &entlen)) != NULL)
 		{
 			while (*ent)
 				str2[j++] = *ent++;
@@ -2032,7 +2093,7 @@
 			int len;
 			const char *ent;
 
-			if ((ent = detect_entity(c, &len)) != NULL) {
+			if ((ent = purple_markup_detect_entity(c, &len)) != NULL) {
 				ret = g_string_append(ret, ent);
 				c += len;
 			} else if (!strncmp(c, "<br>", 4)) {
--- a/libpurple/util.h	Fri Apr 13 02:43:11 2007 +0000
+++ b/libpurple/util.h	Fri Apr 13 04:13:24 2007 +0000
@@ -462,6 +462,36 @@
  */
 char *purple_markup_get_tag_name(const char *tag);
 
+/**
+ * Returns a constant string of the character representation of the HTML
+ * entity pointed by *text. For example *text="&amp;" will return "&" and so on.
+ * The *text variable is expected to point to a '&' - the first character 
+ * of the entity. On unrecognized entity the function will return NULL.
+ * Note that this function, unlike purple_unescape_html() does not seek 
+ * the string for the entity, does not replace the entity and does not
+ * return a newly allocated string.
+ *
+ * @param text A string containing HTML entity.
+ * @param length The string length of the deteced entity is stored in this variable.
+ * @return A constant string containing the character representation of the given entity.
+ */
+const char * purple_markup_detect_entity(const char *text, int *length);
+
+/**
+ * Returns a newly allocated string containing the value of the CSS property specified
+ * in opt. The *style argument is expected to point to a HTML inline CSS style.
+ * ( ie <span style="[inline css]"> ) The function will seek for the CSS propery and
+ * return it's value. Example: for style="direction:rtl;color:#dc4d1b;" opt="color"
+ * the function's return value would be #dc4d1b. On error or if the requested property
+ * was not found, the function returns NULL.
+ *
+ * @param style A string containing the inline CSS text.
+ * @param opt The requested CSS property.
+ * @return the value of the requested CSS property.
+ */
+gchar* purple_markup_get_css_property(const gchar *style, const gchar *opt);
+
+
 /*@}*/
 
 
--- a/pidgin/gtkconv.c	Fri Apr 13 02:43:11 2007 +0000
+++ b/pidgin/gtkconv.c	Fri Apr 13 04:13:24 2007 +0000
@@ -4895,6 +4895,73 @@
 	gtkconv->newday = mktime(tm);
 }
 
+/* Detect string direction and encapsulate the string in a RLE/LRE/PDF unicode characters 
+   str - pointer to string (string is realocated and new pointer is returned) */
+static void
+str_embed_direction_chars(char **str)
+{
+	char pre_str[4];
+	char post_str[10];
+	char* ret = g_malloc(strlen(*str)+13);
+
+	if (PANGO_DIRECTION_RTL == pango_find_base_dir(*str, strlen(*str)))
+	{
+		g_sprintf(pre_str, "%c%c%c", 
+				0xE2, 0x80, 0xAB);	/* RLE */
+		g_sprintf(post_str, "%c%c%c%c%c%c%c%c%c", 
+				0xE2, 0x80, 0xAC,	/* PDF */
+				0xE2, 0x80, 0x8E,	/* LRM */
+				0xE2, 0x80, 0xAC);	/* PDF */
+	}
+	else
+	{
+		g_sprintf(pre_str, "%c%c%c", 
+				0xE2, 0x80, 0xAA);	/* LRE */
+		g_sprintf(post_str, "%c%c%c%c%c%c%c%c%c", 
+				0xE2, 0x80, 0xAC,	/* PDF */
+				0xE2, 0x80, 0x8F,	/* RLM */
+				0xE2, 0x80, 0xAC);	/* PDF */
+	}
+
+	g_sprintf(ret, "%s%s%s", pre_str, *str, post_str);
+
+	g_free(*str);
+	*str = ret;
+	return;
+}
+
+/* Returns true if the given HTML contains RTL text */
+static gboolean 
+html_is_rtl(const char *html)
+{
+	GData *attributes;
+	const gchar *start, *end;
+	gboolean res = FALSE;
+
+	if(purple_markup_find_tag("span", html, &start, &end, &attributes)) 
+	{
+		/* tmp is a member of attributes and is free with g_datalist_clear call */
+		const char *tmp = g_datalist_get_data(&attributes, "dir");
+		if(tmp && !g_ascii_strcasecmp(tmp, "RTL"))
+			res = TRUE;
+		if(!res)
+		{
+			char *tmp2 = NULL;
+			tmp = g_datalist_get_data(&attributes, "style");
+			if(tmp)
+				tmp2 = purple_markup_get_css_property(tmp, "direction");
+			if(tmp2 && !g_ascii_strcasecmp(tmp2, "RTL"))
+				res = TRUE;
+			if(tmp2)
+				g_free(tmp2);
+
+		}
+		g_datalist_clear(&attributes);
+	}
+	return res;
+}
+
+
 static void
 pidgin_conv_write_conv(PurpleConversation *conv, const char *name, const char *alias,
 						const char *message, PurpleMessageFlags flags,
@@ -4922,6 +4989,7 @@
 	gboolean plugin_return;
 	char *bracket;
 	int tag_count = 0;
+	gboolean is_rtl_message = FALSE;
 
 	g_return_if_fail(conv != NULL);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -5034,11 +5102,19 @@
 	if (mdate == NULL)
 	{
 		struct tm *tm = localtime(&mtime);
+		const char *tmp;
 		if (show_date)
-			mdate = g_strdup(purple_date_format_long(tm));
+			tmp = purple_date_format_long(tm);
 		else
-			mdate = g_strdup(purple_time_format(tm));
-	}
+			tmp = purple_time_format(tm);
+		mdate = g_strdup_printf("(%s)", tmp);
+	}
+
+	/* Bi-Directional support - set timestamp direction using unicode characters */
+	is_rtl_message = html_is_rtl(message);
+	/* Enforce direction only if message is RTL - donesn't effect LTR users */
+	if(is_rtl_message)
+		str_embed_direction_chars(&mdate);
 
 	if (mtime >= gtkconv->newday)
 		pidgin_conv_calculate_newday(gtkconv, mtime);
@@ -5063,14 +5139,14 @@
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), message, gtk_font_options_all);
 	} else if (flags & PURPLE_MESSAGE_SYSTEM) {
 		g_snprintf(buf2, sizeof(buf2),
-			   "<FONT %s><FONT SIZE=\"2\"><!--(%s) --></FONT><B>%s</B></FONT>",
+			   "<FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT>",
 			   sml_attrib ? sml_attrib : "", mdate, displaying);
 
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all);
 
 	} else if (flags & PURPLE_MESSAGE_ERROR) {
 		g_snprintf(buf2, sizeof(buf2),
-			   "<FONT COLOR=\"#ff0000\"><FONT %s><FONT SIZE=\"2\"><!--(%s) --></FONT><B>%s</B></FONT></FONT>",
+			   "<FONT COLOR=\"#ff0000\"><FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT></FONT>",
 			   sml_attrib ? sml_attrib : "", mdate, displaying);
 
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all);
@@ -5091,6 +5167,10 @@
 		GtkSmileyTree *tree = NULL;
 		GHashTable *smiley_data = NULL;
 
+		/* Enforce direction on alias */
+		if(is_rtl_message)
+			str_embed_direction_chars(&alias_escaped);
+
 		if (flags & PURPLE_MESSAGE_SEND)
 		{
 			/* Temporarily revert to the original smiley-data to avoid showing up
@@ -5178,12 +5258,12 @@
 			    flags & PURPLE_MESSAGE_NICK ||
 			    purple_find_buddy(account, name) != NULL) {
 				g_snprintf(buf2, BUF_LONG,
-					   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--(%s) --></FONT>"
+					   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>"
 					   "<B>%s</B></FONT> ",
 					   color, sml_attrib ? sml_attrib : "", mdate, str);
 			} else {
 				g_snprintf(buf2, BUF_LONG,
-					   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--(%s) --></FONT>"
+					   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>"
 					   "%s</FONT> ",
 					   color, sml_attrib ? sml_attrib : "", mdate, str);
 
@@ -5191,7 +5271,7 @@
 		} else {
 			/* Bold everyone's name to make the name stand out from the message. */
 			g_snprintf(buf2, BUF_LONG,
-				   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--(%s) --></FONT>"
+				   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>"
 				   "<B>%s</B></FONT> ",
 				   color, sml_attrib ? sml_attrib : "", mdate, str);
 		}
--- a/pidgin/gtkimhtml.c	Fri Apr 13 02:43:11 2007 +0000
+++ b/pidgin/gtkimhtml.c	Fri Apr 13 04:13:24 2007 +0000
@@ -100,7 +100,6 @@
 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextIter *end, GtkIMHtml *imhtml);
 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data);
 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end);
-static gboolean gtk_imhtml_is_amp_escape (const gchar *string, gchar **replace, gint *length);
 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter);
 static void gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data);
 static void gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml);
@@ -1736,7 +1735,7 @@
 	GtkSmileyTree *t = tree;
 	const gchar *x = text;
 	gint len = 0;
-	gchar *amp;
+	const gchar *amp;
 	gint alen;
 
 	while (*x) {
@@ -1745,7 +1744,7 @@
 		if (!t->values)
 			break;
 
-		if(*x == '&' && gtk_imhtml_is_amp_escape(x, &amp, &alen)) {
+		if(*x == '&' && (amp = purple_markup_detect_entity(x, &alen))) {
 			gboolean matched = TRUE;
 			/* Make sure all chars of the unescaped value match */
 			while (*(amp + 1)) {
@@ -1928,62 +1927,6 @@
 
 
 static gboolean
-gtk_imhtml_is_amp_escape (const gchar *string,
-			  gchar       **replace,
-			  gint        *length)
-{
-	static char buf[7];
-	g_return_val_if_fail (string != NULL, FALSE);
-	g_return_val_if_fail (replace != NULL, FALSE);
-	g_return_val_if_fail (length != NULL, FALSE);
-
-	if (!g_ascii_strncasecmp (string, "&amp;", 5)) {
-		*replace = "&";
-		*length = 5;
-	} else if (!g_ascii_strncasecmp (string, "&lt;", 4)) {
-		*replace = "<";
-		*length = 4;
-	} else if (!g_ascii_strncasecmp (string, "&gt;", 4)) {
-		*replace = ">";
-		*length = 4;
-	} else if (!g_ascii_strncasecmp (string, "&nbsp;", 6)) {
-		*replace = " ";
-		*length = 6;
-	} else if (!g_ascii_strncasecmp (string, "&copy;", 6)) {
-		*replace = "©";
-		*length = 6;
-	} else if (!g_ascii_strncasecmp (string, "&quot;", 6)) {
-		*replace = "\"";
-		*length = 6;
-	} else if (!g_ascii_strncasecmp (string, "&reg;", 5)) {
-		*replace = "®";
-		*length = 5;
-	} else if (!g_ascii_strncasecmp (string, "&apos;", 6)) {
-		*replace = "\'";
-		*length = 6;
-	} else if (*(string + 1) == '#') {
-		guint pound = 0;
-		if ((sscanf (string, "&#%u;", &pound) == 1) && pound != 0) {
-			int buflen;
-			if (*(string + 3 + (gint)log10 (pound)) != ';')
-				return FALSE;
-			buflen = g_unichar_to_utf8((gunichar)pound, buf);
-			buf[buflen] = '\0';
-			*replace = buf;
-			*length = 2;
-			while (isdigit ((gint) string [*length])) (*length)++;
-			if (string [*length] == ';') (*length)++;
-		} else {
-			return FALSE;
-		}
-	} else {
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
 gtk_imhtml_is_tag (const gchar *string,
 		   gchar      **tag,
 		   gint        *len,
@@ -2084,7 +2027,7 @@
 	gchar *e, *a;
 	gchar *val;
 	gint len;
-	gchar *c;
+	const gchar *c;
 	GString *ret;
 
 	while (g_ascii_strncasecmp (t, opt, strlen (opt))) {
@@ -2120,7 +2063,7 @@
 	ret = g_string_new("");
 	e = val;
 	while(*e) {
-		if(gtk_imhtml_is_amp_escape(e, &c, &len)) {
+		if((c = purple_markup_detect_entity(e, &len))) {
 			ret = g_string_append(ret, c);
 			e += len;
 		} else {
@@ -2134,71 +2077,6 @@
 	return g_string_free(ret, FALSE);
 }
 
-/* Inline CSS Support - Douglas Thrift */
-static gchar*
-gtk_imhtml_get_css_opt (gchar       *style,
-			 const gchar *opt)
-{
-	gchar *t = style;
-	gchar *e, *a;
-	gchar *val;
-	gint len;
-	gchar *c;
-	GString *ret;
-
-	while (g_ascii_strncasecmp (t, opt, strlen (opt))) {
-/*		gboolean quote = FALSE; */
-		if (*t == '\0') break;
-		while (*t && !((*t == ' ') /*&& !quote*/)) {
-/*			if (*t == '\"')
-				quote = ! quote; */
-			t++;
-		}
-		while (*t && (*t == ' ')) t++;
-	}
-
-	if (!g_ascii_strncasecmp (t, opt, strlen (opt))) {
-		t += strlen (opt);
-		while (*t && (*t == ' ')) t++;
-		if (!*t)
-			return NULL;
-	} else {
-		return NULL;
-	}
-
-/*	if ((*t == '\"') || (*t == '\'')) {
-		e = a = ++t;
-		while (*e && (*e != *(t - 1))) e++;
-		if  (*e == '\0') {
-			return NULL;
-		} else
-			val = g_strndup(a, e - a);
-	} else {
-		e = a = t;
-		while (*e && !isspace ((gint) *e)) e++;
-		val = g_strndup(a, e - a);
-	}*/
-
-	e = a = t;
-	while (*e && *e != ';') e++;
-	val = g_strndup(a, e - a);
-
-	ret = g_string_new("");
-	e = val;
-	while(*e) {
-		if(gtk_imhtml_is_amp_escape(e, &c, &len)) {
-			ret = g_string_append(ret, c);
-			e += len;
-		} else {
-			ret = g_string_append_c(ret, *e);
-			e++;
-		}
-	}
-	g_free(val);
-
-	return g_string_free(ret, FALSE);
-}
-
 static const char *accepted_protocols[] = {
 	"http://",
 	"https://",
@@ -2369,7 +2247,7 @@
 	gint tlen, smilelen, wpos=0;
 	gint type;
 	const gchar *c;
-	gchar *amp;
+	const gchar *amp;
 	gint len_protocol;
 
 	guint	bold = 0,
@@ -2382,6 +2260,9 @@
 		pre = 0;
 
 	gboolean br = FALSE;
+	gboolean align_right = FALSE;
+	gboolean rtl_direction = FALSE;
+	gint align_line = 0;
 
 	GSList *fonts = NULL;
 	GObject *object;
@@ -2748,28 +2629,32 @@
 					 * font-size
 					 * text-decoration: underline
 					 * font-weight: bold
+					 * direction: rtl
+					 * text-align: right
 					 *
 					 * TODO:
 					 * background-color
 					 * font-style
 					 */
 					{
-						gchar *style, *color, *background, *family, *size;
+						gchar *style, *color, *background, *family, *size, *direction, *alignment;
 						gchar *textdec, *weight;
 						GtkIMHtmlFontDetail *font, *oldfont = NULL;
 						style = gtk_imhtml_get_html_opt (tag, "style=");
 
 						if (!style) break;
 
-						color = gtk_imhtml_get_css_opt (style, "color:");
-						background = gtk_imhtml_get_css_opt (style, "background:");
-						family = gtk_imhtml_get_css_opt (style,
-							"font-family:");
-						size = gtk_imhtml_get_css_opt (style, "font-size:");
-						textdec = gtk_imhtml_get_css_opt (style, "text-decoration:");
-						weight = gtk_imhtml_get_css_opt (style, "font-weight:");
-
-						if (!(color || family || size || background || textdec || weight)) {
+						color = purple_markup_get_css_property (style, "color");
+						background = purple_markup_get_css_property (style, "background");
+						family = purple_markup_get_css_property (style, "font-family");
+						size = purple_markup_get_css_property (style, "font-size");
+						textdec = purple_markup_get_css_property (style, "text-decoration");
+						weight = purple_markup_get_css_property (style, "font-weight");
+						direction = purple_markup_get_css_property (style, "direction");
+						alignment = purple_markup_get_css_property (style, "text-align");
+
+
+						if (!(color || family || size || background || textdec || weight || direction || alignment)) {
 							g_free(style);
 							break;
 						}
@@ -2779,6 +2664,27 @@
 						ws[0] = '\0'; wpos = 0;
 						/* NEW_BIT (NEW_TEXT_BIT); */
 
+						/* Bi-Directional text support */
+						if(direction && (!strncasecmp(direction, "RTL", 3))) {
+							rtl_direction = TRUE;
+							/* insert RLE character to set direction */
+							ws[wpos++]  = 0xE2;
+							ws[wpos++]  = 0x80;
+							ws[wpos++]  = 0xAB;
+							ws[wpos]  = '\0';
+							gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
+							ws[0] = '\0'; wpos = 0;
+						}
+						if(direction)
+							g_free(direction);
+
+						if(alignment && (!strncasecmp(alignment, "RIGHT", 5))) {
+							align_right = TRUE;
+							align_line = gtk_text_iter_get_line(iter);
+						}
+						if(alignment)
+							g_free(alignment);
+
 						font = g_new0 (GtkIMHtmlFontDetail, 1);
 						if (fonts)
 							oldfont = fonts->data;
@@ -2987,7 +2893,7 @@
 			pos += smilelen;
 			wpos = 0;
 			ws[0] = 0;
-		} else if (*c == '&' && gtk_imhtml_is_amp_escape (c, &amp, &tlen)) {
+		} else if (*c == '&' && (amp = purple_markup_detect_entity(c, &tlen))) {
 			while(*amp) {
 				ws [wpos++] = *amp++;
 			}
@@ -3035,6 +2941,32 @@
 
 	/* NEW_BIT(NEW_TEXT_BIT); */
 
+	if(align_right) {
+		/* insert RLM+LRM at beginning of the line to set alignment */
+		GtkTextIter line_iter;
+		line_iter = *iter;
+		gtk_text_iter_set_line(&line_iter, align_line);
+		/* insert RLM character to set alignment */
+		ws[wpos++]  = 0xE2;
+		ws[wpos++]  = 0x80;
+		ws[wpos++]  = 0x8F;
+    
+		if(!rtl_direction)
+		{
+			/* insert LRM character to set direction */
+			/* (alignment=right and direction=LTR) */
+			ws[wpos++]  = 0xE2;
+			ws[wpos++]  = 0x80;
+			ws[wpos++]  = 0x8E;
+		}
+
+		ws[wpos]  = '\0';
+		gtk_text_buffer_insert(imhtml->text_buffer, &line_iter, ws, wpos);
+		gtk_text_buffer_get_end_iter(gtk_text_iter_get_buffer(&line_iter), iter);
+		ws[0] = '\0';
+		wpos = 0;
+	}
+
 	while (fonts) {
 		GtkIMHtmlFontDetail *font = fonts->data;
 		fonts = g_slist_remove (fonts, font);
@@ -4626,7 +4558,8 @@
 char *gtk_imhtml_get_markup_range(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
 {
 	gunichar c;
-	GtkTextIter iter, nextiter;
+	GtkTextIter iter, next_iter, non_neutral_iter;
+	gboolean is_rtl_message = FALSE;
 	GString *str = g_string_new("");
 	GSList *tags, *sl;
 	GQueue *q, *r;
@@ -4637,8 +4570,17 @@
 
 
 	gtk_text_iter_order(start, end);
-	nextiter = iter = *start;
-	gtk_text_iter_forward_char(&nextiter);
+	non_neutral_iter = next_iter = iter = *start;
+	gtk_text_iter_forward_char(&next_iter);
+
+	/* Bi-directional text support */
+	/* Get to the first non-neutral character */
+	while((PANGO_DIRECTION_NEUTRAL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter)))
+		&& gtk_text_iter_forward_char(&non_neutral_iter));
+	if(PANGO_DIRECTION_RTL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter))) {
+		is_rtl_message = TRUE;
+		g_string_append(str, "<SPAN style=\"direction:rtl;text-align:right;\">");
+	}
 
 	/* First add the tags that are already in progress (we don't care about non-printing tags)*/
 	tags = gtk_text_iter_get_tags(start);
@@ -4692,7 +4634,7 @@
 		for (sl = tags; sl; sl = sl->next) {
 			tag = sl->data;
 			/** don't worry about non-printing tags ending */
-			if (tag_ends_here(tag, &iter, &nextiter) && strlen(tag_to_html_end(tag)) > 0) {
+			if (tag_ends_here(tag, &iter, &next_iter) && strlen(tag_to_html_end(tag)) > 0) {
 
 				GtkTextTag *tmp;
 
@@ -4700,7 +4642,7 @@
 					if (tmp == NULL)
 						break;
 
-					if (!tag_ends_here(tmp, &iter, &nextiter) && strlen(tag_to_html_end(tmp)) > 0)
+					if (!tag_ends_here(tmp, &iter, &next_iter) && strlen(tag_to_html_end(tmp)) > 0)
 						g_queue_push_tail(r, tmp);
 					g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tmp)));
 				}
@@ -4719,12 +4661,16 @@
 
 		g_slist_free(tags);
 		gtk_text_iter_forward_char(&iter);
-		gtk_text_iter_forward_char(&nextiter);
+		gtk_text_iter_forward_char(&next_iter);
 	}
 
 	while ((tag = g_queue_pop_tail(q)))
 		g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tag)));
 
+	/* Bi-directional text support - close tags */
+	if(is_rtl_message)
+		g_string_append(str, "</SPAN>");
+
 	g_queue_free(q);
 	g_queue_free(r);
 	return g_string_free(str, FALSE);