changeset 17946:b3e971b375b5

Support font sizes in sending and receiving formatted text. Add conversion routines for msim markup <f h=##> to libpurple <font size=##>: pixel height <=> point sizes <=> libpurple relative HTML sizes Text sent between msimprpl and the official MySpaceIM client of varying sizes matches up fairly well with this change.
author Jeffrey Connelly <jaconnel@calpoly.edu>
date Fri, 13 Jul 2007 05:48:13 +0000
parents cf55077a3752
children ac7066ffa109
files libpurple/protocols/myspace/myspace.c libpurple/protocols/myspace/myspace.h
diffstat 2 files changed, 131 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/myspace/myspace.c	Thu Jul 12 04:15:45 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Fri Jul 13 05:48:13 2007 +0000
@@ -630,31 +630,80 @@
 	return rc;
 }
 
-
-#ifdef MSIM_FONT_SIZE_WORKS
-/** Convert a msim markup font height to points. */
-static guint 
-msim_font_height_to_point(guint height)
+/* Indexes of this array + 1 map HTML font size to scale of normal font size. *
+ * Based on _point_sizes from libpurple/gtkimhtml.c 
+ *                                 1    2  3    4     5      6       7 */
+static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 };
+
+#define MAX_FONT_SIZE                   7       /* Purple maximum font size */
+#define POINTS_PER_INCH                 72      /* How many pt's in an inch */
+
+/* Baseline size of purple's fonts, in points. What is size 3 in points. 
+ * _font_scale specifies scaling factor relative to this point size.
+ * TODO: configurable */
+#define BASE_FONT_POINT_SIZE            8
+
+/* Display's DPI. 96 is common but it can differ. TODO: configurable */
+#define DOTS_PER_INCH                   96
+
+/** Convert typographical font point size to HTML font size. 
+ * Based on libpurple/gtkimhtml.c */
+guint
+msim_point_to_purple_size(guint point)
 {
+    guint size, this_point;
+    gdouble scale;
+   
+    for (size = 0; size < sizeof(_font_scale) / sizeof(_font_scale[0]); ++size)
+    {
+        scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
+        this_point = (guint)round(scale * BASE_FONT_POINT_SIZE);
+
+        if (this_point >= point)
+        {
+            purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n",
+                    point, size);
+            return size;
+        }
+    }
+
+    /* No HTML font size was this big; return largest possible. */
+    return this_point;
+}
+
+/** Convert HTML font size to point size. */
+guint
+msim_purple_size_to_point(guint size)
+{
+    gdouble scale;
+    guint point;
+
+    scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
+
+    point = (guint)round(scale * BASE_FONT_POINT_SIZE);
+
+    purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n",
+                    size, point);
+
+    return point;
+}
+
+/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
+guint 
+msim_height_to_point(guint height)
+{
+    return (guint)round((POINTS_PER_INCH * 1. / DOTS_PER_INCH) * height);
+
 	/* See also: libpurple/protocols/bonjour/jabber.c
 	 * _font_size_ichat_to_purple */
-	switch (height)
-	{
-	case 11: return 8;
-	case 12: return 9;
-	case 13: return 10;
-	case 14: 
-	case 15: return 11;
-	case 16: return 12;
-	case 17:
-	case 18: return 13;
-	case 19: return 14;
-	case 20: return 15;
-	case 21: return 16;	 
-	default: return 12;
-	}
 }
-#endif
+
+/** Convert point size to msim pixel height font size specification, for outgoing messages. */
+guint
+msim_point_to_height(guint point)
+{
+    return (guint)round((DOTS_PER_INCH * 1. / POINTS_PER_INCH) * point);
+}
 
 /** Convert the msim markup <f> (font) tag into HTML. */
 static void 
@@ -679,27 +728,19 @@
 		decor = 0;
 
 	gs_begin = g_string_new("");
-#ifdef MSIM_FONT_SIZE_WORKS
 	/* TODO: get font size working */
-	if (!face)
-		g_string_printf(gs_begin, "<font size='%d'>",
-				msim_font_height_to_point(height)); 
-	else
-		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face, 
-				msim_font_height_to_point(height)); 
-#else
-	if (face)
-	{
-		g_string_printf(gs_begin, "<font face='%s'>", face);
-	} else {
-		g_string_printf(gs_begin, "<font>");
-	}
-#endif
-
+	if (height && !face)
+		g_string_printf(gs_begin, "<font size='%d'>", 
+                msim_point_to_purple_size(msim_height_to_point(height)));
+    else if (height && face)
+		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face,  
+                msim_point_to_purple_size(msim_height_to_point(height)));
+    else
+        g_string_printf(gs_begin, "<font>");
 
 	/* No support for font-size CSS? */
 	/* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, 
-			msim_font_height_to_point(height)); */
+			msim_height_to_point(height)); */
 
 	gs_end = g_string_new("</font>");
 
@@ -871,8 +912,11 @@
      * Currently, the 's' value will be overwritten when b/i/u is nested
      * within another one, and only the inner-most formatting will be 
      * applied to the text. */
