Corrected bug in chart rendering which caused the ordinate label to never get displayed
[pspp] / src / output / cairo.c
index a415e5bd8701626a5584038b4e5bcd9ee2345048..6229311aa75003988fdc2ae39bcec48a253c370a 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 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"
@@ -95,10 +96,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,10 +118,7 @@ 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. */
@@ -136,10 +132,12 @@ struct xr_driver
     int line_space;            /* Space between lines. */
     int line_width;            /* Width of lines. */
 
     int line_space;            /* Space between lines. */
     int line_width;            /* Width of lines. */
 
-    enum xr_output_type file_type; /* Type of output file. */
+    double bg_red, bg_green, bg_blue; /* Background color */
+    double fg_red, fg_green, fg_blue; /* 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;
     char *command_name;
     char *title;
     char *subtitle;
@@ -151,13 +149,9 @@ struct xr_driver
 
 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 *,
@@ -187,43 +181,164 @@ 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"
+*/
+static void
+parse_color (struct output_driver *d, struct string_map *options,
+             const char *key, const char *default_value,
+             double *dred, double *dgreen, double *dblue)
 {
 {
-  struct output_driver *d;
-  struct xr_driver *xr;
+  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;
+       }
+    }
+
+  /* Convert 16 bit ints to float */
+  *dred = red / (double) 0xFFFF;
+  *dgreen = green / (double) 0xFFFF;
+  *dblue = blue / (double) 0xFFFF;
+}
+
+static PangoFontDescription *
+parse_font (struct output_driver *d, struct string_map *options,
+            const char *key, const char *default_value,
+            int default_points)
+{
+  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)
+    {
+      error (0, 0, _("`%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_POINTS. */
+  if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
+    pango_font_description_set_size (desc,
+                                     default_points / 1000.0 * PANGO_SCALE);
+
+  return desc;
+}
+
+
+static void
+apply_options (struct xr_driver *xr, struct string_map *o)
+{
+  struct output_driver *d = &xr->driver;
+
+  int paper_width, paper_length;
+
+  int font_points = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
+  xr->fonts[XR_FONT_FIXED].desc = parse_font (d, o, "fixed-font", "monospace",
+                                              font_points);
+  xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font (d, o, "prop-font",
+                                                     "serif", font_points);
+  xr->fonts[XR_FONT_EMPHASIS].desc = parse_font (d, o, "emph-font",
+                                                 "serif italic", font_points);
 
 
-  xr = xzalloc (sizeof *xr);
-  d = &xr->driver;
-  output_driver_init (d, &cairo_driver_class, name, device_type);
-  xr->headers = true;
-  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;
   xr->line_space = XR_POINT;
   xr->line_width = XR_POINT / 2;
   xr->line_gutter = XR_POINT;
   xr->line_space = XR_POINT;
   xr->line_width = XR_POINT / 2;
-  xr->page_number = 1;
+  xr->page_number = 0;
+
+  parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg_red, &xr->bg_green, &xr->bg_blue);
+  parse_color (d, o, "foreground-color", "#000000000000", &xr->fg_red, &xr->fg_green, &xr->fg_blue);
+
+  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"));
+
+  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 bool
+xr_is_72dpi (cairo_t *cr)
+{
+  cairo_surface_type_t type;
+  cairo_surface_t *surface;
+
+  surface = cairo_get_target (cr);
+  type = cairo_surface_get_type (surface);
+  return type == CAIRO_SURFACE_TYPE_PDF || type == CAIRO_SURFACE_TYPE_PS;
+}
+
 static bool
 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
 {
 static bool
 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
 {
+  PangoContext *context;
+  PangoFontMap *map;
   int i;
 
   xr->cairo = cairo;
 
   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
 
   int i;
 
   xr->cairo = cairo;
 
   cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
 
+  map = pango_cairo_font_map_get_default ();
+  context = pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (map));
+  if (xr_is_72dpi (cairo))
+    {
+      /* Pango seems to always scale fonts according to the DPI specified
+         in the font map, even if the surface has a real DPI.  The default
+         DPI is 96, so on a 72 DPI device fonts end up being 96/72 = 133%
+         of their desired size.  We deal with this by fixing the resolution
+         here.  Presumably there is a better solution, but what? */
+      pango_cairo_context_set_resolution (context, 72.0);
+    }
+
+  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_layout_new (context);
+      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, char_width);
+      xr->char_height = MAX (xr->char_height, char_height);
+    }
+
+  g_object_unref (G_OBJECT (context));
 
   if (xr->params == NULL)
     {
 
   if (xr->params == NULL)
     {
@@ -237,8 +352,8 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
       xr->params->aux = xr;
       xr->params->size[H] = xr->width;
       xr->params->size[V] = xr->length;
       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;
@@ -250,6 +365,8 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
         }
     }
 
         }
     }
 
