cairo: Fix word-wrapping of ".000" and similar.
[pspp] / src / output / cairo.c
index 22e060cf936d33bd915a235e5a563be139312f8d..3e824607f2377f6fb0ed0f0f67a7e684df96c219 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+   Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #include "libpspp/assertion.h"
 #include "libpspp/cast.h"
 #include "libpspp/message.h"
+#include "libpspp/pool.h"
 #include "libpspp/start-date.h"
 #include "libpspp/str.h"
 #include "libpspp/string-map.h"
 #include "libpspp/version.h"
+#include "data/file-handle-def.h"
 #include "output/cairo-chart.h"
 #include "output/chart-item-provider.h"
 #include "output/charts/boxplot.h"
 #include "output/charts/np-plot.h"
 #include "output/charts/piechart.h"
+#include "output/charts/barchart.h"
 #include "output/charts/plot-hist.h"
 #include "output/charts/roc-chart.h"
 #include "output/charts/spreadlevel-plot.h"
 #include "output/charts/scree.h"
+#include "output/charts/scatterplot.h"
 #include "output/driver-provider.h"
 #include "output/message-item.h"
 #include "output/options.h"
@@ -54,6 +58,8 @@
 #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"
 #include "gl/xalloc.h"
@@ -75,6 +81,17 @@ xr_to_pt (int x)
   return x / (double) XR_POINT;
 }
 
+/* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
+static int
+px_to_xr (int x)
+{
+  return x * (PANGO_SCALE * 72 / 96);
+}
+
+/* Dimensions for drawing lines in tables. */
+#define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
+#define XR_LINE_SPACE XR_POINT       /* Space between double lines. */
+
 /* Output types. */
 enum xr_output_type
   {
@@ -127,7 +144,6 @@ struct xr_driver
     int top_margin;             /* Top margin in inch/(72 * XR_POINT). */
     int bottom_margin;          /* Bottom margin in inch/(72 * XR_POINT). */
 
-    int line_gutter;           /* Space around lines. */
     int line_space;            /* Space between lines. */
     int line_width;            /* Width of lines. */
 
@@ -155,13 +171,15 @@ static void xr_driver_destroy_fsm (struct xr_driver *);
 static void xr_driver_run_fsm (struct xr_driver *);
 
 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
-                          enum render_line_style styles[TABLE_N_AXES][2]);
+                          enum render_line_style styles[TABLE_N_AXES][2],
+                          struct cell_color colors[TABLE_N_AXES][2]);
 static void xr_measure_cell_width (void *, const struct table_cell *,
                                    int *min, int *max);
 static int xr_measure_cell_height (void *, const struct table_cell *,
                                    int width);
-static void xr_draw_cell (void *, const struct table_cell *,
+static void xr_draw_cell (void *, const struct table_cell *, int color_idx,
                           int bb[TABLE_N_AXES][2],
+                          int spill[TABLE_N_AXES][2],
                           int clip[TABLE_N_AXES][2]);
 static int xr_adjust_break (void *, const struct table_cell *,
                             int width, int height);
@@ -217,37 +235,52 @@ parse_color (struct output_driver *d, struct string_map *options,
 }
 
 static PangoFontDescription *
-parse_font (struct output_driver *d, struct string_map *options,
-            const char *key, const char *default_value,
-            int default_size)
+parse_font (const char *font, int default_size, bool bold, bool italic)
 {
-  PangoFontDescription *desc;
-  char *string;
+  if (!c_strcasecmp (font, "Monospaced"))
+    font = "Monospace";
 
-  /* Parse KEY as a font description. */
-  string = parse_string (opt (d, options, key, default_value));
-  desc = pango_font_description_from_string (string);
+  PangoFontDescription *desc = pango_font_description_from_string (font);
   if (desc == NULL)
+    return NULL;
+
+  /* If the font description didn't include an explicit font size, then set it
+     to DEFAULT_SIZE, which is in inch/72000 units. */
+  if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
+    pango_font_description_set_size (desc,
+                                     (default_size / 1000.0) * PANGO_SCALE);
+
+  pango_font_description_set_weight (desc, (bold
+                                            ? PANGO_WEIGHT_BOLD
+                                            : PANGO_WEIGHT_NORMAL));
+  pango_font_description_set_style (desc, (italic
+                                           ? PANGO_STYLE_ITALIC
+                                           : PANGO_STYLE_NORMAL));
+
+  return desc;
+}
+
+static PangoFontDescription *
+parse_font_option (struct output_driver *d, struct string_map *options,
+                   const char *key, const char *default_value,
+                   int default_size, bool bold, bool italic)
+{
+  char *string = parse_string (opt (d, options, key, default_value));
+  PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
+  if (!desc)
     {
       msg (MW, _("`%s': bad font specification"), string);
 
       /* Fall back to DEFAULT_VALUE, which had better be a valid font
          description. */
-      desc = pango_font_description_from_string (default_value);
+      desc = parse_font (default_value, default_size, bold, italic);
       assert (desc != NULL);
     }
   free (string);
 
-  /* If the font description didn't include an explicit font size, then set it
-     to DEFAULT_SIZE, which is in inch/72000 units. */
-  if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
-    pango_font_description_set_size (desc,
-                                     (default_size / 1000.0) * PANGO_SCALE);
-
   return desc;
 }
 
-
 static void
 apply_options (struct xr_driver *xr, struct string_map *o)
 {
@@ -274,16 +307,13 @@ apply_options (struct xr_driver *xr, struct string_map *o)
     }
 
   font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
-  xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
-                                              font_size);
-  xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
-                                                     "serif", font_size);
-  xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
-                                                 "serif italic", font_size);
-
-  xr->line_gutter = parse_dimension (opt (d, o, "gutter", "3pt")) * scale;
-  xr->line_space = XR_POINT;
-  xr->line_width = XR_POINT / 2;
+  xr->fonts[XR_FONT_FIXED].desc = parse_font_option
+    (d, o, "fixed-font", "monospace", font_size, false, false);
+  xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font_option (
+    d, o, "prop-font", "sans serif", font_size, false, false);
+  xr->fonts[XR_FONT_EMPHASIS].desc = parse_font_option (
+    d, o, "emph-font", "sans serif", font_size, false, true);
+
   xr->page_number = 0;
 
   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
@@ -346,7 +376,7 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
 
   xr->cairo = cairo;
 
-  cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
+  cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
 
   xr->char_width = 0;
   xr->char_height = 0;
@@ -366,8 +396,6 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
 
   if (xr->params == NULL)
     {
-      int single_width, double_width;
-
       xr->params = xmalloc (sizeof *xr->params);
       xr->params->draw_line = xr_draw_line;
       xr->params->measure_cell_width = xr_measure_cell_width;
@@ -380,17 +408,21 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
       xr->params->font_size[H] = xr->char_width;
       xr->params->font_size[V] = xr->char_height;
 
-      single_width = 2 * xr->line_gutter + xr->line_width;
-      double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
+      int lw = XR_LINE_WIDTH;
+      int ls = XR_LINE_SPACE;
       for (i = 0; i < TABLE_N_AXES; i++)
         {
           xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
-          xr->params->line_widths[i][RENDER_LINE_SINGLE] = single_width;
-          xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
+          xr->params->line_widths[i][RENDER_LINE_SINGLE] = lw;
+          xr->params->line_widths[i][RENDER_LINE_DASHED] = lw;
+          xr->params->line_widths[i][RENDER_LINE_THICK] = lw * 2;
+          xr->params->line_widths[i][RENDER_LINE_THIN] = lw / 2;
+          xr->params->line_widths[i][RENDER_LINE_DOUBLE] = 2 * lw + ls;
         }
 
       for (i = 0; i < TABLE_N_AXES; i++)
         xr->params->min_break[i] = xr->min_break[i];
+      xr->params->supports_margins = true;
     }
 
   cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
@@ -467,24 +499,30 @@ xr_create (const char *file_name, enum settings_output_devices device_type,
 }
 
 static struct output_driver *
-xr_pdf_create (const char *file_name, enum settings_output_devices device_type,
+xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
                struct string_map *o)
 {
-  return xr_create (file_name, device_type, o, XR_PDF);
+  struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_PDF);
+  fh_unref (fh);
+  return od ;
 }
 
 static struct output_driver *
-xr_ps_create (const char *file_name, enum settings_output_devices device_type,
+xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
                struct string_map *o)
 {
-  return xr_create (file_name, device_type, o, XR_PS);
+  struct output_driver *od =  xr_create (fh_get_file_name (fh), device_type, o, XR_PS);
+  fh_unref (fh);
+  return od ;
 }
 
 static struct output_driver *
-xr_svg_create (const char *file_name, enum settings_output_devices device_type,
+xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
                struct string_map *o)
 {
-  return xr_create (file_name, device_type, o, XR_SVG);
+  struct output_driver *od = xr_create (fh_get_file_name (fh), device_type, o, XR_SVG);
+  fh_unref (fh);
+  return od ;
 }
 
 static void
@@ -508,7 +546,6 @@ xr_destroy (struct output_driver *driver)
       cairo_destroy (xr->cairo);
     }
 
-  free (xr->command_name);
   for (i = 0; i < XR_N_FONTS; i++)
     {
       struct xr_font *font = &xr->fonts[i];
@@ -531,51 +568,11 @@ xr_flush (struct output_driver *driver)
   cairo_surface_flush (cairo_get_target (xr->cairo));
 }
 
