diff --git a/widgets/rb-header.c b/widgets/rb-header.c
index 9525169..67c80ca 100644
--- a/widgets/rb-header.c
+++ b/widgets/rb-header.c
@@ -31,6 +31,7 @@
 
 #include <math.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
@@ -119,10 +120,10 @@ enum
 	PROP_SLIDER_DRAGGING
 };
 
-#define TITLE_MARKUP(xTITLE) g_markup_printf_escaped ("<big><b>%s</b></big>", xTITLE)
-#define ALBUM_MARKUP(xALBUM) g_markup_printf_escaped (" %s <i>%s</i>", _("from"), xALBUM)
-#define ARTIST_MARKUP(xARTIST) g_markup_printf_escaped (" %s <i>%s</i>", _("by"), xARTIST)
-#define STREAM_MARKUP(xSTREAM) g_markup_printf_escaped (" (%s)", xSTREAM)
+#define TITLE_FORMAT  "<big><b>%s</b></big>"
+#define ALBUM_FORMAT  "<i>%s</i>"
+#define ARTIST_FORMAT "<i>%s</i>"
+#define STREAM_FORMAT "(%s)"
 
 #define SCROLL_UP_SEEK_OFFSET	5
 #define SCROLL_DOWN_SEEK_OFFSET -5
@@ -418,6 +419,177 @@ get_extra_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *field, char
 	}
 }
 
