output: Make it possible to push and pop output engines.
[pspp] / src / output / cairo.c
index 896f3d5fe7cdc2a62fa561f3776d7625b9cbb37d..e7fc7f931ecc2365e421abababad2361825dd249 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+   Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 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
 
    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
@@ -32,6 +32,7 @@
 #include "output/charts/piechart.h"
 #include "output/charts/plot-hist.h"
 #include "output/charts/roc-chart.h"
 #include "output/charts/piechart.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/driver-provider.h"
 #include "output/message-item.h"
 #include "output/charts/scree.h"
 #include "output/driver-provider.h"
 #include "output/message-item.h"
 #include <cairo/cairo-ps.h>
 #include <cairo/cairo-svg.h>
 #include <cairo/cairo.h>
 #include <cairo/cairo-ps.h>
 #include <cairo/cairo-svg.h>
 #include <cairo/cairo.h>
+#include <math.h>
 #include <pango/pango-font.h>
 #include <pango/pango-layout.h>
 #include <pango/pango.h>
 #include <pango/pangocairo.h>
 #include <stdlib.h>
 
 #include <pango/pango-font.h>
 #include <pango/pango-layout.h>
 #include <pango/pango.h>
 #include <pango/pangocairo.h>
 #include <stdlib.h>
 
-#include "gl/error.h"
 #include "gl/intprops.h"
 #include "gl/minmax.h"
 #include "gl/xalloc.h"
 #include "gl/intprops.h"
 #include "gl/minmax.h"
 #include "gl/xalloc.h"
@@ -64,9 +65,8 @@
 #define H TABLE_HORZ
 #define V TABLE_VERT
 
 #define H TABLE_HORZ
 #define V TABLE_VERT
 
-/* Measurements as we present to the rest of PSPP. */
+/* The unit used for internal measurements is inch/(72 * XR_POINT). */
 #define XR_POINT PANGO_SCALE
 #define XR_POINT PANGO_SCALE
-#define XR_INCH (XR_POINT * 72)
 
 /* Conversions to and from points. */
 static double
 
 /* Conversions to and from points. */
 static double
@@ -95,10 +95,8 @@ enum xr_font_type
 /* A font for use with Cairo. */
 struct xr_font
   {
 /* A font for use with Cairo. */
 struct xr_font
   {
-    char *string;
     PangoFontDescription *desc;
     PangoLayout *layout;
     PangoFontDescription *desc;
     PangoLayout *layout;
-    PangoFontMetrics *metrics;
   };
 
 /* An output item whose rendering is in progress. */
   };
 
 /* An output item whose rendering is in progress. */
@@ -119,43 +117,43 @@ struct xr_driver
     struct output_driver driver;
 
     /* User parameters. */
     struct output_driver driver;
 
     /* User parameters. */
-    bool headers;               /* Draw headers at top of page? */
-
     struct xr_font fonts[XR_N_FONTS];
     struct xr_font fonts[XR_N_FONTS];
-    int font_height;            /* In XR units. */
 
     int width;                  /* Page width minus margins. */
     int length;                 /* Page length minus margins and header. */
 
 
     int width;                  /* Page width minus margins. */
     int length;                 /* Page length minus margins and header. */
 
-    int left_margin;            /* Left margin in XR units. */
-    int right_margin;           /* Right margin in XR units. */
-    int top_margin;             /* Top margin in XR units. */
-    int bottom_margin;          /* Bottom margin in XR units. */
+    int left_margin;            /* Left margin in inch/(72 * XR_POINT). */
+    int right_margin;           /* Right margin in inch/(72 * XR_POINT). */
+    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. */
 
 
     int line_gutter;           /* Space around lines. */
     int line_space;            /* Space between lines. */
     int line_width;            /* Width of lines. */
 
+    int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
+
+    struct xr_color bg;    /* Background color */
+    struct xr_color fg;    /* Foreground color */
+
     /* Internal state. */
     struct render_params *params;
     /* Internal state. */
     struct render_params *params;
+    int char_width, char_height;
     char *command_name;
     char *title;
     char *subtitle;
     cairo_t *cairo;
     int page_number;           /* Current page number. */
     char *command_name;
     char *title;
     char *subtitle;
     cairo_t *cairo;
     int page_number;           /* Current page number. */
-    int y;
+    int x, y;
     struct xr_render_fsm *fsm;
     struct xr_render_fsm *fsm;
+    int nest;
   };
 
 static const struct output_driver_class cairo_driver_class;
 
   };
 
 static const struct output_driver_class cairo_driver_class;
 
-static void draw_headers (struct xr_driver *);
 static void xr_driver_destroy_fsm (struct xr_driver *);
 static void xr_driver_run_fsm (struct xr_driver *);
 
 static void xr_driver_destroy_fsm (struct xr_driver *);
 static void xr_driver_run_fsm (struct xr_driver *);
 