-    if (!strcmp(root->name, "b"))
+    if (!strcmp(root->name, "root"))
     {
+        *begin = g_strdup("");
+        *end = g_strdup("");
+    } else if (!strcmp(root->name, "b")) {
         *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD);
         *end = g_strdup("</f>");
     } else if (!strcmp(root->name, "i")) {
@@ -889,13 +933,19 @@
         face = xmlnode_get_attrib(root, "face");
 
         if (face && size)
-            *begin = g_strdup_printf("<f f='%s' h='%s'>", face, size);
-        else if (face)
+        {
+            *begin = g_strdup_printf("<f f='%s' h='%d'>", face, 
+                    msim_point_to_height(msim_purple_size_to_point(
+                            atoi(size))));
+        } else if (face) {
             *begin = g_strdup_printf("<f f='%s'>", face);
-        else if (size)
-            *begin = g_strdup_printf("<f h='%s'>", face);
-        else
+        } else if (size) {
+            *begin = g_strdup_printf("<f h='%d'>", 
+                     msim_point_to_height(msim_purple_size_to_point(
+                            atoi(size))));
+        } else {
             *begin = g_strdup("<f>");
+        }
 
         *end = g_strdup("</f>");
 
@@ -917,7 +967,7 @@
 {
 	xmlnode *node;
 	gchar *begin, *inner, *end;
-	gchar *final;
+    GString *final;
 
 	if (!root || !root->name)
 		return g_strdup("");
@@ -927,7 +977,11 @@
 
 	begin = inner = end = NULL;
 
+    final = g_string_new("");
+
     f(root, &begin, &end);
+    
+    g_string_append(final, begin);
 
 	/* Loop over all child nodes. */
  	for (node = root->child; node != NULL; node = node->next)
@@ -950,7 +1004,7 @@
 			/* Literal text. */
 			inner = g_new0(char, node->data_sz + 1);
 			strncpy(inner, node->data, node->data_sz);
-			inner[node->data_sz + 1] = 0;
+			inner[node->data_sz] = 0;
 
 			purple_debug_info("msim", " ** node data=%s\n", inner);
 			break;
@@ -960,17 +1014,21 @@
 					"msim_convert_xmlnode: strange node\n");
 			inner = g_strdup("");
 		}
-	}
-
-	final = g_strconcat(begin, inner, end, NULL);
-	g_free(begin);
-	g_free(inner);
-	g_free(end);
+
+        if (inner)
+            g_string_append(final, inner);
+    }
+
+    /* TODO: Note that msim counts each piece of text enclosed by <f> as
+     * a paragraph and will display each on its own line. You actually have
+     * to _nest_ <f> tags to intersperse different text in one paragraph!
+     * Comment out this line below to see. */
+    g_string_append(final, end);
 
 	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
-			final);
-
-	return final;
+			final->str);
+
+	return final->str;
 }
 
 /** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
@@ -985,6 +1043,7 @@
 	{
 		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
 				"%s as XML, returning raw\n", raw);
+        /* TODO: msim_unrecognized */
 		return g_strdup(raw);
 	}
 
@@ -1012,8 +1071,18 @@
 gchar *
 html_to_msim_markup(const gchar *raw)
 {
-    return msim_convert_xml(raw,
+    gchar *markup;
+    gchar *enclosed_raw;
+
+    /* Enclose text in one root tag, to try to make it valid XML for parsing. */
+    enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL);
+
+    markup = msim_convert_xml(enclosed_raw,
             (MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup));
+
+    g_free(enclosed_raw);
+
+    return markup;
 }
 
 /**
@@ -1643,7 +1712,7 @@
     g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
 
     delta = time(NULL) - session->last_comm;
-    purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta);
+    //purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta);
     if (delta >= MSIM_KEEPALIVE_INTERVAL)
     {
         errmsg = g_strdup_printf(_("Connection to server lost (no data received within %d seconds)"), (int)delta);
@@ -1747,7 +1816,7 @@
 		 * disconnect the user. As it stands, if Internet connection goes out (this
 		 * just happened here), msimprpl will appear to be connected forever, while
 		 * other plugins (oscar, etc.) will time out. Msimprpl should timeout too. */
-        purple_debug_info("msim", "msim_process: got keep alive\n");
+        //purple_debug_info("msim", "msim_process: got keep alive\n");
         return TRUE;
     } else {
 		msim_unrecognized(session, msg, "in msim_process");
--- a/libpurple/protocols/myspace/myspace.h	Thu Jul 12 04:15:45 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Fri Jul 13 05:48:13 2007 +0000
@@ -196,6 +196,10 @@
 int msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, 
 	PurpleMessageFlags flags);
 gboolean msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, int type);
+guint msim_point_to_purple_size(guint point);
+guint msim_purple_size_to_point(guint size);
+guint msim_height_to_point(guint height);
+guint msim_point_to_height(guint point);
 void msim_send_im_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
 
 void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note);