+/* unicode direction markup characters: http://unicode.org/reports/tr9/ */
+static const char const *UNICODE_LRM = "\xE2\x80\x8E";
+static const char const *UNICODE_RLM = "\xE2\x80\x8F";
+static const char const *UNICODE_LRE = "\xE2\x80\xAA";
+static const char const *UNICODE_RLE = "\xE2\x80\xAB";
+static const char const *UNICODE_PDF = "\xE2\x80\xAC";
+
+/* unicode graphic characters */
+static const char const *UNICODE_MIDDLE_DOT = "\xC2\xB7";
+
+static gboolean
+direction_conflict (PangoDirection dir1, PangoDirection dir2)
+{
+	return (dir1 != dir2) &&
+	       (dir1 != PANGO_DIRECTION_NEUTRAL) &&
+	       (dir2 != PANGO_DIRECTION_NEUTRAL);
+}
+
+/* this function concatenates strings to a single string, preserving
+ * each part's original direction (LTR or RTL) using unicode markup.
+ * it inserts a space between strings.
+ * the algorithm: (http://unicode.org/reports/tr9/ for definitions)
+ * 1. caller supplies the base direction of the result in base_dir.
+ * 2. insert either LRM or RLM at the beginning of the string to set
+ *    its base direction, according to base_dir.
+ * 3. find the direction of each string (using pango).
+ * 4. for strings that have the same direction as the base direction,
+ *    just insert them in.
+ * 5. for strings that have the opposite direction than the base one,
+ *    insert them surrounded with embedding codes RLE/LRE .. PDF.
+ * the function is called like this:
+ * s = bidi_cat(base_dir, N, str1, format1, ..., strN, formatN)
+ * any string that is NULL will be skipped (must still pass format).
+ * format is a printf format with exactly one %s. "%s" or NULL will
+ * insert the string as is without further formatting.
+ */
+static GString *
+bidi_cat (PangoDirection base_dir, int num_parts, ...)
+{
+	PangoDirection text_dir;
+	va_list args;
+	int i;
+	const char *embed_start;
+	const char *embed_stop = UNICODE_PDF;
+	gboolean first = TRUE;
+	GString *result;
+
+	va_start (args, num_parts);
+
+	result = g_string_sized_new (100);
+	if (!result) return NULL;
+
+	if (base_dir == PANGO_DIRECTION_LTR) {
+	    	/* base direction LTR, embedded parts are RTL */
+		g_string_append (result, UNICODE_LRM);
+		embed_start = UNICODE_RLE;
+	} else {
+	    	/* base direction RTL, embedded parts are LTR */
+		g_string_append (result, UNICODE_RLM);
+		embed_start = UNICODE_LRE;
+	}
+
+	for (i = 0; i < num_parts; ++i) {
+		const char *text = va_arg (args, const char *);
+		const char *format = va_arg (args, const char *);
+		if (!text) continue;
+		if (!format) format = "%s";
+
+		if (!first) {
+			g_string_append (result, " ");
+		} else {
+			first = FALSE;
+		}
+
+		text_dir = pango_find_base_dir (text, -1);
+
+		if (direction_conflict (text_dir, base_dir)) {
+			/* surround text with embed codes */
+			g_string_append (result, embed_start);
+			append_and_free (result, g_markup_printf_escaped (format, text));
+			g_string_append (result, embed_stop);
+		} else {
+			append_and_free (result, g_markup_printf_escaped (format, text));
+		}
+	}
+
+	va_end (args);
+
+	return result;
+}
+
+/* this functions checks the direction of all given strings and:
+ * 1. if all strings are direction neutral, returns NEUTRAL;
+ * 2. if all strings are either LTR or neutral, returns LTR;
+ * 3. if all strings are either RTL or neutral, returns RTL;
+ * 4. if at least one is RTL and one LTR, returns NEUTRAL.
+ * note: neutral (1) and mixed (4) are two very different situations,
+ * they share a return code here only because they're the same for our
+ * specific use.
+ */
+static PangoDirection
+common_direction (int num_parts, ...)
+{
+	int i;
+	PangoDirection common_dir = PANGO_DIRECTION_NEUTRAL;
+	PangoDirection text_dir;
+	va_list args;
+
+	va_start (args, num_parts);
+
+	for (i = 0; i < num_parts; ++i) {
+		const char *text = va_arg (args, const char *);
+		if (!text) continue;
+
+		text_dir = pango_find_base_dir (text, -1);
+
+		if (direction_conflict (text_dir, common_dir)) {
+			/* mixed direction */
+			va_end (args);
+			return PANGO_DIRECTION_NEUTRAL;
+		}
+
+		common_dir = text_dir;
+	}
+
+	va_end (args);
+
+	return common_dir;
+}
+
+static GString *
+write_header (PangoDirection native_dir,
+	      const char *title,
+	      const char *artist,
+	      const char *album,
+	      const char *stream)
+{
+	const char *by;
+	const char *from;
+	PangoDirection tags_dir;
+	PangoDirection header_dir;
+
+	if (title  && title [0] == '\0') title  = NULL;
+	if (artist && artist[0] == '\0') artist = NULL;
+	if (album  && album [0] == '\0') album  = NULL;
+	if (stream && stream[0] == '\0') stream = NULL;
+
+	tags_dir = common_direction (4, title, artist, album, stream);
+
+	/* TODO: proposed behaviour: if the tags have a defined direction that
+	 * conflicts with the native direction, show them in their natural
+	 * direction with a neutral separator */
+	if (!direction_conflict (tags_dir, native_dir)) {
+		header_dir = native_dir;
+		by = _("by");
+		from = _("from");
+	} else {
+		header_dir = tags_dir;
+		by = UNICODE_MIDDLE_DOT;
+		from = UNICODE_MIDDLE_DOT;
+	}
+
+	return bidi_cat (header_dir, 6,
+			 title           ? title  : NULL, TITLE_FORMAT,
+			 (artist && by)  ? by     : NULL, "%s",
+			 artist          ? artist : NULL, ARTIST_FORMAT,
+			 (album && from) ? from   : NULL, "%s",
+			 album           ? album  : NULL, ALBUM_FORMAT,
+			 stream          ? stream : NULL, STREAM_FORMAT);
+}
+
 /**
  * rb_header_sync:
  * @header: the #RBHeader
@@ -445,6 +617,7 @@ rb_header_sync (RBHeader *header)
 		char *streaming_artist;
 		char *streaming_album;
 		GString *label_str;
+		PangoDirection widget_dir;
 
 		gboolean have_duration = (header->priv->duration > 0);
 
@@ -480,30 +653,10 @@ rb_header_sync (RBHeader *header)
 			album = streaming_album;
 		}
 
-		label_str = g_string_sized_new (100);
-
-		/* force the string to have the same direction as the widget.
-		 * this fixes the display when the head of the text (usually
-		 * the track name) has a different direction than the widget.
-		 */
-		if (gtk_widget_get_direction (GTK_WIDGET (header->priv->song)) == GTK_TEXT_DIR_RTL) {
-			/* insert a unicode RLM */
-			g_string_append (label_str, "\xE2\x80\x8F");
-		} else {
-			/* insert a unicode LRM */
-			g_string_append (label_str, "\xE2\x80\x8E");
-		}
-
-		append_and_free (label_str, TITLE_MARKUP (title));
-
-		if (artist != NULL && artist[0] != '\0')
-			append_and_free (label_str, ARTIST_MARKUP (artist));
-
-		if (album != NULL && album[0] != '\0')
-			append_and_free (label_str, ALBUM_MARKUP (album));
+		widget_dir = (gtk_widget_get_direction (GTK_WIDGET (header->priv->song)) == GTK_TEXT_DIR_LTR) ?
+			     PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
 
-		if (stream_name && stream_name[0] != '\0')
-			append_and_free (label_str, STREAM_MARKUP (stream_name));
+		label_str = write_header (widget_dir, title, artist, album, stream_name);
 
 		label_text = g_string_free (label_str, FALSE);
 		gtk_label_set_markup (GTK_LABEL (header->priv->song), label_text);
@@ -518,7 +671,7 @@ rb_header_sync (RBHeader *header)
 		g_free (streaming_title);
 	} else {
 		rb_debug ("not playing");
-		label_text = TITLE_MARKUP (_("Not Playing"));
+		label_text = g_markup_printf_escaped (TITLE_FORMAT, _("Not Playing"));
 		gtk_label_set_markup (GTK_LABEL (header->priv->song), label_text);
 		g_free (label_text);
 