-static bool load_font (struct xr_driver *, struct xr_font *);
-static void free_font (struct xr_font *);
-
 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
                           enum render_line_style styles[TABLE_N_AXES][2]);
 static void xr_measure_cell_width (void *, const struct table_cell *,
 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
                           enum render_line_style styles[TABLE_N_AXES][2]);
 static void xr_measure_cell_width (void *, const struct table_cell *,
@@ -165,6 +163,8 @@ static int xr_measure_cell_height (void *, const struct table_cell *,
 static void xr_draw_cell (void *, const struct table_cell *,
                           int bb[TABLE_N_AXES][2],
                           int clip[TABLE_N_AXES][2]);
 static void xr_draw_cell (void *, const struct table_cell *,
                           int bb[TABLE_N_AXES][2],
                           int clip[TABLE_N_AXES][2]);
+static int xr_adjust_break (void *, const struct table_cell *,
+                            int width, int height);
 
 static struct xr_render_fsm *xr_render_output_item (
   struct xr_driver *, const struct output_item *);
 
 static struct xr_render_fsm *xr_render_output_item (
   struct xr_driver *, const struct output_item *);
@@ -185,44 +185,160 @@ opt (struct output_driver *d, struct string_map *options, const char *key,
   return driver_option_get (d, options, key, default_value);
 }
 
   return driver_option_get (d, options, key, default_value);
 }
 
-static struct xr_driver *
-xr_allocate (const char *name, int device_type, struct string_map *o)
+/* Parse color information specified by KEY into {RED,GREEN,BLUE}.
+   Currently, the input string must be of the form "#RRRRGGGGBBBB"
+   Future implementations might allow things like "yellow" and
+   "sky-blue-ultra-brown"
+*/
+void
+parse_color (struct output_driver *d, struct string_map *options,
+            const char *key, const char *default_value,
+            struct xr_color *color)
+{
+  int red, green, blue;
+  char *string = parse_string (opt (d, options, key, default_value));
+
+  if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue))
+    {
+      /* If the parsed option string fails, then try the default value */
+      if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue))
+       {
+         /* ... and if that fails set everything to zero */
+         red = green = blue = 0;
+       }
+    }
+
+  free (string);
+
+  /* Convert 16 bit ints to float */
+  color->red = red / (double) 0xFFFF;
+  color->green = green / (double) 0xFFFF;
+  color->blue = blue / (double) 0xFFFF;
+}
+
+static PangoFontDescription *
+parse_font (struct output_driver *d, struct string_map *options,
+            const char *key, const char *default_value,
+            int default_size)
 {
 {
+  PangoFontDescription *desc;
+  char *string;
+
+  /* Parse KEY as a font description. */
+  string = parse_string (opt (d, options, key, default_value));
+  desc = pango_font_description_from_string (string);
+  if (desc == NULL)
+    {
+      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);
+      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)
+{
+  struct output_driver *d = &xr->driver;
+
+  /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
+  int left_margin, right_margin;
+  int top_margin, bottom_margin;
   int paper_width, paper_length;
   int paper_width, paper_length;
-  struct output_driver *d;
-  struct xr_driver *xr;
+  int font_size;
+  int min_break[TABLE_N_AXES];
 
 
-  xr = xzalloc (sizeof *xr);
-  d = &xr->driver;
-  output_driver_init (d, &cairo_driver_class, name, device_type);
-  xr->font_height = XR_POINT * 10;
-  xr->fonts[XR_FONT_FIXED].string
-    = parse_string (opt (d, o, "fixed-font", "monospace"));
-  xr->fonts[XR_FONT_PROPORTIONAL].string
-    = parse_string (opt (d, o, "prop-font", "serif"));
-  xr->fonts[XR_FONT_EMPHASIS].string
-    = parse_string (opt (d, o, "emph-font", "serif italic"));
-  xr->line_gutter = XR_POINT;
+  /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
+  const double scale = XR_POINT / 1000.;
+
+  int i;
+
+  for (i = 0; i < XR_N_FONTS; i++)
+    {
+      struct xr_font *font = &xr->fonts[i];
+
+      if (font->desc != NULL)
+        pango_font_description_free (font->desc);
+    }
+
+  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->page_number = 0;
 
   xr->line_space = XR_POINT;
   xr->line_width = XR_POINT / 2;
   xr->page_number = 0;
 
-  xr->headers = parse_boolean (opt (d, o, "headers", "true"));
+  parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
+  parse_color (d, o, "foreground-color", "#000000000000", &xr->fg);
 
 
+  /* Get dimensions.  */
   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
-  xr->left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
-  xr->right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
-  xr->top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
-  xr->bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
+  left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
+  right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
+  top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
+  bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
+
+  min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
+  min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
+
+  /* Convert to inch/(XR_POINT * 72). */
+  xr->left_margin = left_margin * scale;
+  xr->right_margin = right_margin * scale;
+  xr->top_margin = top_margin * scale;
+  xr->bottom_margin = bottom_margin * scale;
+  xr->width = (paper_width - left_margin - right_margin) * scale;
+  xr->length = (paper_length - top_margin - bottom_margin) * scale;
+  xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
+  xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
+}
 
 
-  if (xr->headers)
-    xr->top_margin += 3 * xr->font_height;
-  xr->width = paper_width - xr->left_margin - xr->right_margin;
-  xr->length = paper_length - xr->top_margin - xr->bottom_margin;
+static struct xr_driver *
+xr_allocate (const char *name, int device_type, struct string_map *o)
+{
+  struct xr_driver *xr = xzalloc (sizeof *xr);
+  struct output_driver *d = &xr->driver;
+
+  output_driver_init (d, &cairo_driver_class, name, device_type);
+
+  apply_options (xr, o);
 
   return xr;
 }
 
 
   return xr;
 }
 
+static int
+pango_to_xr (int pango)
+{
+  return (XR_POINT != PANGO_SCALE
+          ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
+          : pango);
+}
+
+static int
+xr_to_pango (int xr)
+{
+  return (XR_POINT != PANGO_SCALE
+          ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
+          : xr);
+}
+
 static bool
 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
 {
 static bool
 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
 {
@@ -232,9 +348,21 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *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;
   for (i = 0; i < XR_N_FONTS; i++)
   for (i = 0; i < XR_N_FONTS; i++)
-    if (!load_font (xr, &xr->fonts[i]))
-      return false;
+    {
+      struct xr_font *font = &xr->fonts[i];
+      int char_width, char_height;
+
+      font->layout = pango_cairo_create_layout (cairo);
+      pango_layout_set_font_description (font->layout, font->desc);
+
+      pango_layout_set_text (font->layout, "0", 1);
+      pango_layout_get_size (font->layout, &char_width, &char_height);
+      xr->char_width = MAX (xr->char_width, pango_to_xr (char_width));
+      xr->char_height = MAX (xr->char_height, pango_to_xr (char_height));
+    }
 
   if (xr->params == NULL)
     {
 
   if (xr->params == NULL)
     {
@@ -244,12 +372,13 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
       xr->params->draw_line = xr_draw_line;
       xr->params->measure_cell_width = xr_measure_cell_width;
       xr->params->measure_cell_height = xr_measure_cell_height;
       xr->params->draw_line = xr_draw_line;
       xr->params->measure_cell_width = xr_measure_cell_width;
       xr->params->measure_cell_height = xr_measure_cell_height;
+      xr->params->adjust_break = xr_adjust_break;
       xr->params->draw_cell = xr_draw_cell;
       xr->params->aux = xr;
       xr->params->size[H] = xr->width;
       xr->params->size[V] = xr->length;
       xr->params->draw_cell = xr_draw_cell;
       xr->params->aux = xr;
       xr->params->size[H] = xr->width;
       xr->params->size[V] = xr->length;
-      xr->params->font_size[H] = xr->font_height / 2; /* XXX */
-      xr->params->font_size[V] = xr->font_height;
+      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;
 
       single_width = 2 * xr->line_gutter + xr->line_width;
       double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width;
@@ -259,8 +388,13 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
           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] = single_width;
           xr->params->line_widths[i][RENDER_LINE_DOUBLE] = double_width;
         }
+
+      for (i = 0; i < TABLE_N_AXES; i++)
+        xr->params->min_break[i] = xr->min_break[i];
     }
 
     }
 
+  cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
+
   return true;
 }
 
   return true;
 }
 
@@ -269,17 +403,15 @@ xr_create (const char *file_name, enum settings_output_devices device_type,
            struct string_map *o, enum xr_output_type file_type)
 {
   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
            struct string_map *o, enum xr_output_type file_type)
 {
   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
-  struct output_driver *d;
   struct xr_driver *xr;
   cairo_surface_t *surface;
   cairo_status_t status;
   double width_pt, length_pt;
 
   xr = xr_allocate (file_name, device_type, o);
   struct xr_driver *xr;
   cairo_surface_t *surface;
   cairo_status_t status;
   double width_pt, length_pt;
 
   xr = xr_allocate (file_name, device_type, o);
-  d = &xr->driver;
 
 
-  width_pt = (xr->width + xr->left_margin + xr->right_margin) / 1000.0;
-  length_pt = (xr->length + xr->top_margin + xr->bottom_margin) / 1000.0;
+  width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
+  length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
   if (file_type == XR_PDF)
     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
   else if (file_type == XR_PS)
   if (file_type == XR_PDF)
     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
   else if (file_type == XR_PS)
@@ -292,7 +424,7 @@ xr_create (const char *file_name, enum settings_output_devices device_type,
   status = cairo_surface_status (surface);
   if (status != CAIRO_STATUS_SUCCESS)
     {
   status = cairo_surface_status (surface);
   if (status != CAIRO_STATUS_SUCCESS)
     {
-      error (0, 0, _("error opening output file \"%s\": %s"),
+      msg (ME, _("error opening output file `%s': %s"),
              file_name, cairo_status_to_string (status));
       cairo_surface_destroy (surface);
       goto error;
              file_name, cairo_status_to_string (status));
       cairo_surface_destroy (surface);
       goto error;
@@ -307,29 +439,26 @@ xr_create (const char *file_name, enum settings_output_devices device_type,
   cairo_save (xr->cairo);
   xr_driver_next_page (xr, xr->cairo);
 
   cairo_save (xr->cairo);
   xr_driver_next_page (xr, xr->cairo);
 
-  if (xr->width / (xr->font_height / 2) < MIN_WIDTH)
+  if (xr->width / xr->char_width < MIN_WIDTH)
     {
     {
-      error (0, 0, _("The defined page is not wide enough to hold at least %d "
+      msg (ME, _("The defined page is not wide enough to hold at least %d "
                      "characters in the default font.  In fact, there's only "
                      "room for %d characters."),
              MIN_WIDTH,
                      "characters in the default font.  In fact, there's only "
                      "room for %d characters."),
              MIN_WIDTH,
-             xr->width / (xr->font_height / 2));
+             xr->width / xr->char_width);
       goto error;
     }
 
       goto error;
     }
 
-  if (xr->length / xr->font_height < MIN_LENGTH)
+  if (xr->length / xr->char_height < MIN_LENGTH)
     {
     {
-      error (0, 0, _("The defined page is not long "
-                     "enough to hold margins and headers, plus least %d "
-                     "lines of the default fonts.  In fact, there's only "
+      msg (ME, _("The defined page is not long enough to hold at least %d "
+                     "lines in the default font.  In fact, there's only "
                      "room for %d lines."),
              MIN_LENGTH,
                      "room for %d lines."),
              MIN_LENGTH,
-             xr->length / xr->font_height);
+             xr->length / xr->char_height);
       goto error;
     }
 
       goto error;
     }
 
-  draw_headers (xr);
-
   return &xr->driver;
 
  error:
   return &xr->driver;
 
  error:
@@ -373,7 +502,7 @@ xr_destroy (struct output_driver *driver)
       cairo_surface_finish (cairo_get_target (xr->cairo));
       status = cairo_status (xr->cairo);
       if (status != CAIRO_STATUS_SUCCESS)
       cairo_surface_finish (cairo_get_target (xr->cairo));
       status = cairo_status (xr->cairo);
       if (status != CAIRO_STATUS_SUCCESS)
-        error (0, 0, _("error drawing output for %s driver: %s"),
+        msg (ME, _("error drawing output for %s driver: %s"),
                output_driver_get_name (driver),
                cairo_status_to_string (status));
       cairo_destroy (xr->cairo);
                output_driver_get_name (driver),
                cairo_status_to_string (status));
       cairo_destroy (xr->cairo);
@@ -381,7 +510,15 @@ xr_destroy (struct output_driver *driver)
 
   free (xr->command_name);
   for (i = 0; i < XR_N_FONTS; i++)
 
   free (xr->command_name);
   for (i = 0; i < XR_N_FONTS; i++)
-    free_font (&xr->fonts[i]);
+    {
+      struct xr_font *font = &xr->fonts[i];
+
+      if (font->desc != NULL)
+        pango_font_description_free (font->desc);
+      if (font->layout != NULL)
+        g_object_unref (font->layout);
+    }
+
   free (xr->params);
   free (xr);
 }
   free (xr->params);
   free (xr);
 }
@@ -395,25 +532,35 @@ xr_flush (struct output_driver *driver)
 }
 
 static void
 }
 
 static void
-xr_init_caption_cell (const char *caption, struct table_cell *cell)
+xr_init_caption_cell (const char *caption, struct table_cell *cell,
+                      struct cell_contents *contents)
 {
 {
-  cell->contents = caption;
-  cell->options = TAB_LEFT;
+  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_page *
 xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
   cell->destructor = NULL;
 }
 
 static struct render_page *
 xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
-                      int *caption_heightp)
+                      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 */
 {
   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;
       struct table_cell cell;
-      xr_init_caption_cell (caption, &cell);
-      *caption_heightp = xr_measure_cell_height (xr, &cell, xr->width);
+
+      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;
     }
   else
     *caption_heightp = 0;
@@ -426,6 +573,8 @@ xr_submit (struct output_driver *driver, const struct output_item *output_item)
 {
   struct xr_driver *xr = xr_driver_cast (driver);
 
 {
   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))
     {
   xr_driver_output_item (xr, output_item);
   while (xr_driver_need_new_page (xr))
     {
@@ -437,7 +586,7 @@ xr_submit (struct output_driver *driver, const struct output_item *output_item)
 }
 \f
 /* Functions for rendering a series of output items to a series of Cairo
 }
 \f
 /* Functions for rendering a series of output items to a series of Cairo
-   contexts, with pagination, possibly including headers.
+   contexts, with pagination.
 
    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
    its underlying implementation.
 
    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
    its underlying implementation.
@@ -451,14 +600,21 @@ void
 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
 {
   if (cairo != NULL)
 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
 {
   if (cairo != NULL)
-    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;
 
   xr->page_number++;
   xr->cairo = cairo;
-  xr->y = 0;
-  draw_headers (xr);
+  xr->x = xr->y = 0;
   xr_driver_run_fsm (xr);
 }
 
   xr_driver_run_fsm (xr);
 }
 
@@ -483,8 +639,7 @@ xr_driver_need_new_page (const struct xr_driver *xr)
   return xr->fsm != NULL;
 }
 
   return xr->fsm != NULL;
 }
 
-/* Returns true if the current page doesn't have any content yet (besides
-   headers, if enabled). */
+/* Returns true if the current page doesn't have any content yet. */
 bool
 xr_driver_is_page_blank (const struct xr_driver *xr)
 {
 bool
 xr_driver_is_page_blank (const struct xr_driver *xr)
 {
@@ -511,14 +666,26 @@ xr_driver_run_fsm (struct xr_driver *xr)
 static void
 xr_layout_cell (struct xr_driver *, const struct table_cell *,
                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
 static void
 xr_layout_cell (struct xr_driver *, const struct table_cell *,
                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
-                PangoWrapMode, int *width, int *height);
+                int *width, int *height, int *brk);
 
 static void
 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
 {
   cairo_new_path (xr->cairo);
 
 static void
 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1)
 {
   cairo_new_path (xr->cairo);
-  cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y));
-  cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y));
+  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);
+}
+
+static void UNUSED
+dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
+{
+  cairo_new_path (xr->cairo);
+  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));
+  cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y));
+  cairo_close_path (xr->cairo);
   cairo_stroke (xr->cairo);
 }
 
   cairo_stroke (xr->cairo);
 }
 
@@ -687,10 +854,10 @@ xr_measure_cell_width (void *xr_, const struct table_cell *cell,
   bb[V][0] = 0;
   bb[V][1] = INT_MAX;
   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
   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, PANGO_WRAP_WORD, max_width, &h);
+  xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
 
   bb[H][1] = 1;
 
   bb[H][1] = 1;
-  xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, min_width, &h);
+  xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
 }
 
 static int
 }
 
 static int
@@ -706,7 +873,7 @@ xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
   bb[V][0] = 0;
   bb[V][1] = INT_MAX;
   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
   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, PANGO_WRAP_WORD, &w, &h);
+  xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
   return h;
 }
 
   return h;
 }
 
@@ -715,182 +882,315 @@ xr_draw_cell (void *xr_, const struct table_cell *cell,
               int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
 {
   struct xr_driver *xr = xr_;
               int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
 {
   struct xr_driver *xr = xr_;
-  int w, h;
+  int w, h, brk;
 
 
-  xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
+  xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
 }
 }
-\f
-/* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
-   and with the given cell OPTIONS for XR. */
+
 static int
 static int
-draw_text (struct xr_driver *xr, const char *string, int x, int y,
-           int max_width, unsigned int options)
+xr_adjust_break (void *xr_, const struct table_cell *cell,
+                 int width, int height)
 {
 {
-  struct table_cell cell;
+  struct xr_driver *xr = xr_;
   int bb[TABLE_N_AXES][2];
   int bb[TABLE_N_AXES][2];
-  int w, h;
+  int clip[TABLE_N_AXES][2];
+  int w, h, brk;
 
 
-  cell.contents = string;
-  cell.options = options;
-  bb[H][0] = x;
-  bb[V][0] = y - xr->y;
-  bb[H][1] = x + max_width;
-  bb[V][1] = xr->font_height - xr->y;
-  xr_layout_cell (xr, &cell, bb, bb, PANGO_WRAP_WORD_CHAR, &w, &h);
-  return w;
-}
+  if (xr_measure_cell_height (xr_, cell, width) < height)
+    return -1;
 
 
-/* Writes LEFT left-justified and RIGHT right-justified within
-   (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
-static void
-draw_header_line (struct xr_driver *xr, const char *left, const char *right,
-                  int x0, int x1, int y)
-{
-  int right_width = 0;
-  if (right != NULL)
-    right_width = (draw_text (xr, right, x0, y, x1 - x0, TAB_RIGHT)
-                   + xr->font_height / 2);
-  if (left != NULL)
-    draw_text (xr, left, x0, y, x1 - x0 - right_width, TAB_LEFT);
+  bb[H][0] = 0;
+  bb[H][1] = width;
+  bb[V][0] = 0;
+  bb[V][1] = height;
+  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;
 }
 }
-
-/* Draw top of page headers for XR. */
+\f
 static void
 static void
-draw_headers (struct xr_driver *xr)
+xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
 {
 {
-  char *r1, *r2;
-  int x0, x1;
-  int y;
-
-  if (!xr->headers || xr->cairo == NULL)
-    return;
-
-  y = -3 * xr->font_height;
-  x0 = xr->font_height / 2;
-  x1 = xr->width - xr->font_height / 2;
-
-  /* Draw box. */
-  cairo_rectangle (xr->cairo, 0, xr_to_pt (y), xr_to_pt (xr->width),
-                   xr_to_pt (2 * (xr->font_height
-                                  + xr->line_width + xr->line_gutter)));
-  cairo_save (xr->cairo);
-  cairo_set_source_rgb (xr->cairo, 0.9, 0.9, 0.9);
-  cairo_fill_preserve (xr->cairo);
-  cairo_restore (xr->cairo);
-  cairo_stroke (xr->cairo);
-
-  y += xr->line_width + xr->line_gutter;
-
-  r1 = xasprintf (_("%s - Page %d"), get_start_date (), xr->page_number);
-  r2 = xasprintf ("%s - %s", version, host_system);
-
-  draw_header_line (xr, xr->title, r1, x0, x1, y);
-  y += xr->font_height;
-
-  draw_header_line (xr, xr->subtitle, r2, x0, x1, y);
+  if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
+    {
+      double x0 = xr_to_pt (clip[H][0] + xr->x);
+      double y0 = xr_to_pt (clip[V][0] + xr->y);
+      double x1 = xr_to_pt (clip[H][1] + xr->x);
+      double y1 = xr_to_pt (clip[V][1] + xr->y);
 
 
-  free (r1);
-  free (r2);
+      cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
+      cairo_clip (xr->cairo);
+    }
 }
 }
-\f
-static void
-xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
-                int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
-                PangoWrapMode wrap, int *width, int *height)
+
+static int
+xr_layout_cell_text (struct xr_driver *xr,
+                     const struct cell_contents *contents,
+                     int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
+                     int y, int *widthp, int *brk)
 {
 {
+  unsigned int options = contents->options;
   struct xr_font *font;
   struct xr_font *font;
+  int w, h;
 
 
-  font = (cell->options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
-          : cell->options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
+  font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
+          : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS]
           : &xr->fonts[XR_FONT_PROPORTIONAL]);
 
           : &xr->fonts[XR_FONT_PROPORTIONAL]);
 
-  pango_layout_set_text (font->layout, cell->contents, -1);
+  pango_layout_set_text (font->layout, contents->text, -1);
 
   pango_layout_set_alignment (
     font->layout,
 
   pango_layout_set_alignment (
     font->layout,
-    ((cell->options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
-     : (cell->options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
+    ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT
+     : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT
      : PANGO_ALIGN_CENTER));
      : PANGO_ALIGN_CENTER));
-  pango_layout_set_width (font->layout,
-                          bb[H][1] == INT_MAX ? -1 : bb[H][1] - bb[H][0]);
-  pango_layout_set_wrap (font->layout, wrap);
+  pango_layout_set_width (
+    font->layout,
+    bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0]));
+  pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
 
   if (clip[H][0] != clip[H][1])
     {
       cairo_save (xr->cairo);
 
   if (clip[H][0] != clip[H][1])
     {
       cairo_save (xr->cairo);
+      xr_clip (xr, clip);
+      cairo_translate (xr->cairo,
+                       xr_to_pt (bb[H][0] + xr->x),
+                       xr_to_pt (y + xr->y));
+      pango_cairo_show_layout (xr->cairo, font->layout);
 
 
-      if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
+      /* If enabled, this draws a blue rectangle around the extents of each
+         line of text, which can be rather useful for debugging layout
+         issues. */
+      if (0)
         {
         {
-          double x0 = xr_to_pt (clip[H][0]);
-          double y0 = xr_to_pt (clip[V][0] + xr->y);
-          double x1 = xr_to_pt (clip[H][1]);
-          double y1 = xr_to_pt (clip[V][1] + xr->y);
-
-          cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
-          cairo_clip (xr->cairo);
+          PangoLayoutIter *iter;
+          iter = pango_layout_get_iter (font->layout);
+          do
+            {
+              PangoRectangle extents;
+
+              pango_layout_iter_get_line_extents (iter, &extents, NULL);
+              cairo_save (xr->cairo);
+              cairo_set_source_rgb (xr->cairo, 1, 0, 0);
+              dump_rectangle (xr,
+                              pango_to_xr (extents.x) - xr->x,
+                              pango_to_xr (extents.y) - xr->y,
+                              pango_to_xr (extents.x + extents.width) - xr->x,
+                              pango_to_xr (extents.y + extents.height) - xr->y);
+              cairo_restore (xr->cairo);
+            }
+          while (pango_layout_iter_next_line (iter));
+          pango_layout_iter_free (iter);
         }
 
         }
 
-      cairo_translate (xr->cairo,
-                       xr_to_pt (bb[H][0]),
-                       xr_to_pt (bb[V][0] + xr->y));
-      pango_cairo_show_layout (xr->cairo, font->layout);
       cairo_restore (xr->cairo);
     }
 
       cairo_restore (xr->cairo);
     }
 
-  if (width != NULL || height != NULL)
+  pango_layout_get_size (font->layout, &w, &h);
+  w = pango_to_xr (w);
+  h = pango_to_xr (h);
+  if (w > *widthp)
+    *widthp = w;
+  if (y + h >= bb[V][1])
     {
     {
-      int w, h;
+      PangoLayoutIter *iter;
+      int best UNUSED = 0;
 
 
-      pango_layout_get_size (font->layout, &w, &h);
-      if (width != NULL)
-        *width = w;
-      if (height != NULL)
-        *height = h;
+      /* Choose a breakpoint between lines instead of in the middle of one. */
+      iter = pango_layout_get_iter (font->layout);
+      do
+        {
+          PangoRectangle extents;
+          int y0, y1;
+          int bottom;
+
+          pango_layout_iter_get_line_extents (iter, NULL, &extents);
+          pango_layout_iter_get_line_yrange (iter, &y0, &y1);
+          extents.x = pango_to_xr (extents.x);
+          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;
+          if (bottom < bb[V][1])
+            {
+              if (brk && clip[H][0] != clip[H][1])
+                best = bottom;
+              *brk = bottom;
+            }
+          else
+            break;
+        }
+      while (pango_layout_iter_next_line (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);
+            }
+        }
     }
     }
+  return y + h;
 }
 }
-\f
-/* Attempts to load FONT, initializing its other members based on
-   its 'string' member and the information in DRIVER.  Returns true
-   if successful, otherwise false. */
-static bool
-load_font (struct xr_driver *xr, struct xr_font *font)
+
+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)
 {
 {
-  PangoContext *context;
-  PangoLanguage *language;
+  const struct table *table = contents->table;
+  int single_width, double_width;
+  struct render_params params;
+  struct render_page *page;
+  int r[TABLE_N_AXES][2];
+  int width, height;
+  int i;
 
 
-  font->desc = pango_font_description_from_string (font->string);
-  if (font->desc == 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++)
     {
     {
-      error (0, 0, _("\"%s\": bad font specification"), font->string);
-      return false;
+      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;
     }
     }
-  pango_font_description_set_absolute_size (font->desc, xr->font_height);
 
 
-  font->layout = pango_cairo_create_layout (xr->cairo);
-  pango_layout_set_font_description (font->layout, font->desc);
+  xr->nest++;
+  page = render_page_create (&params, table);
+  width = render_page_get_size (page, H);
+  height = render_page_get_size (page, V);
+  if (bb[V][0] + height >= bb[V][1])
+    *brk = bb[V][0] + render_page_get_best_breakpoint (page, bb[V][1] - bb[V][0]);
 
 
-  language = pango_language_get_default ();
-  context = pango_layout_get_context (font->layout);
-  font->metrics = pango_context_get_metrics (context, font->desc, language);
+  /* 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];
+    }
 
 
-  return true;
+  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_page_draw_region (page, 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_page_unref (page);
+  xr->nest--;
+
+  if (width > *widthp)
+    *widthp = width;
+  return bb[V][0] + height;
 }
 
 }
 
-/* Frees FONT. */
 static void
 static void
-free_font (struct xr_font *font)
+xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
+                int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
+                int *width, int *height, int *brk)
 {
 {
-  free (font->string);
-  if (font->desc != NULL)
-    pango_font_description_free (font->desc);
-  pango_font_metrics_unref (font->metrics);
-  if (font->layout != NULL)
-    g_object_unref (font->layout);
+  int bb[TABLE_N_AXES][2];
+  size_t i;
+
+  *width = 0;
+  *height = 0;
+  if (brk)
+    *brk = 0;
+
+  memcpy (bb, bb_, sizeof bb);
+
+  /* If enabled, draws a blue rectangle around the cell extents, which can be
+     useful for debugging layout. */
+  if (0)
+    {
+      if (clip[H][0] != clip[H][1])
+        {
+          int offset = (xr->nest) * XR_POINT;
+
+          cairo_save (xr->cairo);
+          cairo_set_source_rgb (xr->cairo, 0, 0, 1);
+          dump_rectangle (xr,
+                          bb[H][0] + offset, bb[V][0] + offset,
+                          bb[H][1] - offset, bb[V][1] - offset);
+          cairo_restore (xr->cairo);
+        }
+    }
+
+  for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
+    {
+      const struct cell_contents *contents = &cell->contents[i];
+
+      if (brk)
+        *brk = bb[V][0];
+      if (i > 0)
+        {
+          bb[V][0] += xr->char_height / 2;
+          if (bb[V][0] >= bb[V][1])
+            break;
+          if (brk)
+            *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);
+    }
+  *height = bb[V][0] - bb_[V][0];
 }
 
 }
 
-struct output_driver_factory pdf_driver_factory = { "pdf", xr_pdf_create };
-struct output_driver_factory ps_driver_factory = { "ps", xr_ps_create };
-struct output_driver_factory svg_driver_factory = { "svg", xr_svg_create };
+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 };
+struct output_driver_factory ps_driver_factory =
+  { "ps", "pspp.ps", xr_ps_create };
+struct output_driver_factory svg_driver_factory =
+  { "svg", "pspp.svg", xr_svg_create };
 
 static const struct output_driver_class cairo_driver_class =
 {
 
 static const struct output_driver_class cairo_driver_class =
 {
@@ -904,18 +1204,20 @@ static const struct output_driver_class cairo_driver_class =
 
 struct xr_rendering
   {
 
 struct xr_rendering
   {
+    struct output_item *item;
+
     /* Table items. */
     struct render_page *page;
     struct xr_driver *xr;
     /* Table items. */
     struct render_page *page;
     struct xr_driver *xr;
+    int title_width;
     int title_height;
     int title_height;
-
-    /* Chart items. */
-    struct chart_item *chart;
   };
 
 #define CHART_WIDTH 500
 #define CHART_HEIGHT 375
 
   };
 
 #define CHART_WIDTH 500
 #define CHART_HEIGHT 375
 
+
+
 struct xr_driver *
 xr_driver_create (cairo_t *cairo, struct string_map *options)
 {
 struct xr_driver *
 xr_driver_create (cairo_t *cairo, struct string_map *options)
 {
@@ -946,13 +1248,20 @@ xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
   struct table_item *table_item;
   struct xr_rendering *r;
 
   struct table_item *table_item;
   struct xr_rendering *r;
 
-  table_item = table_item_create (table_from_string (0, text), NULL);
+  table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
   r = xr_rendering_create (xr, &table_item->output_item, cr);
   table_item_unref (table_item);
 
   return r;
 }
 
   r = xr_rendering_create (xr, &table_item->output_item, cr);
   table_item_unref (table_item);
 
   return r;
 }
 
+void 
+xr_rendering_apply_options (struct xr_rendering *xr, struct string_map *o)
+{
+  if (is_table_item (xr->item))
+    apply_options (xr->xr, o);
+}
+
 struct xr_rendering *
 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
                      cairo_t *cr)
 struct xr_rendering *
 xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
                      cairo_t *cr)
@@ -973,15 +1282,16 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
   else if (is_table_item (item))
     {
       r = xzalloc (sizeof *r);
   else if (is_table_item (item))
     {
       r = xzalloc (sizeof *r);
+      r->item = output_item_ref (item);
       r->xr = xr;
       xr_set_cairo (xr, cr);
       r->page = xr_render_table_item (xr, to_table_item (item),
       r->xr = xr;
       xr_set_cairo (xr, cr);
       r->page = xr_render_table_item (xr, to_table_item (item),
-                                      &r->title_height);
+                                      &r->title_width, &r->title_height);
     }
   else if (is_chart_item (item))
     {
       r = xzalloc (sizeof *r);
     }
   else if (is_chart_item (item))
     {
       r = xzalloc (sizeof *r);
-      r->chart = to_chart_item (output_item_ref (item));
+      r->item = output_item_ref (item);
     }
 
   return r;
     }
 
   return r;