-static void
-xr_init_caption_cell (const char *caption, struct table_cell *cell,
-                      struct cell_contents *contents)
-{
-  contents->options = TAB_LEFT;
-  contents->text = CONST_CAST (char *, caption);
-  contents->table = NULL;
-  cell->contents = contents;
-  cell->n_contents = 1;
-  cell->destructor = NULL;
-}
-
-static struct render_pager *
-xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
-                      int *caption_widthp, int *caption_heightp)
-{
-  const char *caption = table_item_get_caption (item);
-
-  if (caption != NULL)
-    {
-      /* XXX doesn't do well with very large captions */
-      struct cell_contents contents;
-      int min_width, max_width;
-      struct table_cell cell;
-
-      xr_init_caption_cell (caption, &cell, &contents);
-
-      xr_measure_cell_width (xr, &cell, &min_width, &max_width);
-      *caption_widthp = MIN (max_width, xr->width);
-      *caption_heightp = xr_measure_cell_height (xr, &cell, *caption_widthp);
-    }
-  else
-    *caption_heightp = 0;
-
-  return render_pager_create (render_page_create (xr->params,
-                                                  table_item_get_table (item)));
-}
-
 static void
 xr_submit (struct output_driver *driver, const struct output_item *output_item)
 {
   struct xr_driver *xr = xr_driver_cast (driver);
 
-  output_driver_track_current_command (output_item, &xr->command_name);
-
   xr_driver_output_item (xr, output_item);
   while (xr_driver_need_new_page (xr))
     {
@@ -594,24 +591,19 @@ xr_submit (struct output_driver *driver, const struct output_item *output_item)
 
    See the big comment in cairo.h for intended usage. */
 
-/* Gives new page CAIRO to XR for output.  CAIRO may be null to skip actually
-   rendering the page (which might be useful to find out how many pages an
-   output document has without actually rendering it). */
+/* Gives new page CAIRO to XR for output. */
 void
 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
 {
-  if (cairo != NULL)
-    {
-      cairo_save (cairo);
-      cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
-      cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
-      cairo_fill (cairo);
-      cairo_restore (cairo);
-
-      cairo_translate (cairo,
-                      xr_to_pt (xr->left_margin),
-                      xr_to_pt (xr->top_margin));
-    }
+  cairo_save (cairo);
+  cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue);
+  cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
+  cairo_fill (cairo);
+  cairo_restore (cairo);
+
+  cairo_translate (cairo,
+                   xr_to_pt (xr->left_margin),
+                   xr_to_pt (xr->top_margin));
 
   xr->page_number++;
   xr->cairo = cairo;
@@ -670,9 +662,17 @@ xr_layout_cell (struct xr_driver *, const struct table_cell *,
                 int *width, int *height, int *brk);
 
 static void
-dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
+dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style,
+           const struct cell_color *color)
 {
   cairo_new_path (xr->cairo);
+  cairo_set_source_rgb (xr->cairo,
+                        color->r / 255.0, color->g / 255.0, color->b / 255.0);
+  cairo_set_line_width (
+    xr->cairo,
+    xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
+              : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
+              : XR_LINE_WIDTH));
   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
   cairo_stroke (xr->cairo);
@@ -682,6 +682,7 @@ static void UNUSED
 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
 {
   cairo_new_path (xr->cairo);
+  cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
   cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
   cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
@@ -690,6 +691,17 @@ dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
   cairo_stroke (xr->cairo);
 }
 
+static void
+fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
+{
+  cairo_new_path (xr->cairo);
+  cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
+  cairo_rectangle (xr->cairo,
+                   xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y),
+                   xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
+  cairo_fill (xr->cairo);
+}
+
 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
    shortening it to X0...X1 if SHORTEN is true.
    Draws a horizontal line X1...X3 at Y if RIGHT says so,
@@ -697,16 +709,19 @@ dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
 static void
 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
            enum render_line_style left, enum render_line_style right,
+           const struct cell_color *left_color,
+           const struct cell_color *right_color,
            bool shorten)
 {
-  if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
-    dump_line (xr, x0, y, x3, y);
+  if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
+      && cell_color_equal (left_color, right_color))
+    dump_line (xr, x0, y, x3, y, left, left_color);
   else
     {
       if (left != RENDER_LINE_NONE)
-        dump_line (xr, x0, y, shorten ? x1 : x2, y);
+        dump_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
       if (right != RENDER_LINE_NONE)
-        dump_line (xr, shorten ? x2 : x1, y, x3, y);
+        dump_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
     }
 }
 
@@ -717,31 +732,42 @@ horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
 static void
 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
            enum render_line_style top, enum render_line_style bottom,
+           const struct cell_color *top_color,
+           const struct cell_color *bottom_color,
            bool shorten)
 {
-  if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
-    dump_line (xr, x, y0, x, y3);
+  if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
+      && cell_color_equal (top_color, bottom_color))
+    dump_line (xr, x, y0, x, y3, top, top_color);
   else
     {
       if (top != RENDER_LINE_NONE)
-        dump_line (xr, x, y0, x, shorten ? y1 : y2);
+        dump_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
       if (bottom != RENDER_LINE_NONE)
-        dump_line (xr, x, shorten ? y2 : y1, x, y3);
+        dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
     }
 }
 
 static void
 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
-              enum render_line_style styles[TABLE_N_AXES][2])
+              enum render_line_style styles[TABLE_N_AXES][2],
+              struct cell_color colors[TABLE_N_AXES][2])
 {
   const int x0 = bb[H][0];
   const int y0 = bb[V][0];
   const int x3 = bb[H][1];
   const int y3 = bb[V][1];
   const int top = styles[H][0];
-  const int left = styles[V][0];
   const int bottom = styles[H][1];
-  const int right = styles[V][1];
+
+  int start_side = render_direction_rtl();
+  int end_side = !start_side;
+  const int start_of_line = styles[V][start_side];
+  const int end_of_line   = styles[V][end_side];
+  const struct cell_color *top_color = &colors[H][0];
+  const struct cell_color *bottom_color = &colors[H][1];
+  const struct cell_color *start_color = &colors[V][start_side];
+  const struct cell_color *end_color = &colors[V][end_side];
 
   /* The algorithm here is somewhat subtle, to allow it to handle
      all the kinds of intersections that we need.
@@ -775,13 +801,13 @@ xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
   struct xr_driver *xr = xr_;
 
   /* Offset from center of each line in a pair of double lines. */
-  int double_line_ofs = (xr->line_space + xr->line_width) / 2;
+  int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
 
   /* Are the lines along each axis single or double?
      (It doesn't make sense to have different kinds of line on the
      same axis, so we don't try to gracefully handle that case.) */
   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
-  bool double_horz = left == RENDER_LINE_DOUBLE || right == RENDER_LINE_DOUBLE;
+  bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
 
   /* When horizontal lines are doubled,
      the left-side line along y1 normally runs from x0 to x2,
@@ -816,8 +842,8 @@ xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
   int x1 = xc - horz_line_ofs;
   int x2 = xc + horz_line_ofs;
 
-  bool shorten_x1_lines = left == RENDER_LINE_DOUBLE;
-  bool shorten_x2_lines = right == RENDER_LINE_DOUBLE;
+  bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
+  bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
   int vert_line_ofs = double_horz ? double_line_ofs : 0;
   int yc = (y0 + y3) / 2;
@@ -825,19 +851,25 @@ xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
   int y2 = yc + vert_line_ofs;
 
   if (!double_horz)
-    horz_line (xr, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
+    horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
+               start_color, end_color, shorten_yc_line);
   else
     {
-      horz_line (xr, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
-      horz_line (xr, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
+      horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
+                 start_color, end_color, shorten_y1_lines);
+      horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
+                 start_color, end_color, shorten_y2_lines);
     }
 
   if (!double_vert)
-    vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
+    vert_line (xr, y0, y1, y2, y3, xc, top, bottom, top_color, bottom_color,
+               shorten_xc_line);
   else
     {
-      vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
-      vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
+      vert_line (xr, y0, y1, y2, y3, x1, top, bottom, top_color, bottom_color,
+                 shorten_x1_lines);
+      vert_line (xr, y0, y1, y2, y3, x2, top, bottom, top_color, bottom_color,
+                 shorten_x2_lines);
     }
 }
 
@@ -859,6 +891,13 @@ xr_measure_cell_width (void *xr_, const struct table_cell *cell,
 
   bb[H][1] = 1;
   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
+
+  if (*min_width > 0)
+    *min_width += px_to_xr (cell->style->margin[H][0]
+                            + cell->style->margin[H][1]);
+  if (*max_width > 0)
+    *max_width += px_to_xr (cell->style->margin[H][0]
+                            + cell->style->margin[H][1]);
 }
 
 static int
@@ -870,22 +909,65 @@ xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
   int w, h;
 
   bb[H][0] = 0;
-  bb[H][1] = width;
+  bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
+                               + cell->style->margin[H][1]);
   bb[V][0] = 0;
   bb[V][1] = INT_MAX;
   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
+  h += px_to_xr (cell->style->margin[V][0] + cell->style->margin[V][1]);
   return h;
 }
 
+static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]);
+
 static void
-xr_draw_cell (void *xr_, const struct table_cell *cell,
-              int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
+xr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
+              int bb[TABLE_N_AXES][2],
+              int spill[TABLE_N_AXES][2],
+              int clip[TABLE_N_AXES][2])
 {
   struct xr_driver *xr = xr_;
   int w, h, brk;
 
-  xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
+  cairo_save (xr->cairo);
+  int bg_clip[TABLE_N_AXES][2];
+  for (int axis = 0; axis < TABLE_N_AXES; axis++)
+    {
+      bg_clip[axis][0] = clip[axis][0];
+      if (bb[axis][0] == clip[axis][0])
+        bg_clip[axis][0] -= spill[axis][0];
+
+      bg_clip[axis][1] = clip[axis][1];
+      if (bb[axis][1] == clip[axis][1])
+        bg_clip[axis][1] += spill[axis][1];
+    }
+  xr_clip (xr, bg_clip);
+  cairo_set_source_rgb (xr->cairo,
+                        cell->style->bg[color_idx].r / 255.,
+                        cell->style->bg[color_idx].g / 255.,
+                        cell->style->bg[color_idx].b / 255.);
+  fill_rectangle (xr,
+                  bb[H][0] - spill[H][0],
+                  bb[V][0] - spill[V][0],
+                  bb[H][1] + spill[H][1],
+                  bb[V][1] + spill[V][1]);
+  cairo_restore (xr->cairo);
+
+  cairo_save (xr->cairo);
+  cairo_set_source_rgb (xr->cairo,
+                        cell->style->fg[color_idx].r / 255.,
+                        cell->style->fg[color_idx].g / 255.,
+                        cell->style->fg[color_idx].b / 255.);
+
+  for (int axis = 0; axis < TABLE_N_AXES; axis++)
+    {
+      bb[axis][0] += px_to_xr (cell->style->margin[axis][0]);
+      bb[axis][1] -= px_to_xr (cell->style->margin[axis][1]);
+    }
+  if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
+    xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
+  cairo_restore (xr->cairo);
 }
 
 static int
@@ -901,9 +983,13 @@ xr_adjust_break (void *xr_, const struct table_cell *cell,
     return -1;
 
   bb[H][0] = 0;
-  bb[H][1] = width;
+  bb[H][1] = width - px_to_xr (cell->style->margin[H][0]
+                               + cell->style->margin[H][1]);
+  if (bb[H][1] <= 0)
+    return 0;
   bb[V][0] = 0;
-  bb[V][1] = height;
+  bb[V][1] = height - px_to_xr (cell->style->margin[V][0]
+                                + cell->style->margin[V][1]);
   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
   return brk;
@@ -924,26 +1010,140 @@ xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
     }
 }
 
+static void
+add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index)
+{
+  attr->start_index = start_index;
+  pango_attr_list_insert (list, attr);
+}
+
 static int
 xr_layout_cell_text (struct xr_driver *xr,
                      const struct cell_contents *contents,
+                     const struct cell_style *style,
                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
-                     int y, int *widthp, int *brk)
+                     int *widthp, int *brk)
 {
   unsigned int options = contents->options;
-  struct xr_font *font;
   int w, h;
 
-  font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
-          : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
-          : &xr->fonts[XR_FONT_PROPORTIONAL]);
+  struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
+                          : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
+                          : &xr->fonts[XR_FONT_PROPORTIONAL]);
+  struct xr_font local_font;
+  if (style->font)
+    {
+      PangoFontDescription *desc = parse_font (
+        style->font,
+        style->font_size ? style->font_size * 1000 * 72 / 128 : 10000,
+        style->bold, style->italic);
+      if (desc)
+        {
+          PangoLayout *layout = pango_cairo_create_layout (xr->cairo);
+          pango_layout_set_font_description (layout, desc);
+
+          local_font.desc = desc;
+          local_font.layout = layout;
+          font = &local_font;
+        }
+    }
+
+  int footnote_adjustment;
+  if (contents->n_footnotes == 0)
+    footnote_adjustment = 0;
+  else if (contents->n_footnotes == 1 && (options & TAB_HALIGN) == TAB_RIGHT)
+    {
+      PangoAttrList *attrs;
+
+      const char *marker = contents->footnotes[0]->marker;
+      pango_layout_set_text (font->layout, marker, strlen (marker));
+
+      attrs = pango_attr_list_new ();
+      pango_attr_list_insert (attrs, pango_attr_rise_new (7000));
+      pango_layout_set_attributes (font->layout, attrs);
+      pango_attr_list_unref (attrs);
+
+      pango_layout_get_size (font->layout, &w, &h);
+      footnote_adjustment = MIN (w, px_to_xr (style->margin[H][1]));
+    }
+  else
+    footnote_adjustment = px_to_xr (style->margin[H][1]);
+
+  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)
+    {
+      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;
+
+      if (ds_is_empty (&tmp))
+        {
+          ds_extend (&tmp, strlen (text) + 16);
+          ds_put_cstr (&tmp, text);
+        }
+      size_t initial_length = ds_length (&tmp);
+
+      cell_contents_format_footnote_markers (contents, &tmp);
+      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), initial_length);
+      add_attr_with_start (
+        attrs, pango_attr_font_desc_new (font->desc), initial_length);
+      pango_layout_set_attributes (font->layout, attrs);
+      pango_attr_list_unref (attrs);
+    }
+  else
+    {
+      const char *content = ds_is_empty (&tmp) ? text : ds_cstr (&tmp);
+      pango_layout_set_text (font->layout, content, -1);
 
-  pango_layout_set_text (font->layout, contents->text, -1);
+      if (style->underline)
+        {
+          PangoAttrList *attrs = pango_attr_list_new ();
+          pango_attr_list_insert (attrs, pango_attr_underline_new (
+                                    PANGO_UNDERLINE_SINGLE));
+          pango_layout_set_attributes (font->layout, attrs);
+          pango_attr_list_unref (attrs);
+        }
+    }
+  ds_destroy (&tmp);
 
   pango_layout_set_alignment (
     font->layout,
-    ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
-     : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
+    ((options & TAB_HALIGN) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
+     : (options & TAB_HALIGN) == TAB_LEFT ? PANGO_ALIGN_LEFT
      : PANGO_ALIGN_CENTER));
   pango_layout_set_width (
     font->layout,
@@ -956,7 +1156,7 @@ xr_layout_cell_text (struct xr_driver *xr,
       xr_clip (xr, clip);
       cairo_translate (xr->cairo,
                        xr_to_pt (bb[H][0] + xr->x),
-                       xr_to_pt (y + xr->y));
+                       xr_to_pt (bb[V][0] + xr->y));
       pango_cairo_show_layout (xr->cairo, font->layout);
 
       /* If enabled, this draws a blue rectangle around the extents of each
@@ -992,7 +1192,7 @@ xr_layout_cell_text (struct xr_driver *xr,
   h = pango_to_xr (h);
   if (w > *widthp)
     *widthp = w;
-  if (y + h >= bb[V][1])
+  if (bb[V][0] + h >= bb[V][1])
     {
       PangoLayoutIter *iter;
       int best UNUSED = 0;
@@ -1011,107 +1211,40 @@ xr_layout_cell_text (struct xr_driver *xr,
           extents.y = pango_to_xr (y0);
           extents.width = pango_to_xr (extents.width);
           extents.height = pango_to_xr (y1 - y0);
-          bottom = y + extents.y + extents.height;
+          bottom = bb[V][0] + extents.y + extents.height;
           if (bottom < bb[V][1])
             {
               if (brk && clip[H][0] != clip[H][1])
                 best = bottom;
-              *brk = bottom;
+              if (brk)
+                *brk = bottom;
             }
           else
             break;
         }
       while (pango_layout_iter_next_line (iter));
+      pango_layout_iter_free (iter);
 
       /* If enabled, draws a green line across the chosen breakpoint, which can
          be useful for debugging issues with breaking.  */
       if (0)
         {
           if (best && !xr->nest)
-            {
-              cairo_save (xr->cairo);
-              cairo_set_source_rgb (xr->cairo, 0, 1, 0);
-              dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best);
-              cairo_restore (xr->cairo);
-            }
+            dump_line (xr, -xr->left_margin, best,
+                       xr->width + xr->right_margin, best,
+                       RENDER_LINE_SINGLE, &CELL_COLOR (0, 255, 0));
         }
     }
-  return y + h;
-}
 
-static int
-xr_layout_cell_subtable (struct xr_driver *xr,
-                         const struct cell_contents *contents,
-                         int bb[TABLE_N_AXES][2],
-                         int clip[TABLE_N_AXES][2], int *widthp, int *brk)
-{
-  const struct table *table = contents->table;
-  int single_width, double_width;
-  struct render_params params;
-  struct render_pager *p;
-  int r[TABLE_N_AXES][2];
-  int width, height;
-  int i;
+  pango_layout_set_attributes (font->layout, NULL);
 
-  params.draw_line = xr_draw_line;
-  params.measure_cell_width = xr_measure_cell_width;
-  params.measure_cell_height = xr_measure_cell_height;
-  params.adjust_break = NULL;
-  params.draw_cell = xr_draw_cell;
-  params.aux = xr;
-  params.size[H] = bb[H][1] - bb[H][0];
-  params.size[V] = bb[V][1] - bb[V][0];
-  params.font_size[H] = xr->char_width;
-  params.font_size[V] = xr->char_height;
-
-  single_width = 2 * xr->line_gutter + xr->line_width;
-  double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
-  for (i = 0; i < TABLE_N_AXES; i++)
+  if (font == &local_font)
     {
-      params.line_widths[i][RENDER_LINE_NONE] = 0;
-      params.line_widths[i][RENDER_LINE_SINGLE] = single_width;
-      params.line_widths[i][RENDER_LINE_DOUBLE] = double_width;
+      g_object_unref (G_OBJECT (font->layout));
+      pango_font_description_free (font->desc);
     }
 
-  xr->nest++;
-  p = render_pager_create (render_page_create (&params, table));
-  width = render_pager_get_size (p, H);
-  height = render_pager_get_size (p, V);
-  if (bb[V][0] + height >= bb[V][1])
-    *brk = bb[V][0] + render_pager_get_best_breakpoint (p, bb[V][1] - bb[V][0]);
-
-  /* r = intersect(bb, clip) - bb. */
-  for (i = 0; i < TABLE_N_AXES; i++)
-    {
-      r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
-      r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
-    }
-
-  if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
-    {
-      unsigned int alignment = contents->options & TAB_ALIGNMENT;
-      int save_x = xr->x;
-
-      cairo_save (xr->cairo);
-      xr_clip (xr, clip);
-      xr->x += bb[H][0];
-      if (alignment == TAB_RIGHT)
-        xr->x += params.size[H] - width;
-      else if (alignment == TAB_CENTER)
-        xr->x += (params.size[H] - width) / 2;
-      xr->y += bb[V][0];
-      render_pager_draw_region (p, r[H][0], r[V][0],
-                                r[H][1] - r[H][0], r[V][1] - r[V][0]);
-      xr->y -= bb[V][0];
-      xr->x = save_x;
-      cairo_restore (xr->cairo);
-    }
-  render_pager_destroy (p);
-  xr->nest--;
-
-  if (width > *widthp)
-    *widthp = width;
-  return bb[V][0] + height;
+  return bb[V][0] + h;
 }
 
 static void
@@ -1161,30 +1294,11 @@ xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
             *brk = bb[V][0];
         }
 
-      if (contents->text)
-        bb[V][0] = xr_layout_cell_text (xr, contents, bb, clip,
-                                        bb[V][0], width, brk);
-      else
-        bb[V][0] = xr_layout_cell_subtable (xr, contents, bb, clip, width, brk);
+      bb[V][0] = xr_layout_cell_text (xr, contents, cell->style, bb, clip,
+                                      width, brk);
     }
   *height = bb[V][0] - bb_[V][0];
 }
-
-static void
-xr_draw_title (struct xr_driver *xr, const char *title,
-               int title_width, int title_height)
-{
-  struct cell_contents contents;
-  struct table_cell cell;
-  int bb[TABLE_N_AXES][2];
-
-  xr_init_caption_cell (title, &cell, &contents);
-  bb[H][0] = 0;
-  bb[H][1] = title_width;
-  bb[V][0] = 0;
-  bb[V][1] = title_height;
-  xr_draw_cell (xr, &cell, bb, bb);
-}
 \f
 struct output_driver_factory pdf_driver_factory =
   { "pdf", "pspp.pdf", xr_pdf_create };
@@ -1210,8 +1324,6 @@ struct xr_rendering
     /* Table items. */
     struct render_pager *p;
     struct xr_driver *xr;
-    int title_width;
-    int title_height;
   };
 
 #define CHART_WIDTH 500
@@ -1249,14 +1361,15 @@ xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
   struct table_item *table_item;
   struct xr_rendering *r;
 
-  table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
+  table_item = table_item_create (table_from_string (TAB_LEFT, text),
+                                  NULL, NULL);
   r = xr_rendering_create (xr, &table_item->output_item, cr);
   table_item_unref (table_item);
 
   return r;
 }
 
-void 
+void
 xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
 {
   if (is_table_item (xr->item))
@@ -1275,8 +1388,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);
     }
@@ -1286,8 +1398,7 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
       r->item = output_item_ref (item);
       r->xr = xr;
       xr_set_cairo (xr, cr);
-      r->p = xr_render_table_item (xr, to_table_item (item),
-                                   &r->title_width, &r->title_height);
+      r->p = render_pager_create (xr->params, to_table_item (item));
     }
   else if (is_chart_item (item))
     {
@@ -1314,10 +1425,8 @@ xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
 {
   if (is_table_item (r->item))
     {
-      int w0 = render_pager_get_size (r->p, H);
-      int w1 = r->title_width;
-      *w = MAX (w0, w1) / XR_POINT;
-      *h = (render_pager_get_size (r->p, V) + r->title_height) / XR_POINT;
+      *w = render_pager_get_size (r->p, H) / XR_POINT;
+      *h = render_pager_get_size (r->p, V) / XR_POINT;
     }
   else
     {
@@ -1329,11 +1438,9 @@ xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
 static void xr_draw_chart (const struct chart_item *, cairo_t *,
                     double x, double y, double width, double height);
 
-/* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
-   and possibly some additional parts. */
+/* Draws onto CR */
 void
-xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
-                   int x, int y, int w, int h)
+xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr)
 {
   if (is_table_item (r->item))
     {
@@ -1341,17 +1448,8 @@ xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
 
       xr_set_cairo (xr, cr);
 
-      if (r->title_height > 0)
-        {
-          xr->y = 0;
-          xr_draw_title (xr, table_item_get_caption (to_table_item (r->item)),
-                         r->title_width, r->title_height);
-        }
+      render_pager_draw (r->p);
 
-      xr->y = r->title_height;
-      render_pager_draw_region (r->p,
-                                x * XR_POINT, (y * XR_POINT) - r->title_height,
-                                w * XR_POINT, h * XR_POINT);
     }
   else
     xr_draw_chart (to_chart_item (r->item), cr,
@@ -1376,12 +1474,16 @@ xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
     xrchart_draw_np_plot (chart_item, cr, &geom);
   else if (is_piechart (chart_item))
     xrchart_draw_piechart (chart_item, cr, &geom);
+  else if (is_barchart (chart_item))
+    xrchart_draw_barchart (chart_item, cr, &geom);
   else if (is_roc_chart (chart_item))
     xrchart_draw_roc (chart_item, cr, &geom);
   else if (is_scree (chart_item))
     xrchart_draw_scree (chart_item, cr, &geom);
   else if (is_spreadlevel_plot_chart (chart_item))
     xrchart_draw_spreadlevel (chart_item, cr, &geom);
+  else if (is_scatterplot_chart (chart_item))
+    xrchart_draw_scatterplot (chart_item, cr, &geom);
   else
     NOT_REACHED ();
   xrchart_geometry_free (cr, &geom);
@@ -1438,7 +1540,6 @@ struct xr_table_state
     struct xr_render_fsm fsm;
     struct table_item *table_item;
     struct render_pager *p;
-    int caption_height;
   };
 
 static bool
@@ -1448,28 +1549,16 @@ xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
 
   while (render_pager_has_next (ts->p))
     {
-      int caption_height = ts->caption_height;
       int used;
 
-      xr->y += caption_height;
       used = render_pager_draw_next (ts->p, xr->length - xr->y);
-      xr->y -= caption_height;
       if (!used)
         {
           assert (xr->y > 0);
           return true;
         }
       else
-        {
-          if (ts->caption_height)
-            {
-              if (xr->cairo)
-                xr_draw_title (xr, table_item_get_caption (ts->table_item),
-                               xr->width, ts->caption_height);
-              ts->caption_height = 0;
-            }
-          xr->y += caption_height + used;
-        }
+        xr->y += used;
     }
   return false;
 }
@@ -1488,7 +1577,6 @@ static struct xr_render_fsm *
 xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
 {
   struct xr_table_state *ts;
-  int caption_width;
 
   ts = xmalloc (sizeof *ts);
   ts->fsm.render = xr_table_render;
@@ -1498,9 +1586,7 @@ xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
   if (xr->y > 0)
     xr->y += xr->char_height;
 
-  ts->p = xr_render_table_item (xr, table_item,
-                                &caption_width, &ts->caption_height);
-  xr->params->size[V] = xr->length - ts->caption_height;
+  ts->p = render_pager_create (xr->params, table_item);
 
   return &ts->fsm;
 }
@@ -1516,13 +1602,20 @@ xr_chart_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
 {
   struct xr_chart_state *cs = UP_CAST (fsm, struct xr_chart_state, fsm);
 
-  if (xr->y > 0)
+  const int chart_height = 0.8 * (xr->length < xr->width ? xr->length : xr->width);
+
+  if (xr->y > xr->length - chart_height)
     return true;
 
   if (xr->cairo != NULL)
-    xr_draw_chart (cs->chart_item, xr->cairo, 0.0, 0.0,
-                   xr_to_pt (xr->width), xr_to_pt (xr->length));
-  xr->y = xr->length;
+    {
+      xr_draw_chart (cs->chart_item, xr->cairo,
+                    0.0,
+                    xr_to_pt (xr->y),
+                    xr_to_pt (xr->width),
+                    xr_to_pt (chart_height));
+    }
+  xr->y += chart_height;
 
   return false;
 }
@@ -1574,13 +1667,23 @@ xr_render_eject (void)
 }
 \f
 static struct xr_render_fsm *
-xr_create_text_renderer (struct xr_driver *xr, const char *text)
+xr_create_text_renderer (struct xr_driver *xr, const struct text_item *item)
 {
-  struct table_item *table_item;
-  struct xr_render_fsm *fsm;
-
-  table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
-  fsm = xr_render_table (xr, table_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;
@@ -1590,21 +1693,10 @@ static struct xr_render_fsm *
 xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
 {
   enum text_item_type type = text_item_get_type (text_item);
-  const char *text = text_item_get_text (text_item);
 
   switch (type)
     {
-    case TEXT_ITEM_TITLE:
-      free (xr->title);
-      xr->title = xstrdup (text);
-      break;
-
-    case TEXT_ITEM_SUBTITLE:
-      free (xr->subtitle);
-      xr->subtitle = xstrdup (text);
-      break;
-
-    case TEXT_ITEM_COMMAND_CLOSE:
+    case TEXT_ITEM_PAGE_TITLE:
       break;
 
     case TEXT_ITEM_BLANK_LINE:
@@ -1618,7 +1710,7 @@ xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
       break;
 
     default:
-      return xr_create_text_renderer (xr, text);
+      return xr_create_text_renderer (xr, text_item);
     }
 
   return NULL;
@@ -1628,13 +1720,11 @@ 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);
-  struct xr_render_fsm *fsm;
-  char *s;
-
-  s = msg_to_string (msg, xr->command_name);
-  fsm = xr_create_text_renderer (xr, s);
+  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;
 }