+  cairo_set_source_rgb (xr->cairo, xr->fg_red, xr->fg_green, xr->fg_blue);
+
   return true;
 }
 
   return true;
 }
 
@@ -263,33 +380,17 @@ xr_create (const char *file_name, enum settings_output_devices device_type,
   cairo_surface_t *surface;
   cairo_status_t status;
   double width_pt, length_pt;
   cairo_surface_t *surface;
   cairo_status_t status;
   double width_pt, length_pt;
-  int paper_width, paper_length;
 
   xr = xr_allocate (file_name, device_type, o);
   d = &xr->driver;
 
 
   xr = xr_allocate (file_name, device_type, o);
   d = &xr->driver;
 
-  xr->headers = parse_boolean (opt (d, o, "headers", "true"));
-
-  xr->file_type = file_type;
-
-  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"));
-
-  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;
-
-  width_pt = paper_width / 1000.0;
-  length_pt = paper_length / 1000.0;
-  if (xr->file_type == XR_PDF)
+  width_pt = (xr->width + xr->left_margin + xr->right_margin) / 1000.0;
+  length_pt = (xr->length + xr->top_margin + xr->bottom_margin) / 1000.0;
+  if (file_type == XR_PDF)
     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
     surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
-  else if (xr->file_type == XR_PS)
+  else if (file_type == XR_PS)
     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
     surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
-  else if (xr->file_type == XR_SVG)
+  else if (file_type == XR_SVG)
     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
   else
     NOT_REACHED ();
     surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
   else
     NOT_REACHED ();
@@ -297,7 +398,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"),
+      error (0, 0, _("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;
@@ -306,36 +407,32 @@ xr_create (const char *file_name, enum settings_output_devices device_type,
   xr->cairo = cairo_create (surface);
   cairo_surface_destroy (surface);
 
   xr->cairo = cairo_create (surface);
   cairo_surface_destroy (surface);
 
-  cairo_translate (xr->cairo,
-                   xr_to_pt (xr->left_margin),
-                   xr_to_pt (xr->top_margin));
-
   if (!xr_set_cairo (xr, xr->cairo))
     goto error;
 
   if (!xr_set_cairo (xr, xr->cairo))
     goto error;
 
-  if (xr->width / (xr->font_height / 2) < MIN_WIDTH)
+  cairo_save (xr->cairo);
+  xr_driver_next_page (xr, xr->cairo);
+
+  if (xr->width / xr->char_width < MIN_WIDTH)
     {
       error (0, 0, _("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,
     {
       error (0, 0, _("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,
-             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 "
+      error (0, 0, _("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:
@@ -387,7 +484,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);
 }
@@ -410,16 +515,21 @@ xr_init_caption_cell (const char *caption, struct table_cell *cell)
 
 static struct render_page *
 xr_render_table_item (struct xr_driver *xr, const struct table_item *item,
 
 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 */
+      int min_width, max_width;
       struct table_cell cell;
       struct table_cell cell;
+
       xr_init_caption_cell (caption, &cell);
       xr_init_caption_cell (caption, &cell);
-      *caption_heightp = xr_measure_cell_height (xr, &cell, xr->width);
+
+      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;
@@ -435,13 +545,15 @@ xr_submit (struct output_driver *driver, const struct output_item *output_item)
   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))
     {
+      cairo_restore (xr->cairo);
       cairo_show_page (xr->cairo);
       cairo_show_page (xr->cairo);
+      cairo_save (xr->cairo);
       xr_driver_next_page (xr, xr->cairo);
     }
 }
 \f
 /* Functions for rendering a series of output items to a series of Cairo
       xr_driver_next_page (xr, xr->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.
@@ -454,10 +566,22 @@ xr_submit (struct output_driver *driver, const struct output_item *output_item)
 void
 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
 {
 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));
+    }
+
   xr->page_number++;
   xr->cairo = cairo;
   xr->y = 0;
   xr->page_number++;
   xr->cairo = cairo;
   xr->y = 0;
-  draw_headers (xr);
   xr_driver_run_fsm (xr);
 }
 
   xr_driver_run_fsm (xr);
 }
 
@@ -482,8 +606,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)
 {
@@ -719,79 +842,6 @@ xr_draw_cell (void *xr_, const struct table_cell *cell,
   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
 }
 \f
   xr_layout_cell (xr, cell, bb, clip, PANGO_WRAP_WORD, &w, &h);
 }
 \f
-/* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
-   and with the given cell OPTIONS for XR. */
-static int
-draw_text (struct xr_driver *xr, const char *string, int x, int y,
-           int max_width, unsigned int options)
-{
-  struct table_cell cell;
-  int bb[TABLE_N_AXES][2];
-  int w, h;
-
-  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;
-}
-
-/* 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);
-}
-
-/* Draw top of page headers for XR. */
-static void
-draw_headers (struct xr_driver *xr)
-{
-  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);
-
-  free (r1);
-  free (r2);
-}
-\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],
 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],
@@ -847,46 +897,22 @@ xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
         *height = h;
     }
 }
         *height = 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)
-{
-  PangoContext *context;
-  PangoLanguage *language;
 
 
-  font->desc = pango_font_description_from_string (font->string);
-  if (font->desc == NULL)
-    {
-      error (0, 0, _("\"%s\": bad font specification"), font->string);
-      return false;
-    }
-  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);
-
-  language = pango_language_get_default ();
-  context = pango_layout_get_context (font->layout);
-  font->metrics = pango_context_get_metrics (context, font->desc, language);
-
-  return true;
-}
-
-/* Frees FONT. */
 static void
 static void
-free_font (struct xr_font *font)
+xr_draw_title (struct xr_driver *xr, const char *title,
+               int title_width, int title_height)
 {
 {
-  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);
-}
+  struct table_cell cell;
+  int bb[TABLE_N_AXES][2];
 
 
+  xr_init_caption_cell (title, &cell);
+  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", 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 };
 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 };