@@ -990,10 +1300,12 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
 void
 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
 {
 void
 xr_rendering_measure (struct xr_rendering *r, int *w, int *h)
 {
-  if (r->chart == NULL)
+  if (is_table_item (r->item))
     {
     {
-      *w = render_page_get_size (r->page, H) / 1024;
-      *h = (render_page_get_size (r->page, V) + r->title_height) / 1024;
+      int w0 = render_page_get_size (r->page, H);
+      int w1 = r->title_width;
+      *w = MAX (w0, w1) / XR_POINT;
+      *h = (render_page_get_size (r->page, V) + r->title_height) / XR_POINT;
     }
   else
     {
     }
   else
     {
@@ -1002,26 +1314,39 @@ 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. */
 void
 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
                    int x, int y, int w, int h)
 {
 /* Draws onto CR at least the region of R that is enclosed in (X,Y)-(X+W,Y+H),
    and possibly some additional parts. */
 void
 xr_rendering_draw (struct xr_rendering *r, cairo_t *cr,
                    int x, int y, int w, int h)
 {
-  if (r->chart == NULL)
+  if (is_table_item (r->item))
     {
       struct xr_driver *xr = r->xr;
 
       xr_set_cairo (xr, cr);
     {
       struct xr_driver *xr = r->xr;
 
       xr_set_cairo (xr, cr);
-      xr->y = 0;
-      render_page_draw_region (r->page,
-                               x * 1024, y * 1024, w * 1024, h * 1024);
+
+      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);
+        }
+
+      xr->y = r->title_height;
+      render_page_draw_region (r->page, 
+                              x * XR_POINT, (y * XR_POINT) - r->title_height,
+                               w * XR_POINT, h * XR_POINT);
     }
   else
     }
   else
-    xr_draw_chart (r->chart, cr, 0, 0, CHART_WIDTH, CHART_HEIGHT);
+    xr_draw_chart (to_chart_item (r->item), cr,
+                   0, 0, CHART_WIDTH, CHART_HEIGHT);
 }
 
 }
 
-void
+static void
 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
                double x, double y, double width, double height)
 {
 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
                double x, double y, double width, double height)
 {
@@ -1043,6 +1368,8 @@ xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
     xrchart_draw_roc (chart_item, cr, &geom);
   else if (is_scree (chart_item))
     xrchart_draw_scree (chart_item, cr, &geom);
     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
     NOT_REACHED ();
   xrchart_geometry_free (cr, &geom);
   else
     NOT_REACHED ();
   xrchart_geometry_free (cr, &geom);
@@ -1052,7 +1379,10 @@ xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
 
 char *
 xr_draw_png_chart (const struct chart_item *item,
 
 char *
 xr_draw_png_chart (const struct chart_item *item,
-                   const char *file_name_template, int number)
+                   const char *file_name_template, int number,
+                  const struct xr_color *fg,
+                  const struct xr_color *bg
+                  )
 {
   const int width = 640;
   const int length = 480;
 {
   const int width = 640;
   const int length = 480;
@@ -1073,19 +1403,16 @@ xr_draw_png_chart (const struct chart_item *item,
   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
   cr = cairo_create (surface);
 
   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
   cr = cairo_create (surface);
 
-  cairo_save (cr);
-  cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
-  cairo_rectangle (cr, 0, 0, width, length);
-  cairo_fill (cr);
-  cairo_restore (cr);
+  cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue);
+  cairo_paint (cr);
 
 
-  cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+  cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue);
 
   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
 
   status = cairo_surface_write_to_png (surface, file_name);
   if (status != CAIRO_STATUS_SUCCESS)
 
   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
 
   status = cairo_surface_write_to_png (surface, file_name);
   if (status != CAIRO_STATUS_SUCCESS)
-    error (0, 0, _("error writing output file \"%s\": %s"),
+    msg (ME, _("error writing output file `%s': %s"),
            file_name, cairo_status_to_string (status));
 
   cairo_destroy (cr);
            file_name, cairo_status_to_string (status));
 
   cairo_destroy (cr);
@@ -1136,18 +1463,9 @@ xr_table_render (struct xr_render_fsm *fsm, struct xr_driver *xr)
       if (ts->caption_height)
         {
           if (xr->cairo)
       if (ts->caption_height)
         {
           if (xr->cairo)
-            {
-              struct table_cell cell;
-              int bb[TABLE_N_AXES][2];
-
-              xr_init_caption_cell (table_item_get_caption (ts->table_item),
-                                    &cell);
-              bb[H][0] = 0;
-              bb[H][1] = xr->width;
-              bb[V][0] = 0;
-              bb[V][1] = ts->caption_height;
-              xr_draw_cell (xr, &cell, bb, bb);
-            }
+            xr_draw_title (xr, table_item_get_caption (ts->table_item),
+                           xr->width, ts->caption_height);
+
           xr->y += ts->caption_height;
           ts->caption_height = 0;
         }
           xr->y += ts->caption_height;
           ts->caption_height = 0;
         }
@@ -1175,6 +1493,7 @@ xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
 {
   struct xr_table_state *ts;
   struct render_page *page;
 {
   struct xr_table_state *ts;
   struct render_page *page;
+  int caption_width;
 
   ts = xmalloc (sizeof *ts);
   ts->fsm.render = xr_table_render;
 
   ts = xmalloc (sizeof *ts);
   ts->fsm.render = xr_table_render;
@@ -1182,9 +1501,10 @@ xr_render_table (struct xr_driver *xr, const struct table_item *table_item)
   ts->table_item = table_item_ref (table_item);
 
   if (xr->y > 0)
   ts->table_item = table_item_ref (table_item);
 
   if (xr->y > 0)
-    xr->y += xr->font_height;
+    xr->y += xr->char_height;
 
 
-  page = xr_render_table_item (xr, table_item, &ts->caption_height);
+  page = xr_render_table_item (xr, table_item,
+                               &caption_width, &ts->caption_height);
   xr->params->size[V] = xr->length - ts->caption_height;
 
   render_break_init (&ts->x_break, page, H);
   xr->params->size[V] = xr->length - ts->caption_height;
 
   render_break_init (&ts->x_break, page, H);
@@ -1285,13 +1605,11 @@ xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
     case TEXT_ITEM_TITLE:
       free (xr->title);
       xr->title = xstrdup (text);
     case TEXT_ITEM_TITLE:
       free (xr->title);
       xr->title = xstrdup (text);
-      draw_headers (xr);
       break;
 
     case TEXT_ITEM_SUBTITLE:
       free (xr->subtitle);
       xr->subtitle = xstrdup (text);
       break;
 
     case TEXT_ITEM_SUBTITLE:
       free (xr->subtitle);
       xr->subtitle = xstrdup (text);
-      draw_headers (xr);
       break;
 
     case TEXT_ITEM_COMMAND_CLOSE:
       break;
 
     case TEXT_ITEM_COMMAND_CLOSE:
@@ -1299,7 +1617,7 @@ xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
 
     case TEXT_ITEM_BLANK_LINE:
       if (xr->y > 0)
 
     case TEXT_ITEM_BLANK_LINE:
       if (xr->y > 0)
-        xr->y += xr->font_height;
+        xr->y += xr->char_height;
       break;
 
     case TEXT_ITEM_EJECT_PAGE:
       break;
 
     case TEXT_ITEM_EJECT_PAGE: