output: Add support for Pango markup of fonts and styles.
[pspp] / src / output / cairo.c
index 4debc93d8e29c02e88afb2e587c248bd49952132..c96384ae0a6018ceb5b02283203f55f82413d719 100644 (file)
@@ -39,6 +39,7 @@
 #include "output/charts/scree.h"
 #include "output/charts/scatterplot.h"
 #include "output/driver-provider.h"
+#include "output/group-item.h"
 #include "output/message-item.h"
 #include "output/options.h"
 #include "output/render.h"
@@ -58,6 +59,7 @@
 #include <pango/pangocairo.h>
 #include <stdlib.h>
 
+#include "gl/c-ctype.h"
 #include "gl/c-strcase.h"
 #include "gl/intprops.h"
 #include "gl/minmax.h"
@@ -70,7 +72,8 @@
 #define H TABLE_HORZ
 #define V TABLE_VERT
 
-/* The unit used for internal measurements is inch/(72 * XR_POINT). */
+/* The unit used for internal measurements is inch/(72 * XR_POINT).
+   (Thus, XR_POINT units represent one point.) */
 #define XR_POINT PANGO_SCALE
 
 /* Conversions to and from points. */
@@ -422,6 +425,7 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
       for (i = 0; i < TABLE_N_AXES; i++)
         xr->params->min_break[i] = xr->min_break[i];
       xr->params->supports_margins = true;
+      xr->params->rtl = render_direction_rtl ();
     }
 
   cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
@@ -1016,6 +1020,27 @@ add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_inde
   pango_attr_list_insert (list, attr);
 }
 
+static void
+markup_escape (const char *in, struct string *out)
+{
+  for (int c = *in++; c; c = *in++)
+    switch (c)
+      {
+      case '&':
+        ds_put_cstr (out, "&amp;");
+        break;
+      case '<':
+        ds_put_cstr (out, "&lt;");
+        break;
+      case '>':
+        ds_put_cstr (out, "&gt;");
+        break;
+      default:
+        ds_put_byte (out, c);
+        break;
+      }
+}
+
 static int
 xr_layout_cell_text (struct xr_driver *xr,
                      const struct cell_contents *contents,
@@ -1024,7 +1049,6 @@ xr_layout_cell_text (struct xr_driver *xr,
                      int *widthp, int *brk)
 {
   unsigned int options = contents->options;
-  size_t length;
   int w, h;
 
   struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
@@ -1069,34 +1093,83 @@ xr_layout_cell_text (struct xr_driver *xr,
   else
     footnote_adjustment = px_to_xr (style->margin[H][1]);
 
-  length = strlen (contents->text);
-  if (footnote_adjustment)
+  struct string tmp = DS_EMPTY_INITIALIZER;
+  const char *text = contents->text;
+
+  /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
+     Pango's implementation of it): it will break after a period or a comma
+     that precedes a digit, e.g. in ".000" it will break after the period.
+     This code looks for such a situation and inserts a U+2060 WORD JOINER
+     to prevent the break.
+
+     This isn't necessary when the decimal point is between two digits
+     (e.g. "0.000" won't be broken) or when the display width is not limited so
+     that word wrapping won't happen.
+
+     It isn't necessary to look for more than one period or comma, as would
+     happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
+     are present then there will always be a digit on both sides of every
+     period and comma. */
+  if (bb[H][1] != INT_MAX)
     {
-      PangoAttrList *attrs;
-      struct string s;
+      const char *decimal = text + strcspn (text, ".,");
+      if (decimal[0]
+          && c_isdigit (decimal[1])
+          && (decimal == text || !c_isdigit (decimal[-1])))
+        {
+          ds_extend (&tmp, strlen (text) + 16);
+          ds_put_substring (&tmp, ss_buffer (text, decimal - text + 1));
+          ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
+          ds_put_cstr (&tmp, decimal + 1);
+        }
+    }
 
+  if (footnote_adjustment)
+    {
       bb[H][1] += footnote_adjustment;
 
-      ds_init_empty (&s);
-      ds_extend (&s, length + contents->n_footnotes * 10);
-      ds_put_cstr (&s, contents->text);
-      cell_contents_format_footnote_markers (contents, &s);
-      pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
-      ds_destroy (&s);
+      if (ds_is_empty (&tmp))
+        {
+          ds_extend (&tmp, strlen (text) + 16);
+          ds_put_cstr (&tmp, text);
+        }
+      size_t initial_length = ds_length (&tmp);
 
-      attrs = pango_attr_list_new ();
+      for (size_t i = 0; i < contents->n_footnotes; i++)
+        {
+          if (i)
+            ds_put_byte (&tmp, ',');
+
+          const char *marker = contents->footnotes[i]->marker;
+          if (options & TAB_MARKUP)
+            markup_escape (marker, &tmp);
+          else
+            ds_put_cstr (&tmp, marker);
+        }
+
+      if (options & TAB_MARKUP)
+        pango_layout_set_markup (font->layout,
+                                 ds_cstr (&tmp), ds_length (&tmp));
+      else
+        pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
+
+      PangoAttrList *attrs = pango_attr_list_new ();
       if (style->underline)
         pango_attr_list_insert (attrs, pango_attr_underline_new (
                                PANGO_UNDERLINE_SINGLE));
-      add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
+      add_attr_with_start (attrs, pango_attr_rise_new (7000), initial_length);
       add_attr_with_start (
-        attrs, pango_attr_font_desc_new (font->desc), length);
+        attrs, pango_attr_font_desc_new (font->desc), initial_length);
       pango_layout_set_attributes (font->layout, attrs);
       pango_attr_list_unref (attrs);
     }
   else
     {
-      pango_layout_set_text (font->layout, contents->text, -1);
+      const char *content = ds_is_empty (&tmp) ? text : ds_cstr (&tmp);
+      if (options & TAB_MARKUP)
+        pango_layout_set_markup (font->layout, content, -1);
+      else
+        pango_layout_set_text (font->layout, content, -1);
 
       if (style->underline)
         {
@@ -1107,6 +1180,7 @@ xr_layout_cell_text (struct xr_driver *xr,
           pango_attr_list_unref (attrs);
         }
     }
+  ds_destroy (&tmp);
 
   pango_layout_set_alignment (
     font->layout,
@@ -1356,8 +1430,7 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
   else if (is_message_item (item))
     {
       const struct message_item *message_item = to_message_item (item);
-      const struct msg *msg = message_item_get_msg (message_item);
-      char *s = msg_to_string (msg, NULL);
+      char *s = msg_to_string (message_item_get_msg (message_item));
       r = xr_rendering_create_text (xr, s, cr);
       free (s);
     }
@@ -1374,6 +1447,9 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
       r = xzalloc (sizeof *r);
       r->item = output_item_ref (item);
     }
+  else if (is_group_open_item (item))
+    r = xr_rendering_create_text (xr, to_group_open_item (item)->command_name,
+                                  cr);
 
   return r;
 }
@@ -1409,7 +1485,8 @@ static void xr_draw_chart (const struct chart_item *, cairo_t *,
 
 /* Draws onto CR */
 void
-xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr)
+xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
+                   int x0, int y0, int x1, int y1)
 {
   if (is_table_item (r->item))
     {
@@ -1417,8 +1494,8 @@ xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr)
 
       xr_set_cairo (xr, cr);
 
-      render_pager_draw (r->p);
-
+      render_pager_draw_region (r->p, x0 * XR_POINT, y0 * XR_POINT,
+                                (x1 - x0) * XR_POINT, (y1 - y0) * XR_POINT);
     }
   else
     xr_draw_chart (to_chart_item (r->item), cr,
@@ -1507,7 +1584,6 @@ xr_draw_png_chart (const struct chart_item *item,
 struct xr_table_state
   {
     struct xr_render_fsm fsm;
-    struct table_item *table_item;
     struct render_pager *p;
   };
 
@@ -1537,25 +1613,24 @@ xr_table_destroy (struct xr_render_fsm *fsm)
 {
   struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
 
-  table_item_unref (ts->table_item);
   render_pager_destroy (ts->p);
   free (ts);
 }
 
 static struct xr_render_fsm *
-xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
+xr_render_table (struct xr_driver *xr, struct table_item *table_item)
 {
   struct xr_table_state *ts;
 
   ts = xmalloc (sizeof *ts);
   ts->fsm.render = xr_table_render;
   ts->fsm.destroy = xr_table_destroy;
-  ts->table_item = table_item_ref (table_item);
 
   if (xr->y > 0)
     xr->y += xr->char_height;
 
   ts->p = render_pager_create (xr->params, table_item);
+  table_item_unref (table_item);
 
   return &ts->fsm;
 }
@@ -1635,29 +1710,6 @@ xr_render_eject (void)
   return &eject_renderer;
 }
 \f
-static struct xr_render_fsm *
-xr_create_text_renderer (struct xr_driver *xr, const struct text_item *item)
-{
-  struct tab_table *tab = tab_create (1, 1);
-
-  struct cell_style *style = pool_alloc (tab->container, sizeof *style);
-  *style = (struct cell_style) CELL_STYLE_INITIALIZER;
-  if (item->font)
-    style->font = pool_strdup (tab->container, item->font);
-  style->font_size = item->font_size;
-  style->bold = item->bold;
-  style->italic = item->italic;
-  style->underline = item->underline;
-  tab->styles[0] = style;
-
-  tab_text (tab, 0, 0, TAB_LEFT, text_item_get_text (item));
-  struct table_item *table_item = table_item_create (&tab->table, NULL, NULL);
-  struct xr_render_fsm *fsm = xr_render_table (xr, table_item);
-  table_item_unref (table_item);
-
-  return fsm;
-}
-
 static struct xr_render_fsm *
 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
 {
@@ -1668,9 +1720,6 @@ xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
     case TEXT_ITEM_PAGE_TITLE:
       break;
 
-    case TEXT_ITEM_COMMAND_CLOSE:
-      break;
-
     case TEXT_ITEM_BLANK_LINE:
       if (xr->y > 0)
         xr->y += xr->char_height;
@@ -1682,7 +1731,8 @@ xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
       break;
 
     default:
-      return xr_create_text_renderer (xr, text_item);
+      return xr_render_table (
+        xr, text_item_to_table_item (text_item_ref (text_item)));
     }
 
   return NULL;
@@ -1692,14 +1742,10 @@ static struct xr_render_fsm *
 xr_render_message (struct xr_driver *xr,
                    const struct message_item *message_item)
 {
-  const struct msg *msg = message_item_get_msg (message_item);
-  char *s = msg_to_string (msg, message_item->command_name);
+  char *s = msg_to_string (message_item_get_msg (message_item));
   struct text_item *item = text_item_create (TEXT_ITEM_PARAGRAPH, s);
   free (s);
-  struct xr_render_fsm *fsm = xr_create_text_renderer (xr, item);
-  text_item_unref (item);
-
-  return fsm;
+  return xr_render_table (xr, text_item_to_table_item (item));
 }
 
 static struct xr_render_fsm *
@@ -1707,7 +1753,7 @@ xr_render_output_item (struct xr_driver *xr,
                        const struct output_item *output_item)
 {
   if (is_table_item (output_item))
-    return xr_render_table (xr, to_table_item (output_item));
+    return xr_render_table (xr, table_item_ref (to_table_item (output_item)));
   else if (is_chart_item (output_item))
     return xr_render_chart (to_chart_item (output_item));
   else if (is_text_item (output_item))