@@ -903,24 +929,24 @@ 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 = xr_allocate ("cairo", 0, options);
 struct xr_driver *
 xr_driver_create (cairo_t *cairo, struct string_map *options)
 {
   struct xr_driver *xr = xr_allocate ("cairo", 0, options);
-  xr->width = INT_MAX / 8;
-  xr->length = INT_MAX / 8;
   if (!xr_set_cairo (xr, cairo))
     {
       output_driver_destroy (&xr->driver);
   if (!xr_set_cairo (xr, cairo))
     {
       output_driver_destroy (&xr->driver);
@@ -929,19 +955,38 @@ xr_driver_create (cairo_t *cairo, struct string_map *options)
   return xr;
 }
 
   return xr;
 }
 
+/* Destroy XR, which should have been created with xr_driver_create().  Any
+   cairo_t added to XR is not destroyed, because it is owned by the client. */
+void
+xr_driver_destroy (struct xr_driver *xr)
+{
+  if (xr != NULL)
+    {
+      xr->cairo = NULL;
+      output_driver_destroy (&xr->driver);
+    }
+}
+
 static struct xr_rendering *
 xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr)
 {
   struct table_item *table_item;
   struct xr_rendering *r;
 
 static struct xr_rendering *
 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 (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)
@@ -962,15 +1007,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;
@@ -979,9 +1025,11 @@ 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;
+      int w0 = render_page_get_size (r->page, H);
+      int w1 = r->title_width;
+      *w = MAX (w0, w1) / 1024;
       *h = (render_page_get_size (r->page, V) + r->title_height) / 1024;
     }
   else
       *h = (render_page_get_size (r->page, V) + r->title_height) / 1024;
     }
   else
@@ -991,22 +1039,38 @@ 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
 void
-xr_rendering_draw (struct xr_rendering *r, cairo_t *cr)
+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 (r->page);
+
+      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 * 1024, (y * 1024) - r->title_height,
+                               w * 1024, h * 1024);
     }
   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)
 {
@@ -1028,6 +1092,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);
@@ -1058,19 +1124,13 @@ 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, 0.0, 0.0, 0.0);
 
   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)
   cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
 
   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"),
+    error (0, 0, _("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);
@@ -1121,18 +1181,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;
         }
@@ -1160,6 +1211,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;
@@ -1167,9 +1219,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);
@@ -1270,13 +1323,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:
@@ -1284,7 +1335,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: