Merge master into output branch.
authorBen Pfaff <blp@gnu.org>
Tue, 11 Aug 2009 21:18:26 +0000 (14:18 -0700)
committerBen Pfaff <blp@gnu.org>
Tue, 11 Aug 2009 21:18:26 +0000 (14:18 -0700)
32 files changed:
1  2 
configure.ac
src/data/subcase.c
src/data/variable.c
src/language/command.c
src/language/data-io/data-parser.c
src/language/data-io/list.q
src/language/data-io/print.c
src/language/dictionary/split-file.c
src/language/stats/aggregate.c
src/language/stats/crosstabs.q
src/language/stats/frequencies.q
src/language/stats/regression.q
src/language/stats/roc.c
src/language/stats/roc.h
src/language/stats/t-test.q
src/language/stats/wilcoxon.c
src/language/syntax-file.c
src/libpspp/str.c
src/output/automake.mk
src/output/cairo.c
src/output/chart-provider.h
src/output/chart.c
src/output/charts/cartesian.c
src/output/charts/cartesian.h
src/output/charts/plot-chart.c
src/output/charts/plot-chart.h
src/output/charts/roc-chart.c
src/output/charts/roc-chart.h
src/output/table.c
src/output/table.h
src/ui/gui/find-dialog.c
src/ui/gui/psppire-output-window.h

diff --combined configure.ac
index 77519330b8d54fc926b058395197dff644ff31a6,c44fdd565187e1b9f800e51b0adb609f0b99bf90..5ed6d74fa5d704e87be89e7797b62013c56fa309
@@@ -2,7 -2,7 +2,7 @@@ dnl Process this file with autoconf to 
  
  dnl Initialize.
  AC_PREREQ(2.60)
 -AC_INIT([pspp],[0.7.2],[bug-gnu-pspp@gnu.org])
 +AC_INIT([pspp],[0.7.3],[bug-gnu-pspp@gnu.org])
  AC_CONFIG_HEADERS([config.h])
  AM_INIT_AUTOMAKE
  
@@@ -36,39 -36,27 +36,39 @@@ AM_GNU_GETTEXT_VERSION([0.17]
  dnl Checks for libraries.
  AC_SYS_LARGEFILE
  AC_SEARCH_LIBS([sin], [m])
 -PSPP_LIBPLOT
  PSPP_LC_PAPER
 -AM_CONDITIONAL(WITHCHARTS, test x"$with_libplot" != x"no")
  
  
  AC_ARG_VAR([PSPP_LDFLAGS], [linker flags to be used for linking the pspp binary only])
  AC_ARG_VAR([PSPPIRE_LDFLAGS], [linker flags to be used for linking the psppire binary only])
  
 -
 -AC_ARG_WITH(
 -  gui, 
 -  [AS_HELP_STRING([--without-gui], [don't build the PSPPIRE gui])])
 -
 -required_gtk_version=2.12
 -
 -if test x"$with_gui" != x"no" ; then 
 -  PKG_CHECK_MODULES(GTK, gtk+-2.0 >= $required_gtk_version,,
 -    [PSPP_REQUIRED_PREREQ([gtk+ 2.0 v$required_gtk_version or later (or use --without-gui)])])
 +# Support for Cairo and Pango.
 +AC_ARG_WITH([cairo],
 +  [AS_HELP_STRING(
 +    [--without-cairo], 
 +    [Don't build support for charts (using Cairo and Pango);
 +     implies --without-gui])],
 +  [], [with_cairo=yes])
 +AM_CONDITIONAL([HAVE_CAIRO], [test "$with_cairo" != no])
 +if test "$with_cairo" != no; then
 +  PKG_CHECK_MODULES([CAIRO], [cairo >= 1.5 pango >= 1.22 pangocairo], 
 +    [CPPFLAGS="$CPPFLAGS $CAIRO_CFLAGS"
 +     AC_DEFINE([HAVE_CAIRO], 1, 
 +       [Define to 1 if Cairo and Pango are available.])],
 +    [PSPP_REQUIRED_PREREQ([cairo 1.5 or later and pango 1.22 or later (or use --without-cairo)])])
  fi
 -AM_CONDITIONAL(WITHGUI, test x"$with_gui" != x"no")
  
 +# Support for GUI.
 +AC_ARG_WITH([gui], 
 +  [AS_HELP_STRING([--without-gui], 
 +                  [Don't build the PSPPIRE GUI (using GTK+)])],
 +  [], [with_gui=yes])
 +AM_CONDITIONAL([HAVE_GUI], 
 +               [test "$with_cairo" != no && test "$with_gui" != "no"])
 +if test "$with_cairo" != no && test "$with_gui" != "no"; then
 +  PKG_CHECK_MODULES([GTK], [gtk+-2.0 >= 2.12], [],
 +    [PSPP_REQUIRED_PREREQ([gtk+ 2.0 version 2.12 or later (or use --without-gui)])])
 +fi
  
  dnl Checks needed for psql reader
  
@@@ -118,7 -106,7 +118,7 @@@ if test x"$with_libpq" != x"no" ; the
  fi
  AM_CONDITIONAL(PSQL_SUPPORT, test -n "$PG_CONFIG")
  
- dnl Checks needed for gnumeric reader
+ dnl Checks needed for Gnumeric reader
  gnm_support=yes;
  PKG_CHECK_MODULES(LIBXML2, libxml-2.0,,
                           [PSPP_OPTIONAL_PREREQ([libxml2]); gnm_support=no;]);
diff --combined src/data/subcase.c
index 948319468c3c1d7a1c43d36663c10a49c6d168f7,6ffaa4c2bed24a603e3ce0aa267c16661e82f471..03098685e86d338164ad8963f3f331bc24abb94d
@@@ -64,6 -64,16 +64,16 @@@ subcase_init_var (struct subcase *sc, c
    subcase_add_var (sc, var, direction);
  }
  
+ void
+ subcase_init (struct subcase *sc, int index, int width,
+                   enum subcase_direction direction)
+ {
+   subcase_init_empty (sc);
+   subcase_add (sc, index, width, direction);
+ }
  /* Removes all the fields from SC. */
  void
  subcase_clear (struct subcase *sc)
@@@ -89,6 -99,7 +99,7 @@@ subcase_destroy (struct subcase *sc
    caseproto_unref (sc->proto);
  }
  
  /* Add a field for VAR to SC, with DIRECTION as the sort order.
     Returns true if successful, false if VAR already has a field
     in SC. */
@@@ -96,7 -107,17 +107,17 @@@ boo
  subcase_add_var (struct subcase *sc, const struct variable *var,
                   enum subcase_direction direction)
  {
-   size_t case_index = var_get_case_index (var);
+   return subcase_add (sc, var_get_case_index (var),
+                     var_get_width (var), direction);
+ }
+ /* Add a field for CASE_INDEX, WIDTH to SC, with DIRECTION as the sort order.
+    Returns true if successful, false if CASE_INDEX already has a field
+    in SC. */
+ bool
+ subcase_add (struct subcase *sc, int case_index, int width,
+                  enum subcase_direction direction)
+ {
    struct subcase_field *field;
    size_t i;
  
    sc->fields = xnrealloc (sc->fields, sc->n_fields + 1, sizeof *sc->fields);
    field = &sc->fields[sc->n_fields++];
    field->case_index = case_index;
-   field->width = var_get_width (var);
+   field->width = width;
    field->direction = direction;
    invalidate_proto (sc);
    return true;
  const struct caseproto *
  subcase_get_proto (const struct subcase *sc_)
  {
 -  struct subcase *sc = (struct subcase *) sc_;
 +  struct subcase *sc = CONST_CAST (struct subcase *, sc_);
  
    if (sc->proto == NULL)
      {
diff --combined src/data/variable.c
index bd0e8bf5691782a22ecdd376537fda692b98b95a,d1e308640d05268df79f8639848cef04c618ba23..05edc57e2db85a3c02df4fbe3a16a6747d92c4bc
@@@ -149,13 -149,13 +149,13 @@@ var_clone (const struct variable *old_v
    return new_var;
  }
  
- /* Create a variable to be used for internal calculations only.
-    The variable is assigned a unique dictionary index and a case
-    index of CASE_IDX. */
+ /* Create a variable of the specified WIDTH to be used for
+    internal calculations only.  The variable is assigned a unique
+    dictionary index and a case index of CASE_IDX. */
  struct variable *
- var_create_internal (int case_idx)
+ var_create_internal (int case_idx, int width)
  {
-   struct variable *v = var_create ("$internal", 0);
+   struct variable *v = var_create ("$internal", width);
    struct vardict_info vdi;
    static int counter = INT_MAX / 2;
  
@@@ -494,7 -494,7 +494,7 @@@ var_is_num_missing (const struct variab
     S[] must contain exactly as many characters as V's width.
     V must be a string variable. */
  bool
- var_is_str_missing (const struct variable *v, const char s[],
+ var_is_str_missing (const struct variable *v, const uint8_t s[],
                      enum mv_class class)
  {
    return mv_is_str_missing (&v->miss, s, class);
@@@ -590,10 -590,12 +590,12 @@@ var_append_value_name (const struct var
                       struct string *str)
  {
    const char *name = var_lookup_value_label (v, value);
+   const struct dictionary *dict = var_get_vardict (v)->dict;
    if (name == NULL)
      {
-       char *s = ds_put_uninit (str, v->print.w);
-       data_out (value, &v->print, s);
+       char *s = data_out (value, dict_get_encoding (dict), &v->print);
+       ds_put_cstr (str, s);
+       free (s);
      }
    else
      ds_put_cstr (str, name);
@@@ -955,7 -957,7 +957,7 @@@ void 
  var_attach_aux (const struct variable *v_,
                  void *aux, void (*aux_dtor) (struct variable *))
  {
 -  struct variable *v = (struct variable *) v_ ; /* cast away const  */
 +  struct variable *v = CONST_CAST (struct variable *, v_);
    assert (v->aux == NULL);
    assert (aux != NULL);
    v->aux = aux;
@@@ -1013,7 -1015,7 +1015,7 @@@ var_get_obs_vals (const struct variabl
  void
  var_set_obs_vals (const struct variable *v_, struct cat_vals *cat_vals)
  {
 -  struct variable *v = (struct variable *) v_ ; /* cast away const */
 +  struct variable *v = CONST_CAST (struct variable *, v_ );
    cat_stored_values_destroy (v->obs_vals);
    v->obs_vals = cat_vals;
  }
@@@ -1033,7 -1035,7 +1035,7 @@@ var_has_obs_vals (const struct variabl
  struct attrset *
  var_get_attributes (const struct variable *v) 
  {
 -  return (struct attrset *) &v->attributes;
 +  return CONST_CAST (struct attrset *, &v->attributes);
  }
  
  /* Replaces variable V's attributes set by a copy of ATTRS. */
diff --combined src/language/command.c
index 548d671eb4d307b4af27ddae629a5407e8b87867,c00d94b9c4b3597bcc2416ae0e29fd56018b229f..74f99a89a682009dfa0fa62452669cf8622a446c
@@@ -37,6 -37,7 +37,6 @@@
  #include <libpspp/message.h>
  #include <libpspp/str.h>
  #include <output/manager.h>
 -#include <output/table.h>
  #include <libpspp/getl.h>
  
  #if HAVE_SYS_WAIT_H
@@@ -204,7 -205,7 +204,7 @@@ do_parse_command (struct lexer *lexer
      }
    else if (command->function == NULL)
      {
-       msg (SE, _("%s is unimplemented."), command->name);
+       msg (SE, _("%s is not yet implemented."), command->name);
        result = CMD_NOT_IMPLEMENTED;
        goto finish;
      }
  
    /* Execute command. */
    msg_set_command_name (command->name);
 -  tab_set_command_name (command->name);
 +  som_set_command_name (command->name);
    result = command->function (lexer, ds);
 -  tab_set_command_name (NULL);
 +  som_set_command_name (NULL);
    msg_set_command_name (NULL);
  
    assert (cmd_result_is_valid (result));
index 44054267785fb51a874de832ac66e2da51376881,020f8e4c7e5a20a0caabc4add9f228f98937d1b6..fe78aeb36ed272f309481870d79ea6669d48d735
@@@ -41,6 -41,7 +41,7 @@@
  /* Data parser for textual data like that read by DATA LIST. */
  struct data_parser
    {
+     const struct dictionary *dict; /*Dictionary of destination */
      enum data_parser_type type; /* Type of data to parse. */
      int skip_records;           /* Records to skip before first real data. */
      casenumber max_cases;       /* Max number of cases to read. */
@@@ -79,7 -80,7 +80,7 @@@ static void set_any_sep (struct data_pa
  
  /* Creates and returns a new data parser. */
  struct data_parser *
- data_parser_create (void)
+ data_parser_create (const struct dictionary *dict)
  {
    struct data_parser *parser = xmalloc (sizeof *parser);
  
@@@ -91,6 -92,7 +92,7 @@@
    parser->fields = NULL;
    parser->field_cnt = 0;
    parser->field_allocated = 0;
+   parser->dict = dict;
  
    parser->span = true;
    parser->empty_line_has_field = false;
@@@ -483,7 -485,8 +485,8 @@@ cut_field (const struct data_parser *pa
        /* Regular field. */
        ss_get_chars (&p, ss_cspan (p, ds_ss (&parser->any_sep)), field);
        *last_column = dfm_column_start (reader);
-       if (!ss_ltrim (&p, parser->soft_seps) || ss_is_empty (p))
+       if (!ss_ltrim (&p, parser->soft_seps) || ss_is_empty (p)
+           || ss_find_char (parser->hard_seps, p.string[0]) != SIZE_MAX)
          {
            /* Advance past a trailing hard separator,
               regardless of whether one actually existed.  If
@@@ -505,7 -508,7 +508,7 @@@ static boo
  parse_fixed (const struct data_parser *parser, struct dfm_reader *reader,
               struct ccase *c)
  {
-   enum legacy_encoding encoding = dfm_reader_get_legacy_encoding (reader);
+   const char *encoding = dfm_reader_get_legacy_encoding (reader);
    struct field *f;
    int row;
  
                              f->format.w),
                   encoding, f->format.type, f->format.d,
                   f->first_column, f->first_column + f->format.w,
+                parser->dict,
                   case_data_rw_idx (c, f->case_idx),
                   fmt_var_width (&f->format));
  
@@@ -547,7 -551,7 +551,7 @@@ static boo
  parse_delimited_span (const struct data_parser *parser,
                        struct dfm_reader *reader, struct ccase *c)
  {
-   enum legacy_encoding encoding = dfm_reader_get_legacy_encoding (reader);
+   const char *encoding = dfm_reader_get_legacy_encoding (reader);
    struct string tmp = DS_EMPTY_INITIALIZER;
    struct field *f;
  
  
        data_in (s, encoding, f->format.type, 0,
                 first_column, last_column,
+              parser->dict,
                 case_data_rw_idx (c, f->case_idx),
                 fmt_var_width (&f->format));
      }
@@@ -588,7 -593,7 +593,7 @@@ static boo
  parse_delimited_no_span (const struct data_parser *parser,
                           struct dfm_reader *reader, struct ccase *c)
  {
-   enum legacy_encoding encoding = dfm_reader_get_legacy_encoding (reader);
+   const char *encoding = dfm_reader_get_legacy_encoding (reader);
    struct string tmp = DS_EMPTY_INITIALIZER;
    struct substring s;
    struct field *f;
  
        data_in (s, encoding, f->format.type, 0,
                 first_column, last_column,
+              parser->dict,
                 case_data_rw_idx (c, f->case_idx),
                 fmt_var_width (&f->format));
      }
@@@ -638,8 -644,8 +644,8 @@@ dump_fixed_table (const struct data_par
    struct tab_table *t;
    size_t i;
  
 -  t = tab_create (4, parser->field_cnt + 1, 0);
 -  tab_columns (t, TAB_COL_DOWN, 1);
 +  t = tab_create (4, parser->field_cnt + 1);
 +  tab_columns (t, TAB_COL_DOWN);
    tab_headers (t, 0, 0, 1, 0);
    tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
    tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
    tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
    tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, parser->field_cnt);
    tab_hline (t, TAL_2, 0, 3, 1);
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
  
    for (i = 0; i < parser->field_cnt; i++)
      {
@@@ -679,14 -685,14 +685,14 @@@ dump_delimited_table (const struct data
    struct tab_table *t;
    size_t i;
  
 -  t = tab_create (2, parser->field_cnt + 1, 0);
 -  tab_columns (t, TAB_COL_DOWN, 1);
 +  t = tab_create (2, parser->field_cnt + 1);
 +  tab_columns (t, TAB_COL_DOWN);
    tab_headers (t, 0, 0, 1, 0);
    tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
    tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Format"));
    tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 1, parser->field_cnt);
    tab_hline (t, TAL_2, 0, 1, 1);
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
  
    for (i = 0; i < parser->field_cnt; i++)
      {
index 5f1b3a99e5fe01c66ff45d1a1529639cc70cc78c,c3f9b0881b959fac98a3cffac628662ffe143299..614fa1271e71b5ed3180e2f4e98e1b454745b769
@@@ -35,7 -35,7 +35,7 @@@
  #include <language/dictionary/split-file.h>
  #include <language/lexer/lexer.h>
  #include <libpspp/compiler.h>
 -#include <libpspp/message.h>
 +#include <libpspp/ll.h>
  #include <libpspp/message.h>
  #include <libpspp/misc.h>
  #include <output/htmlP.h>
  /* (functions) */
  
  /* Layout for one output driver. */
 -struct list_ext
 +struct list_target
    {
 +    struct ll ll;
 +    struct outp_driver *driver;
      int type;         /* 0=Values and labels fit across the page. */
      size_t n_vertical;        /* Number of labels to list vertically. */
      size_t header_rows;       /* Number of header rows. */
@@@ -86,12 -84,11 +86,12 @@@ static void write_line (struct outp_dri
  
  /* Other functions. */
  static void list_case (const struct ccase *, casenumber case_idx,
 -                       const struct dataset *);
 -static void determine_layout (void);
 -static void clean_up (void);
 -static void write_header (struct outp_driver *);
 -static void write_all_headers (struct casereader *, const struct dataset*);
 +                       const struct dataset *, struct ll_list *targets);
 +static void determine_layout (struct ll_list *targets);
 +static void clean_up (struct ll_list *targets);
 +static void write_header (struct list_target *);
 +static void write_all_headers (struct casereader *, const struct dataset *,
 +                               struct ll_list *targets);
  
  /* Returns the number of text lines that can fit on the remainder of
     the page. */
@@@ -139,7 -136,6 +139,7 @@@ cmd_list (struct lexer *lexer, struct d
    struct variable *casenum_var = NULL;
    struct casegrouper *grouper;
    struct casereader *group;
 +  struct ll_list targets;
    casenumber case_idx;
    bool ok;
  
        cmd.v_variables[0] = casenum_var;
      }
  
 -  determine_layout ();
 +  determine_layout (&targets);
  
    case_idx = 0;
    for (grouper = casegrouper_create_splits (proc_open (ds), dict);
      {
        struct ccase *c;
  
 -      write_all_headers (group, ds);
 +      write_all_headers (group, ds, &targets);
        for (; (c = casereader_read (group)) != NULL; case_unref (c))
          {
            case_idx++;
            if (case_idx >= cmd.first && case_idx <= cmd.last
                && (case_idx - cmd.first) % cmd.step == 0)
 -            list_case (c, case_idx, ds);
 +            list_case (c, case_idx, ds, &targets);
          }
      }
    ok = casegrouper_destroy (grouper);
  
    ds_destroy(&line_buffer);
  
 -  clean_up ();
 +  clean_up (&targets);
  
    var_destroy (casenum_var);
  
  /* Writes headers to all devices.  This is done at the beginning of
     each SPLIT FILE group. */
  static void
 -write_all_headers (struct casereader *input, const struct dataset *ds)
 +write_all_headers (struct casereader *input, const struct dataset *ds,
 +                   struct ll_list *targets)
  {
 -  struct outp_driver *d;
 +  struct list_target *target;
    struct ccase *c;
  
    c = casereader_peek (input, 0);
    output_split_file_values (ds, c);
    case_unref (c);
  
 -  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
 +  ll_for_each (target, struct list_target, ll, targets)
      {
 +      struct outp_driver *d = target->driver;
        if (!d->class->special)
        {
          d->cp_y += d->font_height;            /* Blank line. */
 -        write_header (d);
 +        write_header (target);
        }
        else if (d->class == &html_class)
        {
  /* Writes the headers.  Some of them might be vertical; most are
     probably horizontal. */
  static void
 -write_header (struct outp_driver *d)
 +write_header (struct list_target *target)
  {
 -  struct list_ext *prc = d->prc;
 +  struct outp_driver *d = target->driver;
  
 -  if (!prc->header_rows)
 +  if (d->class->special || !target->header_rows)
      return;
  
 -  if (n_lines_remaining (d) < prc->header_rows + 1)
 +  if (n_lines_remaining (d) < target->header_rows + 1)
      {
        outp_eject_page (d);
 -      assert (n_lines_remaining (d) >= prc->header_rows + 1);
 +      assert (n_lines_remaining (d) >= target->header_rows + 1);
      }
  
    /* Design the header. */
 -  if (!prc->header)
 +  if (!target->header)
      {
        size_t i;
        size_t x;
  
        /* Allocate, initialize header. */
 -      prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
 +      target->header = xnmalloc (target->header_rows, sizeof *target->header);
        {
        int w = n_chars_width (d);
 -      for (i = 0; i < prc->header_rows; i++)
 +      for (i = 0; i < target->header_rows; i++)
          {
 -          prc->header[i] = xmalloc (w + 1);
 -          memset (prc->header[i], ' ', w);
 +          target->header[i] = xmalloc (w + 1);
 +          memset (target->header[i], ' ', w);
          }
        }
  
        /* Put in vertical names. */
 -      for (i = x = 0; i < prc->n_vertical; i++)
 +      for (i = x = 0; i < target->n_vertical; i++)
        {
          const struct variable *v = cmd.v_variables[i];
            const char *name = var_get_name (v);
            const struct fmt_spec *print = var_get_print_format (v);
          size_t j;
  
 -        memset (&prc->header[prc->header_rows - 1][x], '-', print->w);
 +        memset (&target->header[target->header_rows - 1][x], '-', print->w);
          x += print->w - 1;
          for (j = 0; j < name_len; j++)
 -          prc->header[name_len - j - 1][x] = name[j];
 +          target->header[name_len - j - 1][x] = name[j];
          x += 2;
        }
  
            size_t name_len = strlen (name);
            const struct fmt_spec *print = var_get_print_format (v);
  
 -        memset (&prc->header[prc->header_rows - 1][x], '-',
 +        memset (&target->header[target->header_rows - 1][x], '-',
                  MAX (print->w, (int) name_len));
          if ((int) name_len < print->w)
            x += print->w - name_len;
 -        memcpy (&prc->header[0][x], name, name_len);
 +        memcpy (&target->header[0][x], name, name_len);
          x += name_len + 1;
        }
  
        /* Add null bytes. */
 -      for (i = 0; i < prc->header_rows; i++)
 +      for (i = 0; i < target->header_rows; i++)
        {
          for (x = n_chars_width (d); x >= 1; x--)
 -          if (prc->header[i][x - 1] != ' ')
 +          if (target->header[i][x - 1] != ' ')
              {
 -              prc->header[i][x] = 0;
 +              target->header[i][x] = 0;
                break;
              }
          assert (x);
      }
  
    /* Write out the header, in back-to-front order except for the last line. */
 -  if (prc->header_rows >= 2)
 +  if (target->header_rows >= 2)
      {
        size_t i;
  
 -      for (i = prc->header_rows - 1; i-- != 0; )
 -        write_line (d, prc->header[i]);
 +      for (i = target->header_rows - 1; i-- != 0; )
 +        write_line (d, target->header[i]);
      }
 -  write_line (d, prc->header[prc->header_rows - 1]);
 +  write_line (d, target->header[target->header_rows - 1]);
  }
  
  
  /* Frees up all the memory we've allocated. */
  static void
 -clean_up (void)
 +clean_up (struct ll_list *targets)
  {
 -  struct outp_driver *d;
 +  struct list_target *target, *next;
  
 -  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
 -    if (d->class->special == 0)
 -      {
 -      struct list_ext *prc = d->prc;
 -      size_t i;
 -
 -      if (prc->header)
 -        {
 -          for (i = 0; i < prc->header_rows; i++)
 -            free (prc->header[i]);
 -          free (prc->header);
 -        }
 -      free (prc);
 -      }
 -    else if (d->class == &html_class)
 -      {
 -      if (d->page_open)
 -        {
 -          struct html_driver_ext *x = d->ext;
 +  ll_for_each_safe (target, next, struct list_target, ll, targets)
 +    {
 +      struct outp_driver *d = target->driver;
 +      if (d->class->special == 0)
 +        {
 +          if (target->header)
 +            {
 +              size_t i;
 +              for (i = 0; i < target->header_rows; i++)
 +                free (target->header[i]);
 +              free (target->header);
 +            }
 +        }
 +      else if (d->class == &html_class)
 +        {
 +          if (d->page_open)
 +            {
 +              struct html_driver_ext *x = d->ext;
  
 -          fputs ("</TABLE>\n", x->file);
 -        }
 -      }
 -    else
 -      NOT_REACHED ();
 +              fputs ("</TABLE>\n", x->file);
 +            }
 +        }
 +      else
 +        NOT_REACHED ();
  
 +      ll_remove (&target->ll);
 +      free (target);
 +    }
 +  
    free (cmd.v_variables);
  }
  
@@@ -550,7 -541,7 +550,7 @@@ write_fallback_headers (struct outp_dri
     This is complicated by the fact that we have to do all this for
     every output driver, not just once.  */
  static void
 -determine_layout (void)
 +determine_layout (struct ll_list *targets)
  {
    struct outp_driver *d;
  
       size buffer to allocate. */
    int largest_page_width = 0;
  
 +  ll_init (targets);
    for (d = outp_drivers (NULL); d; d = outp_drivers (d))
      {
        size_t column;  /* Current column. */
        int height;       /* Height of vertical names. */
        int max_width;  /* Page width. */
  
 -      struct list_ext *prc;
 +      struct list_target *target;
 +
 +      target = xmalloc (sizeof *target);
 +      ll_push_tail (targets, &target->ll);
 +      target->driver = d;
 +      target->type = 0;
 +      target->n_vertical = 0;
 +      target->header = NULL;
  
        if (d->class == &html_class)
        continue;
 -
        assert (d->class->special == 0);
  
        outp_open_page (d);
        max_width = n_chars_width (d);
        largest_page_width = MAX (largest_page_width, max_width);
  
 -      prc = d->prc = xmalloc (sizeof *prc);
 -      prc->type = 0;
 -      prc->n_vertical = 0;
 -      prc->header = NULL;
 -
        /* Try layout #1. */
        for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
        {
        }
        if (width <= max_width)
        {
 -        prc->header_rows = 2;
 +        target->header_rows = 2;
          continue;
        }
  
        if (width <= max_width && height <= SHORT_NAME_LEN)
        {
  #ifndef NDEBUG
 -        prc->n_vertical = SIZE_MAX;
 +        target->n_vertical = SIZE_MAX;
  #endif
          for (column = cmd.n_variables; column-- != 0; )
            {
              int trial_width = width - fmt_width + MAX (fmt_width, name_len);
              if (trial_width > max_width)
                {
 -                prc->n_vertical = column + 1;
 +                target->n_vertical = column + 1;
                  break;
                }
              width = trial_width;
            }
 -        assert (prc->n_vertical != SIZE_MAX);
 +        assert (target->n_vertical != SIZE_MAX);
  
 -        prc->n_vertical = cmd.n_variables;
 +        target->n_vertical = cmd.n_variables;
          /* Finally determine the length of the headers. */
 -        for (prc->header_rows = 0, column = 0;
 -             column < prc->n_vertical;
 +        for (target->header_rows = 0, column = 0;
 +             column < target->n_vertical;
               column++)
              {
                const struct variable *var = cmd.v_variables[column];
                size_t name_len = strlen (var_get_name (var));
 -              prc->header_rows = MAX (prc->header_rows, name_len);
 +              target->header_rows = MAX (target->header_rows, name_len);
              }
 -        prc->header_rows++;
 +        target->header_rows++;
          continue;
        }
  
        /* Otherwise use the ugly fallback listing format. */
 -      prc->type = 1;
 -      prc->header_rows = 0;
 +      target->type = 1;
 +      target->header_rows = 0;
  
        d->cp_y += d->font_height;
        write_fallback_headers (d);
  /* Writes case C to output. */
  static void
  list_case (const struct ccase *c, casenumber case_idx,
 -           const struct dataset *ds)
 +           const struct dataset *ds, struct ll_list *targets)
  {
    struct dictionary *dict = dataset_dict (ds);
 -  struct outp_driver *d;
++  const char *encoding = dict_get_encoding (dict);
 +  struct list_target *target;
  
 -  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
 -    if (d->class->special == 0)
 -      {
 -      const struct list_ext *prc = d->prc;
 -      const int max_width = n_chars_width (d);
 -      int column;
 -
 -      if (!prc->header_rows)
 -        {
 -          ds_put_format(&line_buffer, "%8s: ",
 -                          var_get_name (cmd.v_variables[0]));
 -        }
 -
 -
 -      for (column = 0; column < cmd.n_variables; column++)
 -        {
 -          const struct variable *v = cmd.v_variables[column];
 -            const struct fmt_spec *print = var_get_print_format (v);
 -          int width;
 -
 -          if (prc->type == 0 && column >= prc->n_vertical)
 -              {
 -                int name_len = strlen (var_get_name (v));
 -                width = MAX (name_len, print->w);
 -              }
 -          else
 -            width = print->w;
 -
 -          if (width + ds_length(&line_buffer) > max_width &&
 -              ds_length(&line_buffer) != 0)
 -            {
 -              if (!n_lines_remaining (d))
 -                {
 -                  outp_eject_page (d);
 -                  write_header (d);
 -                }
 +  ll_for_each (target, struct list_target, ll, targets)
 +    {
 +      struct outp_driver *d = target->driver;
  
 -              write_line (d, ds_cstr (&line_buffer));
 -              ds_clear(&line_buffer);
 +      if (d->class->special == 0)
 +        {
 +          const int max_width = n_chars_width (d);
 +          int column;
  
 -              if (!prc->header_rows)
 -                  ds_put_format (&line_buffer, "%8s: ", var_get_name (v));
 -            }
 +          if (!target->header_rows)
 +            {
 +              ds_put_format(&line_buffer, "%8s: ",
 +                            var_get_name (cmd.v_variables[0]));
 +            }
  
 -          if (width > print->w)
 -              ds_put_char_multiple(&line_buffer, ' ', width - print->w);
  
 -            if (fmt_is_string (print->type)
 -                || dict_contains_var (dict, v))
 -            {
 -              char *s = data_out (case_data (c, v), dict_get_encoding (dict), print);
 -              ds_put_cstr (&line_buffer, s);
 -              free (s);
 -            }
 -            else
 -              {
 -              char *s;
 -                union value case_idx_value;
 -                case_idx_value.f = case_idx;
 -                s = data_out (&case_idx_value, dict_get_encoding (dict), print);
 -              ds_put_cstr (&line_buffer, s);
 -              free (s);
 -              }
 -
 -          ds_put_char (&line_buffer, ' ');
 -        }
 +          for (column = 0; column < cmd.n_variables; column++)
 +            {
 +              const struct variable *v = cmd.v_variables[column];
 +              const struct fmt_spec *print = var_get_print_format (v);
 +              int width;
++              char *s;
 +
 +              if (target->type == 0 && column >= target->n_vertical)
 +                {
 +                  int name_len = strlen (var_get_name (v));
 +                  width = MAX (name_len, print->w);
 +                }
 +              else
 +                width = print->w;
 +
 +              if (width + ds_length(&line_buffer) > max_width &&
 +                  ds_length(&line_buffer) != 0)
 +                {
 +                  if (!n_lines_remaining (d))
 +                    {
 +                      outp_eject_page (d);
 +                      write_header (target);
 +                    }
 +
 +                  write_line (d, ds_cstr (&line_buffer));
 +                  ds_clear(&line_buffer);
 +
 +                  if (!target->header_rows)
 +                    ds_put_format (&line_buffer, "%8s: ", var_get_name (v));
 +                }
 +
 +              if (width > print->w)
 +                ds_put_char_multiple(&line_buffer, ' ', width - print->w);
 +
-               if (fmt_is_string (print->type)
-                   || dict_contains_var (dict, v))
-                 {
-                   data_out (case_data (c, v), print,
-                             ds_put_uninit (&line_buffer, print->w));
-                 }
++              if (fmt_is_string (print->type) || dict_contains_var (dict, v))
++                s = data_out (case_data (c, v), encoding, print);
 +              else
 +                {
 +                  union value case_idx_value;
 +                  case_idx_value.f = case_idx;
-                   data_out (&case_idx_value, print,
-                             ds_put_uninit (&line_buffer,print->w));
++                  s = data_out (&case_idx_value, encoding, print);
 +                }
 +
++              ds_put_cstr (&line_buffer, s);
++              free (s);
 +              ds_put_char(&line_buffer, ' ');
 +            }
  
 -      if (!n_lines_remaining (d))
 -        {
 -          outp_eject_page (d);
 -          write_header (d);
 -        }
 +          if (!n_lines_remaining (d))
 +            {
 +              outp_eject_page (d);
 +              write_header (target);
 +            }
  
 -      write_line (d, ds_cstr (&line_buffer));
 -      ds_clear(&line_buffer);
 -      }
 -    else if (d->class == &html_class)
 -      {
 -      struct html_driver_ext *x = d->ext;
 -      int column;
 +          write_line (d, ds_cstr (&line_buffer));
 +          ds_clear(&line_buffer);
 +        }
 +      else if (d->class == &html_class)
 +        {
 +          struct html_driver_ext *x = d->ext;
 +          int column;
  
 -      fputs ("  <TR>\n", x->file);
 +          fputs ("  <TR>\n", x->file);
  
 -      for (column = 0; column < cmd.n_variables; column++)
 -        {
 -          const struct variable *v = cmd.v_variables[column];
 -            const struct fmt_spec *print = var_get_print_format (v);
 -          char *s = NULL;
 -
 -            if (fmt_is_string (print->type)
 -                || dict_contains_var (dict, v))
 -            s = data_out (case_data (c, v), dict_get_encoding (dict), print);
 -            else
 -              {
 -                union value case_idx_value;
 -                case_idx_value.f = case_idx;
 -                s = data_out (&case_idx_value, dict_get_encoding (dict), print);
 -              }
 -
 -            fputs ("    <TD>", x->file);
 -            html_put_cell_contents (d, TAB_FIX, ss_buffer (s, print->w));
 -          free (s);
 -            fputs ("</TD>\n", x->file);
 -        }
 +          for (column = 0; column < cmd.n_variables; column++)
 +            {
 +              const struct variable *v = cmd.v_variables[column];
 +              const struct fmt_spec *print = var_get_print_format (v);
-               char buf[256];
++              char *s;
 +
 +              if (fmt_is_string (print->type)
 +                  || dict_contains_var (dict, v))
-                 data_out (case_data (c, v), print, buf);
++                s = data_out (case_data (c, v), encoding, print);
 +              else
 +                {
 +                  union value case_idx_value;
 +                  case_idx_value.f = case_idx;
-                   data_out (&case_idx_value, print, buf);
++                  s = data_out (&case_idx_value, encoding, print);
 +                }
 +
 +              fputs ("    <TD>", x->file);
-               html_put_cell_contents (d, TAB_FIX, ss_buffer (buf, print->w));
++              html_put_cell_contents (d, TAB_FIX, ss_cstr (s));
 +              fputs ("</TD>\n", x->file);
++
++              free (s);
 +            }
  
 -      fputs ("  </TR>\n", x->file);
 -      }
 -    else
 -      NOT_REACHED ();
 +          fputs ("  </TR>\n", x->file);
 +        }
 +      else
 +        NOT_REACHED ();
 +    }
  }
  
 +
  /*
     Local Variables:
     mode: c
index 897c10cc158c8858a6c539bb54275bb53e9c0aec,e345477baf706f50add706bbac7e5f04b0d4057d..dd1e26d270dab5d8d13eff9a85146260b68cc053
@@@ -32,6 -32,7 +32,7 @@@
  #include <language/lexer/lexer.h>
  #include <language/lexer/variable-parser.h>
  #include <libpspp/assertion.h>
+ #include <libpspp/i18n.h>
  #include <libpspp/compiler.h>
  #include <libpspp/ll.h>
  #include <libpspp/message.h>
@@@ -83,7 -84,7 +84,7 @@@ struct print_trn
      struct pool *pool;          /* Stores related data. */
      bool eject;                 /* Eject page before printing? */
      bool include_prefix;        /* Prefix lines with space? */
-     enum legacy_encoding encoding; /* Encoding to use for output. */
+     const char *encoding;       /* Encoding to use for output. */
      struct dfm_writer *writer;        /* Output file, NULL=listing file. */
      struct ll_list specs;       /* List of struct prt_out_specs. */
      size_t record_cnt;          /* Number of records to write. */
@@@ -395,8 -396,8 +396,8 @@@ dump_table (struct print_trns *trns, co
    int row;
  
    spec_cnt = ll_count (&trns->specs);
 -  t = tab_create (4, spec_cnt + 1, 0);
 -  tab_columns (t, TAB_COL_DOWN, 1);
 +  t = tab_create (4, spec_cnt + 1);
 +  tab_columns (t, TAB_COL_DOWN);
    tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, spec_cnt);
    tab_hline (t, TAL_2, 0, 3, 1);
    tab_headers (t, 0, 0, 1, 0);
    tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
    tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Columns"));
    tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
    row = 1;
    ll_for_each (spec, struct prt_out_spec, ll, &trns->specs)
      {
@@@ -480,12 -481,13 +481,13 @@@ print_trns_proc (void *trns_, struct cc
        else
          {
            ds_put_substring (&trns->line, ds_ss (&spec->string));
-           if (trns->encoding != LEGACY_NATIVE)
+           if (0 != strcmp (trns->encoding, LEGACY_NATIVE))
              {
                size_t length = ds_length (&spec->string);
                char *data = ss_data (ds_tail (&trns->line, length));
-               legacy_recode (LEGACY_NATIVE, data,
-                              trns->encoding, data, length);
+             char *s = recode_string (trns->encoding, LEGACY_NATIVE, data, length);
+             memcpy (data, s, length);
+             free (s);
              }
          }
      }
index 5d3650e3b3de569bd6e3eee5e1ff30d3ffddd267,5d2b42d758eed2d1e305d05046d59cf0aa290877..d2f59ccc813fe8661435bb03bee0cbf1a01caa20
@@@ -77,8 -77,8 +77,8 @@@ output_split_file_values (const struct 
    if (split_cnt == 0)
      return;
  
 -  t = tab_create (3, split_cnt + 1, 0);
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  t = tab_create (3, split_cnt + 1);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
    tab_vline (t, TAL_GAP, 1, 0, split_cnt);
    tab_vline (t, TAL_GAP, 2, 0, split_cnt);
    tab_text (t, 0, 0, TAB_NONE, _("Variable"));
    for (i = 0; i < split_cnt; i++)
      {
        const struct variable *v = split[i];
-       char temp_buf[80];
+       char *s;
        const char *val_lab;
        const struct fmt_spec *print = var_get_print_format (v);
  
        tab_text_format (t, 0, i + 1, TAB_LEFT, "%s", var_get_name (v));
  
-       data_out (case_data (c, v), print, temp_buf);
-       temp_buf[print->w] = 0;
+       s = data_out (case_data (c, v), dict_get_encoding (dict), print);
 +
-       tab_text_format (t, 1, i + 1, 0, "%.*s", print->w, temp_buf);
+       tab_text_format (t, 1, i + 1, 0, "%.*s", print->w, s);
  
+       free (s);
+       
        val_lab = var_lookup_value_label (v, case_data (c, v));
        if (val_lab)
        tab_text (t, 2, i + 1, TAB_LEFT, val_lab);
index 3af2cd8d749a833a55b1a437b9aede885772c1dd,08d2f5e15c7d06176f1787b5e81017c7da54ad83..891e0ccaa3cb8f7c4bb4ba0da73feb1d456a1843
@@@ -965,20 -965,20 +965,20 @@@ dump_aggregate_info (struct agr_proc *a
          case MEDIAN:
            {
              struct casereader *sorted_reader;
 -            struct order_stats *median = percentile_create (0.5, i->cc);
 +            struct percentile *median = percentile_create (0.5, i->cc);
 +              struct order_stats *os = &median->parent;
  
              sorted_reader = casewriter_make_reader (i->writer);
  
 -            order_stats_accumulate (&median, 1,
 +            order_stats_accumulate (&os, 1,
                                      sorted_reader,
                                      i->weight,
                                      i->subject,
                                      i->exclude);
  
 -            v->f = percentile_calculate ((struct percentile *) median,
 -                                         PC_HAVERAGE);
 +            v->f = percentile_calculate (median, PC_HAVERAGE);
  
 -            statistic_destroy ((struct statistic *) median);
 +            statistic_destroy (&median->parent.parent);
            }
            break;
          case SD:
@@@ -1105,10 -1105,10 +1105,10 @@@ initialize_aggregate_info (struct agr_p
              proto = caseproto_add_width (proto, 0);
  
            if ( ! iter->subject)
-             iter->subject = var_create_internal (0);
+             iter->subject = var_create_internal (0, 0);
  
            if ( ! iter->weight)
-             iter->weight = var_create_internal (1);
+             iter->weight = var_create_internal (1, 0);
  
              subcase_init_var (&ordering, iter->subject, SC_ASCEND);
            iter->writer = sort_create_writer (&ordering, proto);
index 7db55a2c24b2278072acad0fb26d00482a3db24b,99fa41d9c056a039ef9d92fdc9090758539c9d15..5695ed6478b64490095ea0c8abde83873fb38030
@@@ -177,6 -177,7 +177,7 @@@ get_var_range (const struct variable *v
  
  struct crosstabs_proc
    {
+     const struct dictionary *dict;
      enum { INTEGER, GENERAL } mode;
      enum mv_class exclude;
      bool pivot;
      unsigned int statistics;    /* Bit k is 1 if statistic k is requested. */
    };
  
 +/* Auxiliary data structure for tab_dim. */
 +struct crosstabs_dim_aux
 +  {
 +    enum mv_class exclude;
 +  };
 +
  static void
  init_proc (struct crosstabs_proc *proc, struct dataset *ds)
  {
    const struct variable *wv = dict_get_weight (dataset_dict (ds));
+   proc->dict = dataset_dict (ds);
    proc->bad_warn = true;
    proc->variables = NULL;
    proc->n_variables = 0;
@@@ -234,8 -230,8 +236,8 @@@ free_proc (struct crosstabs_proc *proc
  
           The rest of the data was allocated and destroyed at a
           lower level already. */
 -      free (pt);
      }
 +  free (proc->pivots);
  }
  
  static int internal_cmd_crosstabs (struct lexer *lexer, struct dataset *ds,
@@@ -840,7 -836,7 +842,7 @@@ make_summary_table (struct crosstabs_pr
    struct string name;
    int i;
  
 -  summary = tab_create (7, 3 + proc->n_pivots, 1);
 +  summary = tab_create (7, 3 + proc->n_pivots);
    tab_title (summary, _("Summary."));
    tab_headers (summary, 1, 0, 3, 0);
    tab_joint_text (summary, 1, 0, 6, 0, TAB_CENTER, _("Cases"));
@@@ -918,8 -914,8 +920,8 @@@ static void display_symmetric (struct c
  static void display_risk (struct pivot_table *, struct tab_table *);
  static void display_directional (struct crosstabs_proc *, struct pivot_table *,
                                   struct tab_table *);
 -static void crosstabs_dim (struct tab_table *, struct outp_driver *,
 -                           void *proc);
 +static void crosstabs_dim (struct tab_rendering *, void *aux);
 +static void crosstabs_dim_free (void *aux);
  static void table_value_missing (struct crosstabs_proc *proc,
                                   struct tab_table *table, int c, int r,
                                 unsigned char opt, const union value *v,
@@@ -1151,7 -1147,8 +1153,7 @@@ create_crosstab_table (struct crosstabs
    int i;
  
    table = tab_create (pt->n_consts + 1 + pt->n_cols + 1,
 -                      (pt->n_entries / pt->n_cols) * 3 / 2 * proc->n_cells + 10,
 -                      true);
 +                      (pt->n_entries / pt->n_cols) * 3 / 2 * proc->n_cells + 10);
    tab_headers (table, pt->n_consts + 1, 0, 2, 0);
  
    /* First header line. */
      {
        const struct variable *var = pt->const_vars[i];
        size_t ofs;
+       char *s = NULL;
  
        ds_put_format (&title, ", %s=", var_get_name (var));
  
        /* Insert the formatted value of the variable, then trim
           leading spaces in what was just inserted. */
        ofs = ds_length (&title);
-       data_out (&pt->const_values[i], var_get_print_format (var),
-                 ds_put_uninit (&title, var_get_width (var)));
+       s = data_out (&pt->const_values[i], dict_get_encoding (proc->dict), var_get_print_format (var));
+       ds_put_cstr (&title, s);
+       free (s);
        ds_remove (&title, ofs, ss_cspan (ds_substr (&title, ofs, SIZE_MAX),
                                          ss_cstr (" ")));
      }
@@@ -1225,7 -1224,8 +1229,7 @@@ create_chisq_table (struct pivot_table 
    struct tab_table *chisq;
  
    chisq = tab_create (6 + (pt->n_vars - 2),
 -                      pt->n_entries / pt->n_cols * 3 / 2 * N_CHISQ + 10,
 -                      1);
 +                      pt->n_entries / pt->n_cols * 3 / 2 * N_CHISQ + 10);
    tab_headers (chisq, 1 + (pt->n_vars - 2), 0, 1, 0);
  
    tab_title (chisq, _("Chi-square tests."));
    tab_text (chisq, 3, 0, TAB_RIGHT | TAT_TITLE,
              _("Asymp. Sig. (2-sided)"));
    tab_text (chisq, 4, 0, TAB_RIGHT | TAT_TITLE,
-             _("Exact. Sig. (2-sided)"));
+             _("Exact Sig. (2-sided)"));
    tab_text (chisq, 5, 0, TAB_RIGHT | TAT_TITLE,
-             _("Exact. Sig. (1-sided)"));
+             _("Exact Sig. (1-sided)"));
    tab_offset (chisq, 0, 1);
  
    return chisq;
@@@ -1252,7 -1252,7 +1256,7 @@@ create_sym_table (struct pivot_table *p
    struct tab_table *sym;
  
    sym = tab_create (6 + (pt->n_vars - 2),
 -                    pt->n_entries / pt->n_cols * 7 + 10, 1);
 +                    pt->n_entries / pt->n_cols * 7 + 10);
    tab_headers (sym, 2 + (pt->n_vars - 2), 0, 1, 0);
    tab_title (sym, _("Symmetric measures."));
  
@@@ -1274,7 -1274,8 +1278,7 @@@ create_risk_table (struct pivot_table *
  {
    struct tab_table *risk;
  
 -  risk = tab_create (4 + (pt->n_vars - 2), pt->n_entries / pt->n_cols * 4 + 10,
 -                     1);
 +  risk = tab_create (4 + (pt->n_vars - 2), pt->n_entries / pt->n_cols * 4 + 10);
    tab_headers (risk, 1 + pt->n_vars - 2, 0, 2, 0);
    tab_title (risk, _("Risk estimate."));
  
@@@ -1299,7 -1300,7 +1303,7 @@@ create_direct_table (struct pivot_tabl
    struct tab_table *direct;
  
    direct = tab_create (7 + (pt->n_vars - 2),
 -                       pt->n_entries / pt->n_cols * 7 + 10, 1);
 +                       pt->n_entries / pt->n_cols * 7 + 10);
    tab_headers (direct, 3 + (pt->n_vars - 2), 0, 1, 0);
    tab_title (direct, _("Directional measures."));
  
@@@ -1347,7 -1348,6 +1351,7 @@@ static voi
  submit (struct crosstabs_proc *proc, struct pivot_table *pt,
          struct tab_table *t)
  {
 +  struct crosstabs_dim_aux *aux;
    int i;
  
    if (t == NULL)
    tab_box (t, -1, -1, -1, TAL_GAP, 0, tab_t (t), tab_l (t) - 1,
           tab_nr (t) - 1);
    tab_vline (t, TAL_2, tab_l (t), 0, tab_nr (t) - 1);
 -  tab_dim (t, crosstabs_dim, proc);
 +
 +  aux = xmalloc (sizeof *aux);
 +  aux->exclude = proc->exclude;
 +  tab_dim (t, crosstabs_dim, crosstabs_dim_free, aux);
 +
    tab_submit (t);
  }
  
  /* Sets the widths of all the columns and heights of all the rows in
     table T for driver D. */
  static void
 -crosstabs_dim (struct tab_table *t, struct outp_driver *d, void *proc_)
 +crosstabs_dim (struct tab_rendering *r, void *aux_)
  {
 -  struct crosstabs_proc *proc = proc_;
 +  const struct tab_table *t = r->table;
 +  struct outp_driver *d = r->driver;
 +  struct crosstabs_dim_aux *aux = aux_;
    int i;
  
    /* Width of a numerical column. */
    int c = outp_string_width (d, "0.000000", OUTP_PROPORTIONAL);
 -  if (proc->exclude == MV_NEVER)
 +  if (aux->exclude == MV_NEVER)
      c += outp_string_width (d, "M", OUTP_PROPORTIONAL);
  
    /* Set width for header columns. */
 -  if (t->l != 0)
 +  if (tab_l (t) != 0)
      {
        size_t i;
        int w;
  
 -      w = d->width - c * (t->nc - t->l);
 -      for (i = 0; i <= t->nc; i++)
 -        w -= t->wrv[i];
 -      w /= t->l;
 +      w = d->width - c * (tab_nc (t) - tab_l (t));
 +      for (i = 0; i <= tab_nc (t); i++)
 +        w -= r->wrv[i];
 +      w /= tab_l (t);
  
        if (w < d->prop_em_width * 8)
        w = d->prop_em_width * 8;
        if (w > d->prop_em_width * 15)
        w = d->prop_em_width * 15;
  
 -      for (i = 0; i < t->l; i++)
 -      t->w[i] = w;
 +      for (i = 0; i < tab_l (t); i++)
 +      r->w[i] = w;
      }
  
 -  for (i = t->l; i < t->nc; i++)
 -    t->w[i] = c;
 +  for (i = tab_l (t); i < tab_nc (t); i++)
 +    r->w[i] = c;
 +
 +  for (i = 0; i < tab_nr (t); i++)
 +    r->h[i] = tab_natural_height (r, i);
 +}
  
 -  for (i = 0; i < t->nr; i++)
 -    t->h[i] = tab_natural_height (t, d, i);
 +static void
 +crosstabs_dim_free (void *aux_)
 +{
 +  struct crosstabs_dim_aux *aux = aux_;
 +  free (aux);
  }
  
  static bool
@@@ -1526,22 -1513,26 +1530,21 @@@ table_value_missing (struct crosstabs_p
                       struct tab_table *table, int c, int r, unsigned char opt,
                     const union value *v, const struct variable *var)
  {
 -  struct substring s;
 -  const struct fmt_spec *print = var_get_print_format (var);
 -
    const char *label = var_lookup_value_label (var, v);
 -  if (label)
 -    {
 -      tab_text (table, c, r, TAB_LEFT, label);
 -      return;
 -    }
 -
 -  s = ss_cstr (data_out_pool (v, dict_get_encoding (proc->dict), print,
 -                           table->container));
 -  if (proc->exclude == MV_NEVER && var_is_num_missing (var, v->f, MV_USER))
 -    s.string[s.length++] = 'M';
 -  while (s.length && *s.string == ' ')
 +  if (label != NULL)
 +    tab_text (table, c, r, TAB_LEFT, label);
 +  else
      {
 -      s.length--;
 -      s.string++;
 +      const struct fmt_spec *print = var_get_print_format (var);
 +      if (proc->exclude == MV_NEVER && var_is_value_missing (var, v, MV_USER))
 +        {
-           char *s = xmalloc (print->w + 2);
-           strcpy (&s[print->w], "M");
-           tab_text (table, c, r, opt, s + strspn (s, " "));
++          char *s = data_out (v, dict_get_encoding (proc->dict), print);
++          tab_text_format (table, c, r, opt, "%sM", s + strspn (s, " "));
 +          free (s);
 +        }
 +      else
-         tab_value (table, c, r, opt, v, print);
++        tab_value (table, c, r, opt, v, proc->dict, print);
      }
 -  tab_raw (table, c, r, opt, &s);
  }
  
  /* Draws a line across TABLE at the current row to indicate the most
@@@ -1566,22 -1557,26 +1569,26 @@@ display_dimensions (struct crosstabs_pr
     additionally suffixed with a letter `M'. */
  static void
  format_cell_entry (struct tab_table *table, int c, int r, double value,
-                    char suffix, bool mark_missing)
+                    char suffix, bool mark_missing, const struct dictionary *dict)
  {
    const struct fmt_spec f = {FMT_F, 10, 1};
    union value v;
-   int len = 10;
-   char s[16];
 -  struct substring s;
++  char suffixes[3];
++  int suffix_len;
++  char *s;
  
    v.f = value;
-   data_out (&v, &f, s);
 -  s = ss_cstr (data_out_pool (&v, dict_get_encoding (dict), &f, table->container));
++  s = data_out (&v, dict_get_encoding (dict), &f);
 -  while (*s.string == ' ')
 -    {
 -      s.length--;
 -      s.string++;
 -    }
++  suffix_len = 0;
    if (suffix != 0)
-     s[len++] = suffix;
 -    s.string[s.length++] = suffix;
++    suffixes[suffix_len++] = suffix;
    if (mark_missing)
-     s[len++] = 'M';
-   s[len] = '\0';
 -    s.string[s.length++] = 'M';
++    suffixes[suffix_len++] = 'M';
++  suffixes[suffix_len] = '\0';
  
-   tab_text (table, c, r, TAB_RIGHT, s + strspn (s, " "));
 -  tab_raw (table, c, r, TAB_RIGHT, &s);
++  tab_text_format (table, c, r, TAB_RIGHT, "%s%s",
++                   s + strspn (s, " "), suffixes);
  }
  
  /* Displays the crosstabulation table. */
@@@ -1656,7 -1651,7 +1663,7 @@@ display_crosstabulation (struct crossta
                  default:
                    NOT_REACHED ();
                  }
-               format_cell_entry (table, c, i, v, suffix, mark_missing);
+               format_cell_entry (table, c, i, v, suffix, mark_missing, proc->dict);
              }
  
            mp++;
                NOT_REACHED ();
              }
  
-           format_cell_entry (table, pt->n_cols, 0, v, suffix, mark_missing);
+           format_cell_entry (table, pt->n_cols, 0, v, suffix, mark_missing, proc->dict);
            tab_next_row (table);
          }
      }
                NOT_REACHED ();
              }
  
-           format_cell_entry (table, c, i, v, suffix, mark_missing);
+           format_cell_entry (table, c, i, v, suffix, mark_missing, proc->dict);
          }
        last_row = i;
      }
index e670c089b5c69c93a40ba52427a254d34c3b6732,83b864c9f695c4494c5b5f2204b9d4ecd0054c46..76ba5d3f1f874c4bd720f33f02b20e82b9642195
@@@ -207,6 -207,8 +207,8 @@@ struct freq_ta
      struct hsh_table *data;   /* Undifferentiated data. */
      struct freq_mutable *valid; /* Valid freqs. */
      int n_valid;              /* Number of total freqs. */
+     const struct dictionary *dict; /* The dict from whence entries in the table
+                                     come */
  
      struct freq_mutable *missing; /* Missing freqs. */
      int n_missing;            /* Number of missing freqs. */
@@@ -610,14 -612,13 +612,14 @@@ postcalc (const struct dataset *ds
  
          hist = freq_tab_to_hist (ft,v);
  
 -        histogram_plot_n (hist, var_to_string(v),
 +          chart_submit (histogram_chart_create (
 +                          hist, var_to_string(v),
                          vf->tab.valid_cases,
                          d[frq_mean],
                          d[frq_stddev],
 -                        normal);
 +                        normal));
  
 -        statistic_destroy ((struct statistic *)hist);
 +        statistic_destroy (&hist->parent);
        }
  
        if ( chart == GFT_PIE)
@@@ -756,6 -757,7 +758,7 @@@ frq_custom_variables (struct lexer *lex
        }
        vf = var_attach_aux (v, xmalloc (sizeof *vf), var_dtor_free);
        vf->tab.valid = vf->tab.missing = NULL;
+       vf->tab.dict = dataset_dict (ds);
        vf->n_groups = 0;
        vf->groups = NULL;
        vf->width = var_get_width (v);
@@@ -998,39 -1000,26 +1001,39 @@@ compare_freq_alpha_d (const void *a_, c
  \f
  /* Frequency table display. */
  
 +struct full_dim_aux
 +  {
 +    bool show_labels;
 +  };
 +
  /* Sets the widths of all the columns and heights of all the rows in
     table T for driver D. */
  static void
 -full_dim (struct tab_table *t, struct outp_driver *d, void *aux UNUSED)
 +full_dim (struct tab_rendering *r, void *aux_)
  {
 -  int i = 0;
 -  int columns = 5;
 +  const struct outp_driver *d = r->driver;
 +  const struct tab_table *t = r->table;
 +  const struct full_dim_aux *aux = aux_;
 +  int i;
  
 -  if (cmd.labels == FRQ_LABELS)
 +  for (i = 0; i < tab_nc (t); i++)
      {
 -    t->w[0] = MIN (tab_natural_width (t, d, 0), d->prop_em_width * 15);
 -      i = 1;
 -      columns ++;
 +      r->w[i] = tab_natural_width (r, i);
 +      if (aux->show_labels && i == 0)
 +        r->w[i] = MIN (r->w[i], d->prop_em_width * 15);
 +      else
 +        r->w[i] = MAX (r->w[i], d->prop_em_width * 8);
      }
  
 -  for (;i < columns; i++)
 -    t->w[i] = MAX (tab_natural_width (t, d, i), d->prop_em_width * 8);
 +  for (i = 0; i < tab_nr (t); i++)
 +    r->h[i] = d->font_height;
 +}
  
 -  for (i = 0; i < t->nr; i++)
 -    t->h[i] = d->font_height;
 +static void
 +full_dim_free (void *aux_)
 +{
 +  struct full_dim_aux *aux = aux_;
 +  free (aux);
  }
  
  /* Displays a full frequency table for variable V. */
@@@ -1073,17 -1062,12 +1076,17 @@@ dump_full (const struct variable *v, co
  
    const bool lab = (cmd.labels == FRQ_LABELS);
  
 +  struct full_dim_aux *aux;
 +
    vf = get_var_freqs (v);
    ft = &vf->tab;
    n_categories = ft->n_valid + ft->n_missing;
 -  t = tab_create (5 + lab, n_categories + 3, 0);
 +  t = tab_create (5 + lab, n_categories + 3);
    tab_headers (t, 0, 0, 2, 0);
 -  tab_dim (t, full_dim, NULL);
 +
 +  aux = xmalloc (sizeof *aux);
 +  aux->show_labels = lab;
 +  tab_dim (t, full_dim, full_dim_free, aux);
  
    if (lab)
      tab_text (t, 0, 1, TAB_CENTER | TAT_TITLE, _("Value Label"));
            tab_text (t, 0, r, TAB_LEFT, label);
        }
  
-       tab_value (t, 0 + lab, r, TAB_NONE, &f->value, &vf->print);
+       tab_value (t, 0 + lab, r, TAB_NONE, &f->value, ft->dict, &vf->print);
        tab_double (t, 1 + lab, r, TAB_NONE, f->count, wfmt);
        tab_double (t, 2 + lab, r, TAB_NONE, percent, NULL);
        tab_double (t, 3 + lab, r, TAB_NONE, valid_percent, NULL);
            tab_text (t, 0, r, TAB_LEFT, label);
        }
  
-       tab_value (t, 0 + lab, r, TAB_NONE, &f->value, &vf->print);
+       tab_value (t, 0 + lab, r, TAB_NONE, &f->value, ft->dict, &vf->print);
        tab_double (t, 1 + lab, r, TAB_NONE, f->count, wfmt);
        tab_double (t, 2 + lab, r, TAB_NONE,
                     f->count / ft->total_cases * 100.0, NULL);
  /* Sets the widths of all the columns and heights of all the rows in
     table T for driver D. */
  static void
 -condensed_dim (struct tab_table *t, struct outp_driver *d, void *aux UNUSED)
 +condensed_dim (struct tab_rendering *r, void *aux UNUSED)
  {
 -  int cum_w = MAX (outp_string_width (d, _("Cum"), OUTP_PROPORTIONAL),
 -                 MAX (outp_string_width (d, _("Cum"), OUTP_PROPORTIONAL),
 -                      outp_string_width (d, "000", OUTP_PROPORTIONAL)));
 +  struct outp_driver *d = r->driver;
 +  const struct tab_table *t = r->table;
 +
 +  int cum_width = outp_string_width (d, _("Cum"), OUTP_PROPORTIONAL);
 +  int zeros_width = outp_string_width (d, "000", OUTP_PROPORTIONAL);
 +  int max_width = MAX (cum_width, zeros_width);
  
    int i;
  
    for (i = 0; i < 2; i++)
 -    t->w[i] = MAX (tab_natural_width (t, d, i), d->prop_em_width * 8);
 +    {
 +      r->w[i] = tab_natural_width (r, i);
 +      r->w[i] = MAX (r->w[i], d->prop_em_width * 8);
 +    }
    for (i = 2; i < 4; i++)
 -    t->w[i] = cum_w;
 -  for (i = 0; i < t->nr; i++)
 -    t->h[i] = d->font_height;
 +    r->w[i] = max_width;
 +  for (i = 0; i < tab_nr (t); i++)
 +    r->h[i] = d->font_height;
  }
  
  /* Display condensed frequency table for variable V. */
@@@ -1192,7 -1170,7 +1195,7 @@@ dump_condensed (const struct variable *
    vf = get_var_freqs (v);
    ft = &vf->tab;
    n_categories = ft->n_valid + ft->n_missing;
 -  t = tab_create (4, n_categories + 2, 0);
 +  t = tab_create (4, n_categories + 2);
  
    tab_headers (t, 0, 0, 2, 0);
    tab_text (t, 0, 1, TAB_CENTER | TAT_TITLE, _("Value"));
    tab_text (t, 2, 1, TAB_CENTER | TAT_TITLE, _("Pct"));
    tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Cum"));
    tab_text (t, 3, 1, TAB_CENTER | TAT_TITLE, _("Pct"));
 -  tab_dim (t, condensed_dim, NULL);
 +  tab_dim (t, condensed_dim, NULL, NULL);
  
    r = 2;
    for (f = ft->valid; f < ft->missing; f++)
        percent = f->count / ft->total_cases * 100.0;
        cum_total += f->count / ft->valid_cases * 100.0;
  
-       tab_value (t, 0, r, TAB_NONE, &f->value, &vf->print);
+       tab_value (t, 0, r, TAB_NONE, &f->value, ft->dict, &vf->print);
        tab_double (t, 1, r, TAB_NONE, f->count, wfmt);
        tab_double (t, 2, r, TAB_NONE, percent, NULL);
        tab_double (t, 3, r, TAB_NONE, cum_total, NULL);
      }
    for (; f < &ft->valid[n_categories]; f++)
      {
-       tab_value (t, 0, r, TAB_NONE, &f->value, &vf->print);
+       tab_value (t, 0, r, TAB_NONE, &f->value, ft->dict, &vf->print);
        tab_double (t, 1, r, TAB_NONE, f->count, wfmt);
        tab_double (t, 2, r, TAB_NONE,
                 f->count / ft->total_cases * 100.0, NULL);
           0, 0, 3, r - 1);
    tab_hline (t, TAL_2, 0, 3, 2);
    tab_title (t, "%s", var_to_string (v));
 -  tab_columns (t, SOM_COL_DOWN, 1);
 +  tab_columns (t, SOM_COL_DOWN);
    tab_submit (t);
  }
  \f
@@@ -1398,8 -1376,8 +1401,8 @@@ dump_statistics (const struct variable 
      }
    calc_stats (v, stat_value);
  
 -  t = tab_create (3, n_stats + n_percentiles + 2, 0);
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  t = tab_create (3, n_stats + n_percentiles + 2);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
  
    tab_box (t, TAL_1, TAL_1, -1, -1 , 0 , 0 , 2, tab_nr(t) - 1) ;
  
                  var_get_print_format (v));
      }
  
 -  tab_columns (t, SOM_COL_DOWN, 1);
 +  tab_columns (t, SOM_COL_DOWN);
    if (show_varname)
      tab_title (t, "%s", var_to_string (v));
    else
@@@ -1459,7 -1437,7 +1462,7 @@@ freq_tab_to_hist (const struct freq_ta
    double x_min = DBL_MAX;
    double x_max = -DBL_MAX;
  
 -  struct statistic *hist;
 +  struct histogram *hist;
    const double bins = 11;
  
    struct hsh_iterator hi;
    for( i = 0 ; i < ft->n_valid ; ++i )
      {
        frq = &ft->valid[i];
 -      histogram_add ((struct histogram *)hist, frq->value.f, frq->count);
 +      histogram_add (hist, frq->value.f, frq->count);
      }
  
 -  return (struct histogram *)hist;
 +  return hist;
  }
  
  
@@@ -1516,7 -1494,7 +1519,7 @@@ freq_tab_to_slice_array(const struct fr
  
        ds_init_empty (&slices[i].label);
        var_append_value_name (var, &frq->value, &slices[i].label);
 -      slices[i].magnetude = frq->count;
 +      slices[i].magnitude = frq->count;
      }
  
    return slices;
@@@ -1533,12 -1511,14 +1536,12 @@@ do_piechart(const struct variable *var
  
    slices = freq_tab_to_slice_array(frq_tab, var, &n_slices);
  
 -  piechart_plot(var_to_string(var), slices, n_slices);
 +  chart_submit (piechart_create (var_to_string(var), slices, n_slices));
  
    for (i = 0 ; i < n_slices ; ++i )
 -    {
 -      ds_destroy (&slices[i].label);
 -    }
 +    ds_destroy (&slices[i].label);
  
 -  free(slices);
 +  free (slices);
  }
  
  
index 84fd9e5787e17f1d57555bee40e30df475b6a0ab,2c259d0f904c1834c7d7e1614fe4f47084bab5ee..34bdc6f0940724dd4420a1594b00514ad306de30
@@@ -149,8 -149,8 +149,8 @@@ reg_stats_r (pspp_linreg_cache * c
    rsq = c->ssm / c->sst;
    adjrsq = 1.0 - (1.0 - rsq) * (c->n_obs - 1.0) / (c->n_obs - c->n_indeps);
    std_error = sqrt (pspp_linreg_mse (c));
 -  t = tab_create (n_cols, n_rows, 0);
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  t = tab_create (n_cols, n_rows);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
    tab_box (t, TAL_2, TAL_2, -1, TAL_1, 0, 0, n_cols - 1, n_rows - 1);
    tab_hline (t, TAL_2, 0, n_cols - 1, 1);
    tab_vline (t, TAL_2, 2, 0, n_rows - 1);
@@@ -191,9 -191,9 +191,9 @@@ reg_stats_coeff (pspp_linreg_cache * c
    assert (c != NULL);
    n_rows = c->n_coeffs + 3;
  
 -  t = tab_create (n_cols, n_rows, 0);
 +  t = tab_create (n_cols, n_rows);
    tab_headers (t, 2, 0, 1, 0);
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
    tab_box (t, TAL_2, TAL_2, -1, TAL_1, 0, 0, n_cols - 1, n_rows - 1);
    tab_hline (t, TAL_2, 0, n_cols - 1, 1);
    tab_vline (t, TAL_2, 2, 0, n_rows - 1);
@@@ -288,9 -288,9 +288,9 @@@ reg_stats_anova (pspp_linreg_cache * c
    struct tab_table *t;
  
    assert (c != NULL);
 -  t = tab_create (n_cols, n_rows, 0);
 +  t = tab_create (n_cols, n_rows);
    tab_headers (t, 2, 0, 1, 0);
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
  
    tab_box (t, TAL_2, TAL_2, -1, TAL_1, 0, 0, n_cols - 1, n_rows - 1);
  
@@@ -379,9 -379,9 +379,9 @@@ reg_stats_bcov (pspp_linreg_cache * c
    assert (c != NULL);
    n_cols = c->n_indeps + 1 + 2;
    n_rows = 2 * (c->n_indeps + 1);
 -  t = tab_create (n_cols, n_rows, 0);
 +  t = tab_create (n_cols, n_rows);
    tab_headers (t, 2, 0, 1, 0);
 -  tab_dim (t, tab_natural_dimensions, NULL);
 +  tab_dim (t, tab_natural_dimensions, NULL, NULL);
    tab_box (t, TAL_2, TAL_2, -1, TAL_1, 0, 0, n_cols - 1, n_rows - 1);
    tab_hline (t, TAL_2, 0, n_cols - 1, 1);
    tab_vline (t, TAL_2, 2, 0, n_rows - 1);
@@@ -947,7 -947,7 +947,7 @@@ run_regression (struct casereader *inpu
              lopts.get_indep_mean_std[i] = 1;
            }
          models[k] = pspp_linreg_cache_alloc (dep_var, (const struct variable **) indep_vars,
-                                              X->m->size1, X->m->size2);
+                                              X->m->size1, n_indep);
          models[k]->depvar = dep_var;
          /*
             For large data sets, use QR decomposition.
diff --combined src/language/stats/roc.c
index 0000000000000000000000000000000000000000,1d61a55c57a3cad25c834c204c3571632d9df778..1d21a4f6a1221ed6b83f3c2bbb8d4e25c457c95a
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1227 +1,1179 @@@
 -#include <output/charts/plot-chart.h>
 -#include <output/charts/cartesian.h>
+ /* PSPP - a program for statistical analysis.
+    Copyright (C) 2009 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
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+ #include <config.h>
++#include <language/stats/roc.h>
++
+ #include <data/procedure.h>
+ #include <language/lexer/variable-parser.h>
+ #include <language/lexer/value-parser.h>
+ #include <language/command.h>
+ #include <language/lexer/lexer.h>
+ #include <data/casegrouper.h>
+ #include <data/casereader.h>
+ #include <data/casewriter.h>
+ #include <data/dictionary.h>
+ #include <data/format.h>
+ #include <math/sort.h>
+ #include <data/subcase.h>
+ #include <libpspp/misc.h>
+ #include <gsl/gsl_cdf.h>
+ #include <output/table.h>
 -#define CUTPOINT 0
 -#define TP 1
 -#define FN 2
 -#define TN 3
 -#define FP 4
 -
 -
++#include <output/chart.h>
++#include <output/charts/roc-chart.h>
+ #include "gettext.h"
+ #define _(msgid) gettext (msgid)
+ #define N_(msgid) msgid
+ struct cmd_roc
+ {
+   size_t n_vars;
+   const struct variable **vars;
+   const struct dictionary *dict;
+   const struct variable *state_var ;
+   union value state_value;
+   /* Plot the roc curve */
+   bool curve;
+   /* Plot the reference line */
+   bool reference;
+   double ci;
+   bool print_coords;
+   bool print_se;
+   bool bi_neg_exp; /* True iff the bi-negative exponential critieria
+                     should be used */
+   enum mv_class exclude;
+   bool invert ; /* True iff a smaller test result variable indicates
+                  a positive result */
+   double pos;
+   double neg;
+   double pos_weighted;
+   double neg_weighted;
+ };
+ static int run_roc (struct dataset *ds, struct cmd_roc *roc);
+ int
+ cmd_roc (struct lexer *lexer, struct dataset *ds)
+ {
+   struct cmd_roc roc ;
+   const struct dictionary *dict = dataset_dict (ds);
+   roc.vars = NULL;
+   roc.n_vars = 0;
+   roc.print_se = false;
+   roc.print_coords = false;
+   roc.exclude = MV_ANY;
+   roc.curve = true;
+   roc.reference = false;
+   roc.ci = 95;
+   roc.bi_neg_exp = false;
+   roc.invert = false;
+   roc.pos = roc.pos_weighted = 0;
+   roc.neg = roc.neg_weighted = 0;
+   roc.dict = dataset_dict (ds);
+   if (!parse_variables_const (lexer, dict, &roc.vars, &roc.n_vars,
+                             PV_APPEND | PV_NO_DUPLICATE | PV_NUMERIC))
+     goto error;
+   if ( ! lex_force_match (lexer, T_BY))
+     {
+       goto error;
+     }
+   roc.state_var = parse_variable (lexer, dict);
+   if ( !lex_force_match (lexer, '('))
+     {
+       goto error;
+     }
+   parse_value (lexer, &roc.state_value, var_get_width (roc.state_var));
+   if ( !lex_force_match (lexer, ')'))
+     {
+       goto error;
+     }
+   while (lex_token (lexer) != '.')
+     {
+       lex_match (lexer, '/');
+       if (lex_match_id (lexer, "MISSING"))
+         {
+           lex_match (lexer, '=');
+           while (lex_token (lexer) != '.' && lex_token (lexer) != '/')
+             {
+             if (lex_match_id (lexer, "INCLUDE"))
+               {
+                 roc.exclude = MV_SYSTEM;
+               }
+             else if (lex_match_id (lexer, "EXCLUDE"))
+               {
+                 roc.exclude = MV_ANY;
+               }
+             else
+               {
+                   lex_error (lexer, NULL);
+                 goto error;
+               }
+           }
+       }
+       else if (lex_match_id (lexer, "PLOT"))
+       {
+         lex_match (lexer, '=');
+         if (lex_match_id (lexer, "CURVE"))
+           {
+             roc.curve = true;
+             if (lex_match (lexer, '('))
+               {
+                 roc.reference = true;
+                 lex_force_match_id (lexer, "REFERENCE");
+                 lex_force_match (lexer, ')');
+               }
+           }
+         else if (lex_match_id (lexer, "NONE"))
+           {
+             roc.curve = false;
+           }
+         else
+           {
+             lex_error (lexer, NULL);
+             goto error;
+           }
+       }
+       else if (lex_match_id (lexer, "PRINT"))
+       {
+         lex_match (lexer, '=');
+           while (lex_token (lexer) != '.' && lex_token (lexer) != '/')
+           {
+             if (lex_match_id (lexer, "SE"))
+               {
+                 roc.print_se = true;
+               }
+             else if (lex_match_id (lexer, "COORDINATES"))
+               {
+                 roc.print_coords = true;
+               }
+             else
+               {
+                 lex_error (lexer, NULL);
+                 goto error;
+               }
+           }
+       }
+       else if (lex_match_id (lexer, "CRITERIA"))
+       {
+         lex_match (lexer, '=');
+           while (lex_token (lexer) != '.' && lex_token (lexer) != '/')
+           {
+             if (lex_match_id (lexer, "CUTOFF"))
+               {
+                 lex_force_match (lexer, '(');
+                 if (lex_match_id (lexer, "INCLUDE"))
+                   {
+                     roc.exclude = MV_SYSTEM;
+                   }
+                 else if (lex_match_id (lexer, "EXCLUDE"))
+                   {
+                     roc.exclude = MV_USER | MV_SYSTEM;
+                   }
+                 else
+                   {
+                     lex_error (lexer, NULL);
+                     goto error;
+                   }
+                 lex_force_match (lexer, ')');
+               }
+             else if (lex_match_id (lexer, "TESTPOS"))
+               {
+                 lex_force_match (lexer, '(');
+                 if (lex_match_id (lexer, "LARGE"))
+                   {
+                     roc.invert = false;
+                   }
+                 else if (lex_match_id (lexer, "SMALL"))
+                   {
+                     roc.invert = true;
+                   }
+                 else
+                   {
+                     lex_error (lexer, NULL);
+                     goto error;
+                   }
+                 lex_force_match (lexer, ')');
+               }
+             else if (lex_match_id (lexer, "CI"))
+               {
+                 lex_force_match (lexer, '(');
+                 lex_force_num (lexer);
+                 roc.ci = lex_number (lexer);
+                 lex_get (lexer);
+                 lex_force_match (lexer, ')');
+               }
+             else if (lex_match_id (lexer, "DISTRIBUTION"))
+               {
+                 lex_force_match (lexer, '(');
+                 if (lex_match_id (lexer, "FREE"))
+                   {
+                     roc.bi_neg_exp = false;
+                   }
+                 else if (lex_match_id (lexer, "NEGEXPO"))
+                   {
+                     roc.bi_neg_exp = true;
+                   }
+                 else
+                   {
+                     lex_error (lexer, NULL);
+                     goto error;
+                   }
+                 lex_force_match (lexer, ')');
+               }
+             else
+               {
+                 lex_error (lexer, NULL);
+                 goto error;
+               }
+           }
+       }
+       else
+       {
+         lex_error (lexer, NULL);
+         break;
+       }
+     }
+   if ( ! run_roc (ds, &roc)) 
+     goto error;
+   free (roc.vars);
+   return CMD_SUCCESS;
+  error:
+   free (roc.vars);
+   return CMD_FAILURE;
+ }
+ static void
+ do_roc (struct cmd_roc *roc, struct casereader *group, struct dictionary *dict);
+ static int
+ run_roc (struct dataset *ds, struct cmd_roc *roc)
+ {
+   struct dictionary *dict = dataset_dict (ds);
+   bool ok;
+   struct casereader *group;
+   struct casegrouper *grouper = casegrouper_create_splits (proc_open (ds), dict);
+   while (casegrouper_get_next_group (grouper, &group))
+     {
+       do_roc (roc, group, dataset_dict (ds));
+     }
+   ok = casegrouper_destroy (grouper);
+   ok = proc_commit (ds) && ok;
+   return ok;
+ }
+ #if 0
+ static void
+ dump_casereader (struct casereader *reader)
+ {
+   struct ccase *c;
+   struct casereader *r = casereader_clone (reader);
+   for ( ; (c = casereader_read (r) ); case_unref (c))
+     {
+       int i;
+       for (i = 0 ; i < case_get_value_cnt (c); ++i)
+       {
+         printf ("%g ", case_data_idx (c, i)->f);
+       }
+       printf ("\n");
+     }
+   casereader_destroy (r);
+ }
+ #endif
+ /* 
+    Return true iff the state variable indicates that C has positive actual state.
+    As a side effect, this function also accumulates the roc->{pos,neg} and 
+    roc->{pos,neg}_weighted counts.
+  */
+ static bool
+ match_positives (const struct ccase *c, void *aux)
+ {
+   struct cmd_roc *roc = aux;
+   const struct variable *wv = dict_get_weight (roc->dict);
+   const double weight = wv ? case_data (c, wv)->f : 1.0;
+   const bool positive =
+   ( 0 == value_compare_3way (case_data (c, roc->state_var), &roc->state_value,
+     var_get_width (roc->state_var)));
+   if ( positive )
+     {
+       roc->pos++;
+       roc->pos_weighted += weight;
+     }
+   else
+     {
+       roc->neg++;
+       roc->neg_weighted += weight;
+     }
+   return positive;
+ }
+ #define VALUE  0
+ #define N_EQ   1
+ #define N_PRED 2
+ /* Some intermediate state for calculating the cutpoints and the 
+    standard error values */
+ struct roc_state
+ {
+   double auc;  /* Area under the curve */
+   double n1;  /* total weight of positives */
+   double n2;  /* total weight of negatives */
+   /* intermediates for standard error */
+   double q1hat; 
+   double q2hat;
+   /* intermediates for cutpoints */
+   struct casewriter *cutpoint_wtr;
+   struct casereader *cutpoint_rdr;
+   double prev_result;
+   double min;
+   double max;
+ };
 -      const double cp = case_data_idx (cpc, CUTPOINT)->f;
+ /* 
+    Return a new casereader based upon CUTPOINT_RDR.
+    The number of "positive" cases are placed into
+    the position TRUE_INDEX, and the number of "negative" cases
+    into FALSE_INDEX.
+    POS_COND and RESULT determine the semantics of what is 
+    "positive".
+    WEIGHT is the value of a single count.
+  */
+ static struct casereader *
+ accumulate_counts (struct casereader *cutpoint_rdr, 
+                  double result, double weight, 
+                  bool (*pos_cond) (double, double),
+                  int true_index, int false_index)
+ {
+   const struct caseproto *proto = casereader_get_proto (cutpoint_rdr);
+   struct casewriter *w =
+     autopaging_writer_create (proto);
+   struct casereader *r = casereader_clone (cutpoint_rdr);
+   struct ccase *cpc;
+   double prev_cp = SYSMIS;
+   for ( ; (cpc = casereader_read (r) ); case_unref (cpc))
+     {
+       struct ccase *new_case;
 -                      TP, FN);
++      const double cp = case_data_idx (cpc, ROC_CUTPOINT)->f;
+       assert (cp != SYSMIS);
+       /* We don't want duplicates here */
+       if ( cp == prev_cp )
+       continue;
+       new_case = case_clone (cpc);
+       if ( pos_cond (result, cp))
+       case_data_rw_idx (new_case, true_index)->f += weight;
+       else
+       case_data_rw_idx (new_case, false_index)->f += weight;
+       prev_cp = cp;
+       casewriter_write (w, new_case);
+     }
+   casereader_destroy (r);
+   return casewriter_make_reader (w);
+ }
+ static void output_roc (struct roc_state *rs, const struct cmd_roc *roc);
+ /*
+   This function does 3 things:
+   1. Counts the number of cases which are equal to every other case in READER,
+   and those cases for which the relationship between it and every other case
+   satifies PRED (normally either > or <).  VAR is variable defining a case's value
+   for this purpose.
+   2. Counts the number of true and false cases in reader, and populates
+   CUTPOINT_RDR accordingly.  TRUE_INDEX and FALSE_INDEX are the indices
+   which receive these values.  POS_COND is the condition defining true
+   and false.
+   
+   3. CC is filled with the cumulative weight of all cases of READER.
+ */
+ static struct casereader *
+ process_group (const struct variable *var, struct casereader *reader,
+              bool (*pred) (double, double),
+              const struct dictionary *dict,
+              double *cc,
+              struct casereader **cutpoint_rdr, 
+              bool (*pos_cond) (double, double),
+              int true_index,
+              int false_index)
+ {
+   const struct variable *w = dict_get_weight (dict);
+   struct casereader *r1 =
+     casereader_create_distinct (sort_execute_1var (reader, var), var, w);
+   const int weight_idx  = w ? var_get_case_index (w) :
+     caseproto_get_n_widths (casereader_get_proto (r1)) - 1;
+   
+   struct ccase *c1;
+   struct casereader *rclone = casereader_clone (r1);
+   struct casewriter *wtr;
+   struct caseproto *proto = caseproto_create ();
+   proto = caseproto_add_width (proto, 0);
+   proto = caseproto_add_width (proto, 0);
+   proto = caseproto_add_width (proto, 0);
+   wtr = autopaging_writer_create (proto);  
+   *cc = 0;
+   for ( ; (c1 = casereader_read (r1) ); case_unref (c1))
+     {
+       struct ccase *new_case = case_create (proto);
+       struct ccase *c2;
+       struct casereader *r2 = casereader_clone (rclone);
+       const double weight1 = case_data_idx (c1, weight_idx)->f;
+       const double d1 = case_data (c1, var)->f;
+       double n_eq = 0.0;
+       double n_pred = 0.0;
+       *cutpoint_rdr = accumulate_counts (*cutpoint_rdr, d1, weight1,
+                                        pos_cond,
+                                        true_index, false_index);
+       *cc += weight1;
+       for ( ; (c2 = casereader_read (r2) ); case_unref (c2))
+       {
+         const double d2 = case_data (c2, var)->f;
+         const double weight2 = case_data_idx (c2, weight_idx)->f;
+         if ( d1 == d2 )
+           {
+             n_eq += weight2;
+             continue;
+           }
+         else  if ( pred (d2, d1))
+           {
+             n_pred += weight2;
+           }
+       }
+       case_data_rw_idx (new_case, VALUE)->f = d1;
+       case_data_rw_idx (new_case, N_EQ)->f = n_eq;
+       case_data_rw_idx (new_case, N_PRED)->f = n_pred;
+       casewriter_write (wtr, new_case);
+       casereader_destroy (r2);
+     }
+   casereader_destroy (r1);
+   casereader_destroy (rclone);
+   return casewriter_make_reader (wtr);
+ }
+ /* Some more indeces into case data */
+ #define N_POS_EQ 1  /* number of positive cases with values equal to n */
+ #define N_POS_GT 2  /* number of postive cases with values greater than n */
+ #define N_NEG_EQ 3  /* number of negative cases with values equal to n */
+ #define N_NEG_LT 4  /* number of negative cases with values less than n */
+ static bool
+ gt (double d1, double d2)
+ {
+   return d1 > d2;
+ }
+ static bool
+ ge (double d1, double d2)
+ {
+   return d1 > d2;
+ }
+ static bool
+ lt (double d1, double d2)
+ {
+   return d1 < d2;
+ }
+ /*
+   Return a casereader with width 3,
+   populated with cases based upon READER.
+   The cases will have the values:
+   (N, number of cases equal to N, number of cases greater than N)
+   As a side effect, update RS->n1 with the number of positive cases.
+ */
+ static struct casereader *
+ process_positive_group (const struct variable *var, struct casereader *reader,
+                       const struct dictionary *dict,
+                       struct roc_state *rs)
+ {
+   return process_group (var, reader, gt, dict, &rs->n1,
+                       &rs->cutpoint_rdr,
+                       ge,
 -                      TN, FP);
++                      ROC_TP, ROC_FN);
+ }
+ /*
+   Return a casereader with width 3,
+   populated with cases based upon READER.
+   The cases will have the values:
+   (N, number of cases equal to N, number of cases less than N)
+   As a side effect, update RS->n2 with the number of negative cases.
+ */
+ static struct casereader *
+ process_negative_group (const struct variable *var, struct casereader *reader,
+                       const struct dictionary *dict,
+                       struct roc_state *rs)
+ {
+   return process_group (var, reader, lt, dict, &rs->n2,
+                       &rs->cutpoint_rdr,
+                       lt,
 -  case_data_rw_idx (cc, CUTPOINT)->f = cutpoint;
 -  case_data_rw_idx (cc, TP)->f = 0;
 -  case_data_rw_idx (cc, FN)->f = 0;
 -  case_data_rw_idx (cc, TN)->f = 0;
 -  case_data_rw_idx (cc, FP)->f = 0;
++                      ROC_TN, ROC_FP);
+ }
+ static void
+ append_cutpoint (struct casewriter *writer, double cutpoint)
+ {
+   struct ccase *cc = case_create (casewriter_get_proto (writer));
 -   be created with width 5, ready to take the values (cutpoint, TP, FN, TN, FP), and the
++  case_data_rw_idx (cc, ROC_CUTPOINT)->f = cutpoint;
++  case_data_rw_idx (cc, ROC_TP)->f = 0;
++  case_data_rw_idx (cc, ROC_FN)->f = 0;
++  case_data_rw_idx (cc, ROC_TN)->f = 0;
++  case_data_rw_idx (cc, ROC_FP)->f = 0;
+   casewriter_write (writer, cc);
+ }
+ /* 
+    Create and initialise the rs[x].cutpoint_rdr casereaders.  That is, the readers will
 -   However on exit from this function, only CUTPOINT entries will be set to their final
++   be created with width 5, ready to take the values (cutpoint, ROC_TP, ROC_FN, ROC_TN, ROC_FP), and the
+    reader will be populated with its final number of cases.
 -  subcase_init (&ordering, CUTPOINT, 0, SC_ASCEND);
++   However on exit from this function, only ROC_CUTPOINT entries will be set to their final
+    value.  The other entries will be initialised to zero.
+ */
+ static void
+ prepare_cutpoints (struct cmd_roc *roc, struct roc_state *rs, struct casereader *input)
+ {
+   int i;
+   struct casereader *r = casereader_clone (input);
+   struct ccase *c;
+   struct caseproto *proto = caseproto_create ();
+   struct subcase ordering;
 -  proto = caseproto_add_width (proto, 0); /* TP */
 -  proto = caseproto_add_width (proto, 0); /* FN */
 -  proto = caseproto_add_width (proto, 0); /* TN */
 -  proto = caseproto_add_width (proto, 0); /* FP */
++  subcase_init (&ordering, ROC_CUTPOINT, 0, SC_ASCEND);
+   proto = caseproto_add_width (proto, 0); /* cutpoint */
 -  struct tab_table *tbl = tab_create (n_cols, n_rows, 0);
++  proto = caseproto_add_width (proto, 0); /* ROC_TP */
++  proto = caseproto_add_width (proto, 0); /* ROC_FN */
++  proto = caseproto_add_width (proto, 0); /* ROC_TN */
++  proto = caseproto_add_width (proto, 0); /* ROC_FP */
+   for (i = 0 ; i < roc->n_vars; ++i)
+     {
+       rs[i].cutpoint_wtr = sort_create_writer (&ordering, proto);
+       rs[i].prev_result = SYSMIS;
+       rs[i].max = -DBL_MAX;
+       rs[i].min = DBL_MAX;
+     }
+   for (; (c = casereader_read (r)) != NULL; case_unref (c))
+     {
+       for (i = 0 ; i < roc->n_vars; ++i)
+       {
+         const union value *v = case_data (c, roc->vars[i]); 
+         const double result = v->f;
+         if ( mv_is_value_missing (var_get_missing_values (roc->vars[i]), v, roc->exclude))
+           continue;
+         minimize (&rs[i].min, result);
+         maximize (&rs[i].max, result);
+         if ( rs[i].prev_result != SYSMIS && rs[i].prev_result != result )
+           {
+             const double mean = (result + rs[i].prev_result ) / 2.0;
+             append_cutpoint (rs[i].cutpoint_wtr, mean);
+           }
+         rs[i].prev_result = result;
+       }
+     }
+   casereader_destroy (r);
+   /* Append the min and max cutpoints */
+   for (i = 0 ; i < roc->n_vars; ++i)
+     {
+       append_cutpoint (rs[i].cutpoint_wtr, rs[i].min - 1);
+       append_cutpoint (rs[i].cutpoint_wtr, rs[i].max + 1);
+       rs[i].cutpoint_rdr = casewriter_make_reader (rs[i].cutpoint_wtr);
+     }
+ }
+ static void
+ do_roc (struct cmd_roc *roc, struct casereader *reader, struct dictionary *dict)
+ {
+   int i;
+   struct roc_state *rs = xcalloc (roc->n_vars, sizeof *rs);
+   struct casereader *negatives = NULL;
+   struct casereader *positives = NULL;
+   struct caseproto *n_proto = caseproto_create ();
+   struct subcase up_ordering;
+   struct subcase down_ordering;
+   struct casewriter *neg_wtr = NULL;
+   struct casereader *input = casereader_create_filter_missing (reader,
+                                                              roc->vars, roc->n_vars,
+                                                              roc->exclude,
+                                                              NULL,
+                                                              NULL);
+   input = casereader_create_filter_missing (input,
+                                           &roc->state_var, 1,
+                                           roc->exclude,
+                                           NULL,
+                                           NULL);
+   neg_wtr = autopaging_writer_create (casereader_get_proto (input));
+   prepare_cutpoints (roc, rs, input);
+   /* Separate the positive actual state cases from the negative ones */
+   positives = 
+     casereader_create_filter_func (input,
+                                  match_positives,
+                                  NULL,
+                                  roc,
+                                  neg_wtr);
+   n_proto = caseproto_create ();
+       
+   n_proto = caseproto_add_width (n_proto, 0);
+   n_proto = caseproto_add_width (n_proto, 0);
+   n_proto = caseproto_add_width (n_proto, 0);
+   n_proto = caseproto_add_width (n_proto, 0);
+   n_proto = caseproto_add_width (n_proto, 0);
+   subcase_init (&up_ordering, VALUE, 0, SC_ASCEND);
+   subcase_init (&down_ordering, VALUE, 0, SC_DESCEND);
+   for (i = 0 ; i < roc->n_vars; ++i)
+     {
+       struct casewriter *w = NULL;
+       struct casereader *r = NULL;
+       struct ccase *c;
+       struct ccase *cpos;
+       struct casereader *n_neg ;
+       const struct variable *var = roc->vars[i];
+       struct casereader *neg ;
+       struct casereader *pos = casereader_clone (positives);
+       struct casereader *n_pos =
+       process_positive_group (var, pos, dict, &rs[i]);
+       if ( negatives == NULL)
+       {
+         negatives = casewriter_make_reader (neg_wtr);
+       }
+       neg = casereader_clone (negatives);
+       n_neg = process_negative_group (var, neg, dict, &rs[i]);
+       /* Merge the n_pos and n_neg casereaders */
+       w = sort_create_writer (&up_ordering, n_proto);
+       for ( ; (cpos = casereader_read (n_pos) ); case_unref (cpos))
+       {
+         struct ccase *pos_case = case_create (n_proto);
+         struct ccase *cneg;
+         const double jpos = case_data_idx (cpos, VALUE)->f;
+         while ((cneg = casereader_read (n_neg)))
+           {
+             struct ccase *nc = case_create (n_proto);
+             const double jneg = case_data_idx (cneg, VALUE)->f;
+             case_data_rw_idx (nc, VALUE)->f = jneg;
+             case_data_rw_idx (nc, N_POS_EQ)->f = 0;
+             case_data_rw_idx (nc, N_POS_GT)->f = SYSMIS;
+             *case_data_rw_idx (nc, N_NEG_EQ) = *case_data_idx (cneg, N_EQ);
+             *case_data_rw_idx (nc, N_NEG_LT) = *case_data_idx (cneg, N_PRED);
+             casewriter_write (w, nc);
+             case_unref (cneg);
+             if ( jneg > jpos)
+               break;
+           }
+         case_data_rw_idx (pos_case, VALUE)->f = jpos;
+         *case_data_rw_idx (pos_case, N_POS_EQ) = *case_data_idx (cpos, N_EQ);
+         *case_data_rw_idx (pos_case, N_POS_GT) = *case_data_idx (cpos, N_PRED);
+         case_data_rw_idx (pos_case, N_NEG_EQ)->f = 0;
+         case_data_rw_idx (pos_case, N_NEG_LT)->f = SYSMIS;
+         casewriter_write (w, pos_case);
+       }
+ /* These aren't used anymore */
+ #undef N_EQ
+ #undef N_PRED
+       r = casewriter_make_reader (w);
+       /* Propagate the N_POS_GT values from the positive cases
+        to the negative ones */
+       {
+       double prev_pos_gt = rs[i].n1;
+       w = sort_create_writer (&down_ordering, n_proto);
+       for ( ; (c = casereader_read (r) ); case_unref (c))
+         {
+           double n_pos_gt = case_data_idx (c, N_POS_GT)->f;
+           struct ccase *nc = case_clone (c);
+           if ( n_pos_gt == SYSMIS)
+             {
+               n_pos_gt = prev_pos_gt;
+               case_data_rw_idx (nc, N_POS_GT)->f = n_pos_gt;
+             }
+           
+           casewriter_write (w, nc);
+           prev_pos_gt = n_pos_gt;
+         }
+       r = casewriter_make_reader (w);
+       }
+       /* Propagate the N_NEG_LT values from the negative cases
+        to the positive ones */
+       {
+       double prev_neg_lt = rs[i].n2;
+       w = sort_create_writer (&up_ordering, n_proto);
+       for ( ; (c = casereader_read (r) ); case_unref (c))
+         {
+           double n_neg_lt = case_data_idx (c, N_NEG_LT)->f;
+           struct ccase *nc = case_clone (c);
+           if ( n_neg_lt == SYSMIS)
+             {
+               n_neg_lt = prev_neg_lt;
+               case_data_rw_idx (nc, N_NEG_LT)->f = n_neg_lt;
+             }
+           
+           casewriter_write (w, nc);
+           prev_neg_lt = n_neg_lt;
+         }
+       r = casewriter_make_reader (w);
+       }
+       {
+       struct ccase *prev_case = NULL;
+       for ( ; (c = casereader_read (r) ); case_unref (c))
+         {
+           const struct ccase *next_case = casereader_peek (r, 0);
+           const double j = case_data_idx (c, VALUE)->f;
+           double n_pos_eq = case_data_idx (c, N_POS_EQ)->f;
+           double n_pos_gt = case_data_idx (c, N_POS_GT)->f;
+           double n_neg_eq = case_data_idx (c, N_NEG_EQ)->f;
+           double n_neg_lt = case_data_idx (c, N_NEG_LT)->f;
+           if ( prev_case && j == case_data_idx (prev_case, VALUE)->f)
+             {
+               if ( 0 ==  case_data_idx (c, N_POS_EQ)->f)
+                 {
+                   n_pos_eq = case_data_idx (prev_case, N_POS_EQ)->f;
+                   n_pos_gt = case_data_idx (prev_case, N_POS_GT)->f;
+                 }
+               if ( 0 ==  case_data_idx (c, N_NEG_EQ)->f)
+                 {
+                   n_neg_eq = case_data_idx (prev_case, N_NEG_EQ)->f;
+                   n_neg_lt = case_data_idx (prev_case, N_NEG_LT)->f;
+                 }
+             }
+           if ( NULL == next_case || j != case_data_idx (next_case, VALUE)->f)
+             {
+               rs[i].auc += n_pos_gt * n_neg_eq + (n_pos_eq * n_neg_eq) / 2.0;
+               rs[i].q1hat +=
+                 n_neg_eq * ( pow2 (n_pos_gt) + n_pos_gt * n_pos_eq + pow2 (n_pos_eq) / 3.0);
+               rs[i].q2hat +=
+                 n_pos_eq * ( pow2 (n_neg_lt) + n_neg_lt * n_neg_eq + pow2 (n_neg_eq) / 3.0);
+             }
+           case_unref (prev_case);
+           prev_case = case_clone (c);
+         }
+       rs[i].auc /=  rs[i].n1 * rs[i].n2; 
+       if ( roc->invert ) 
+         rs[i].auc = 1 - rs[i].auc;
+       if ( roc->bi_neg_exp )
+         {
+           rs[i].q1hat = rs[i].auc / ( 2 - rs[i].auc);
+           rs[i].q2hat = 2 * pow2 (rs[i].auc) / ( 1 + rs[i].auc);
+         }
+       else
+         {
+           rs[i].q1hat /= rs[i].n2 * pow2 (rs[i].n1);
+           rs[i].q2hat /= rs[i].n1 * pow2 (rs[i].n2);
+         }
+       }
+     }
+   casereader_destroy (positives);
+   casereader_destroy (negatives);
+   output_roc (rs, roc);
+   free (rs);
+ }
+ static void
+ show_auc  (struct roc_state *rs, const struct cmd_roc *roc)
+ {
+   int i;
+   const int n_fields = roc->print_se ? 5 : 1;
+   const int n_cols = roc->n_vars > 1 ? n_fields + 1: n_fields;
+   const int n_rows = 2 + roc->n_vars;
 -  tab_dim (tbl, tab_natural_dimensions, NULL);
++  struct tab_table *tbl = tab_create (n_cols, n_rows);
+   if ( roc->n_vars > 1)
+     tab_title (tbl, _("Area Under the Curve"));
+   else
+     tab_title (tbl, _("Area Under the Curve (%s)"), var_to_string (roc->vars[0]));
+   tab_headers (tbl, n_cols - n_fields, 0, 1, 0);
 -  struct tab_table *tbl = tab_create (n_cols, n_rows, 0);
++  tab_dim (tbl, tab_natural_dimensions, NULL, NULL);
+   tab_text (tbl, n_cols - n_fields, 1, TAT_TITLE, _("Area"));
+   tab_hline (tbl, TAL_2, 0, n_cols - 1, 2);
+   tab_box (tbl,
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1,
+          n_rows - 1);
+   if ( roc->print_se )
+     {
+       tab_text (tbl, n_cols - 4, 1, TAT_TITLE, _("Std. Error"));
+       tab_text (tbl, n_cols - 3, 1, TAT_TITLE, _("Asymptotic Sig."));
+       tab_text (tbl, n_cols - 2, 1, TAT_TITLE, _("Lower Bound"));
+       tab_text (tbl, n_cols - 1, 1, TAT_TITLE, _("Upper Bound"));
+       tab_joint_text_format (tbl, n_cols - 2, 0, 4, 0,
+                            TAT_TITLE | TAB_CENTER,
+                            _("Asymp. %g%% Confidence Interval"), roc->ci);
+       tab_vline (tbl, 0, n_cols - 1, 0, 0);
+       tab_hline (tbl, TAL_1, n_cols - 2, n_cols - 1, 1);
+     }
+   if ( roc->n_vars > 1)
+     tab_text (tbl, 0, 1, TAT_TITLE, _("Variable under test"));
+   if ( roc->n_vars > 1)
+     tab_vline (tbl, TAL_2, 1, 0, n_rows - 1);
+   for ( i = 0 ; i < roc->n_vars ; ++i )
+     {
+       tab_text (tbl, 0, 2 + i, TAT_TITLE, var_to_string (roc->vars[i]));
+       tab_double (tbl, n_cols - n_fields, 2 + i, 0, rs[i].auc, NULL);
+       if ( roc->print_se )
+       {
+         double se ;
+         const double sd_0_5 = sqrt ((rs[i].n1 + rs[i].n2 + 1) /
+                                     (12 * rs[i].n1 * rs[i].n2));
+         double ci ;
+         double yy ;
+         se = rs[i].auc * (1 - rs[i].auc) + (rs[i].n1 - 1) * (rs[i].q1hat - pow2 (rs[i].auc)) +
+           (rs[i].n2 - 1) * (rs[i].q2hat - pow2 (rs[i].auc));
+         se /= rs[i].n1 * rs[i].n2;
+         se = sqrt (se);
+         tab_double (tbl, n_cols - 4, 2 + i, 0,
+                     se,
+                     NULL);
+         ci = 1 - roc->ci / 100.0;
+         yy = gsl_cdf_gaussian_Qinv (ci, se) ;
+         tab_double (tbl, n_cols - 2, 2 + i, 0,
+                     rs[i].auc - yy,
+                     NULL);
+         tab_double (tbl, n_cols - 1, 2 + i, 0,
+                     rs[i].auc + yy,
+                     NULL);
+         tab_double (tbl, n_cols - 3, 2 + i, 0,
+                     2.0 * gsl_cdf_ugaussian_Q (fabs ((rs[i].auc - 0.5 ) / sd_0_5)),
+                     NULL);
+       }
+     }
+   tab_submit (tbl);
+ }
+ static void
+ show_summary (const struct cmd_roc *roc)
+ {
+   const int n_cols = 3;
+   const int n_rows = 4;
 -  tab_dim (tbl, tab_natural_dimensions, NULL);
++  struct tab_table *tbl = tab_create (n_cols, n_rows);
+   tab_title (tbl, _("Case Summary"));
+   tab_headers (tbl, 1, 0, 2, 0);
 -  tbl = tab_create (n_cols, n_rows, 0);
++  tab_dim (tbl, tab_natural_dimensions, NULL, NULL);
+   tab_box (tbl,
+          TAL_2, TAL_2,
+          -1, -1,
+          0, 0,
+          n_cols - 1,
+          n_rows - 1);
+   tab_hline (tbl, TAL_2, 0, n_cols - 1, 2);
+   tab_vline (tbl, TAL_2, 1, 0, n_rows - 1);
+   tab_hline (tbl, TAL_2, 1, n_cols - 1, 1);
+   tab_vline (tbl, TAL_1, 2, 1, n_rows - 1);
+   tab_text (tbl, 0, 1, TAT_TITLE | TAB_LEFT, var_to_string (roc->state_var));
+   tab_text (tbl, 1, 1, TAT_TITLE, _("Unweighted"));
+   tab_text (tbl, 2, 1, TAT_TITLE, _("Weighted"));
+   tab_joint_text (tbl, 1, 0, 2, 0,
+                 TAT_TITLE | TAB_CENTER,
+                 _("Valid N (listwise)"));
+   tab_text (tbl, 0, 2, TAB_LEFT, _("Positive"));
+   tab_text (tbl, 0, 3, TAB_LEFT, _("Negative"));
+   tab_double (tbl, 1, 2, 0, roc->pos, &F_8_0);
+   tab_double (tbl, 1, 3, 0, roc->neg, &F_8_0);
+   tab_double (tbl, 2, 2, 0, roc->pos_weighted, 0);
+   tab_double (tbl, 2, 3, 0, roc->neg_weighted, 0);
+   tab_submit (tbl);
+ }
+ static void
+ show_coords (struct roc_state *rs, const struct cmd_roc *roc)
+ {
+   int x = 1;
+   int i;
+   const int n_cols = roc->n_vars > 1 ? 4 : 3;
+   int n_rows = 1;
+   struct tab_table *tbl ;
+   for (i = 0; i < roc->n_vars; ++i)
+     n_rows += casereader_count_cases (rs[i].cutpoint_rdr);
 -  tab_dim (tbl, tab_natural_dimensions, NULL);
++  tbl = tab_create (n_cols, n_rows);
+   if ( roc->n_vars > 1)
+     tab_title (tbl, _("Coordinates of the Curve"));
+   else
+     tab_title (tbl, _("Coordinates of the Curve (%s)"), var_to_string (roc->vars[0]));
+   tab_headers (tbl, 1, 0, 1, 0);
 -        const double se = case_data_idx (cc, TP)->f /
++  tab_dim (tbl, tab_natural_dimensions, NULL, NULL);
+   tab_hline (tbl, TAL_2, 0, n_cols - 1, 1);
+   if ( roc->n_vars > 1)
+     tab_text (tbl, 0, 0, TAT_TITLE, _("Test variable"));
+   tab_text (tbl, n_cols - 3, 0, TAT_TITLE, _("Positive if greater than or equal to"));
+   tab_text (tbl, n_cols - 2, 0, TAT_TITLE, _("Sensitivity"));
+   tab_text (tbl, n_cols - 1, 0, TAT_TITLE, _("1 - Specificity"));
+   tab_box (tbl,
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1,
+          n_rows - 1);
+   if ( roc->n_vars > 1)
+     tab_vline (tbl, TAL_2, 1, 0, n_rows - 1);
+   for (i = 0; i < roc->n_vars; ++i)
+     {
+       struct ccase *cc;
+       struct casereader *r = casereader_clone (rs[i].cutpoint_rdr);
+       if ( roc->n_vars > 1)
+       tab_text (tbl, 0, x, TAT_TITLE, var_to_string (roc->vars[i]));
+       if ( i > 0)
+       tab_hline (tbl, TAL_1, 0, n_cols - 1, x);
+       for (; (cc = casereader_read (r)) != NULL;
+          case_unref (cc), x++)
+       {
 -           case_data_idx (cc, TP)->f
++        const double se = case_data_idx (cc, ROC_TP)->f /
+           (
 -           case_data_idx (cc, FN)->f
++           case_data_idx (cc, ROC_TP)->f
+            +
 -        const double sp = case_data_idx (cc, TN)->f /
++           case_data_idx (cc, ROC_FN)->f
+            );
 -           case_data_idx (cc, TN)->f
++        const double sp = case_data_idx (cc, ROC_TN)->f /
+           (
 -           case_data_idx (cc, FP)->f
++           case_data_idx (cc, ROC_TN)->f
+            +
 -        tab_double (tbl, n_cols - 3, x, 0, case_data_idx (cc, CUTPOINT)->f,
++           case_data_idx (cc, ROC_FP)->f
+            );
 -static void
 -draw_roc (struct roc_state *rs, const struct cmd_roc *roc)
 -{
 -  int i;
 -
 -  struct chart *roc_chart = chart_create ();
 -
 -  chart_write_title (roc_chart, _("ROC Curve"));
 -  chart_write_xlabel (roc_chart, _("1 - Specificity"));
 -  chart_write_ylabel (roc_chart, _("Sensitivity"));
 -
 -  chart_write_xscale (roc_chart, 0, 1, 5);
 -  chart_write_yscale (roc_chart, 0, 1, 5);
 -
 -  if ( roc->reference )
 -    {
 -      chart_line (roc_chart, 1.0, 0,
 -                0.0, 1.0,
 -                CHART_DIM_X);
 -    }
 -
 -  for (i = 0; i < roc->n_vars; ++i)
 -    {
 -      struct ccase *cc;
 -      struct casereader *r = casereader_clone (rs[i].cutpoint_rdr);
 -
 -      chart_vector_start (roc_chart, var_get_name (roc->vars[i]));
 -      for (; (cc = casereader_read (r)) != NULL;
 -         case_unref (cc))
 -      {
 -        double se = case_data_idx (cc, TP)->f;
 -        double sp = case_data_idx (cc, TN)->f;
 -
 -        se /= case_data_idx (cc, FN)->f +
 -          case_data_idx (cc, TP)->f ;
 -
 -        sp /= case_data_idx (cc, TN)->f +
 -          case_data_idx (cc, FP)->f ;
 -
 -        chart_vector (roc_chart, 1 - sp, se);
 -      }
 -      chart_vector_end (roc_chart);
 -      casereader_destroy (r);
 -    }
 -
 -  chart_write_legend (roc_chart);
 -
 -  chart_submit (roc_chart);
 -}
 -
 -
++        tab_double (tbl, n_cols - 3, x, 0, case_data_idx (cc, ROC_CUTPOINT)->f,
+                     var_get_print_format (roc->vars[i]));
+         tab_double (tbl, n_cols - 2, x, 0, se, NULL);
+         tab_double (tbl, n_cols - 1, x, 0, 1 - sp, NULL);
+       }
+       casereader_destroy (r);
+     }
+   tab_submit (tbl);
+ }
 -    draw_roc (rs, roc);
+ static void
+ output_roc (struct roc_state *rs, const struct cmd_roc *roc)
+ {
+   show_summary (roc);
+   if ( roc->curve )
 -
++    {
++      struct roc_chart *rc;
++      size_t i;
++
++      rc = roc_chart_create (roc->reference);
++      for (i = 0; i < roc->n_vars; i++)
++        roc_chart_add_var (rc, var_get_name (roc->vars[i]),
++                           rs[i].cutpoint_rdr);
++      chart_submit (roc_chart_get_chart (rc));
++    }
+   show_auc (rs, roc);
+   if ( roc->print_coords )
+     show_coords (rs, roc);
+ }
diff --combined src/language/stats/roc.h
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5d63c96fb4e5872ff10eede534f35105c9176fb5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,28 @@@
++/* PSPP - a program for statistical analysis.
++   Copyright (C) 2009 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
++   the Free Software Foundation, either version 3 of the License, or
++   (at your option) any later version.
++
++   This program is distributed in the hope that it will be useful,
++   but WITHOUT ANY WARRANTY; without even the implied warranty of
++   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++   GNU General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
++
++#ifndef LANGUAGE_STATS_ROC_H
++#define LANGUAGE_STATS_ROC_H 1
++
++/* These are case indexes into the cutpoint case readers for ROC
++   output, used by roc.c and roc-chart.c. */
++#define ROC_CUTPOINT 0
++#define ROC_TP 1
++#define ROC_FN 2
++#define ROC_TN 3
++#define ROC_FP 4
++
++#endif /* language/stats/roc.h */
index 3d47bb81f8d7b0bfcfbb648e9fe2f49fb873af38,d02cdb28d21597a087f51640762de88573c33a3b..8d2b2122d1bda6eca09682ba5c98a6df4c5f31b2
@@@ -475,13 -475,13 +475,13 @@@ static voi
  ssbox_base_init (struct ssbox *this, int cols, int rows)
  {
    this->finalize = ssbox_base_finalize;
 -  this->t = tab_create (cols, rows, 0);
 +  this->t = tab_create (cols, rows);
  
 -  tab_columns (this->t, SOM_COL_DOWN, 1);
 +  tab_columns (this->t, SOM_COL_DOWN);
    tab_headers (this->t, 0, 0, 1, 0);
    tab_box (this->t, TAL_2, TAL_2, TAL_0, TAL_1, 0, 0, cols - 1, rows - 1);
    tab_hline (this->t, TAL_2, 0, cols- 1, 1);
 -  tab_dim (this->t, tab_natural_dimensions, NULL);
 +  tab_dim (this->t, tab_natural_dimensions, NULL, NULL);
  }
  \f
  /* ssbox implementations. */
@@@ -974,8 -974,8 +974,8 @@@ trbox_paired_populate (struct trbox *tr
        /* Degrees of freedom */
        tab_double (trb->t, 8, i + 3, TAB_RIGHT, df, &proc->weight_format);
  
-       p = gsl_cdf_tdist_P (t, df);
-       q = gsl_cdf_tdist_P (t, df);
+       p = gsl_cdf_tdist_P (t,df);
+       q = gsl_cdf_tdist_Q (t,df);
  
        tab_double (trb->t, 9, i + 3, TAB_RIGHT, 2.0 * (t > 0 ? q : p), NULL);
      }
@@@ -1067,11 -1067,11 +1067,11 @@@ trbox_base_init (struct trbox *self, si
    const size_t rows = 3 + data_rows;
  
    self->finalize = trbox_base_finalize;
 -  self->t = tab_create (cols, rows, 0);
 +  self->t = tab_create (cols, rows);
    tab_headers (self->t, 0, 0, 3, 0);
    tab_box (self->t, TAL_2, TAL_2, TAL_0, TAL_0, 0, 0, cols - 1, rows - 1);
    tab_hline (self->t, TAL_2, 0, cols- 1, 3);
 -  tab_dim (self->t, tab_natural_dimensions, NULL);
 +  tab_dim (self->t, tab_natural_dimensions, NULL, NULL);
  }
  
  /* Base finalizer for the trbox */
@@@ -1091,14 -1091,14 +1091,14 @@@ pscbox (struct t_test_proc *proc
  
    struct tab_table *table;
  
 -  table = tab_create (cols, rows, 0);
 +  table = tab_create (cols, rows);
  
 -  tab_columns (table, SOM_COL_DOWN, 1);
 +  tab_columns (table, SOM_COL_DOWN);
    tab_headers (table, 0, 0, 1, 0);
    tab_box (table, TAL_2, TAL_2, TAL_0, TAL_1, 0, 0, cols - 1, rows - 1);
    tab_hline (table, TAL_2, 0, cols - 1, 1);
    tab_vline (table, TAL_2, 2, 0, rows - 1);
 -  tab_dim (table, tab_natural_dimensions, NULL);
 +  tab_dim (table, tab_natural_dimensions, NULL, NULL);
    tab_title (table, _("Paired Samples Correlations"));
  
    /* column headings */
index 26d14ab8f3b803559c01696e03bf2cdd632b89ca,310206c0e7bf88961e820c05f97831f8568da5a6..c7e302779e63a8937c64a707eeb74126823e948c
@@@ -88,7 -88,7 +88,7 @@@ wilcoxon_execute (const struct dataset 
  
    struct wilcoxon_state *ws = xcalloc (sizeof (*ws), t2s->n_pairs);
    const struct variable *weight = dict_get_weight (dict);
-   struct variable *weightx = var_create_internal (WEIGHT_IDX);
+   struct variable *weightx = var_create_internal (WEIGHT_IDX, 0);
    struct caseproto *proto;
  
    input =
        struct subcase ordering;
        variable_pair *vp = &t2s->pairs[i];
  
-       ws[i].sign = var_create_internal (0);
-       ws[i].absdiff = var_create_internal (1);
+       ws[i].sign = var_create_internal (0, 0);
+       ws[i].absdiff = var_create_internal (1, 0);
  
        r = casereader_create_filter_missing (r, *vp, 2,
                                            exclude,
@@@ -225,9 -225,9 +225,9 @@@ show_ranks_box (const struct wilcoxon_s
    const struct variable *wv = dict_get_weight (dict);
    const struct fmt_spec *wfmt = wv ? var_get_print_format (wv) : & F_8_0;
  
 -  struct tab_table *table = tab_create (5, 1 + 4 * t2s->n_pairs, 0);
 +  struct tab_table *table = tab_create (5, 1 + 4 * t2s->n_pairs);
  
 -  tab_dim (table, tab_natural_dimensions, NULL);
 +  tab_dim (table, tab_natural_dimensions, NULL, NULL);
  
    tab_title (table, _("Ranks"));
  
  
    /* Vertical lines inside the box */
    tab_box (table, 0, 0, -1, TAL_1,
 -         1, 0, table->nc - 1, tab_nr (table) - 1 );
 +         1, 0, tab_nc (table) - 1, tab_nr (table) - 1 );
  
    /* Box around entire table */
    tab_box (table, TAL_2, TAL_2, -1, -1,
 -         0, 0, table->nc - 1, tab_nr (table) - 1 );
 +         0, 0, tab_nc (table) - 1, tab_nr (table) - 1 );
  
  
    tab_text (table,  2, 0,  TAB_CENTER, _("N"));
        tab_text (table, 1, 3 + i * 4, TAB_LEFT, _("Ties"));
        tab_text (table, 1, 4 + i * 4, TAB_LEFT, _("Total"));
  
 -      tab_hline (table, TAL_1, 0, table->nc - 1, 1 + i * 4);
 +      tab_hline (table, TAL_1, 0, tab_nc (table) - 1, 1 + i * 4);
  
  
        tab_text (table, 0, 1 + i * 4, TAB_LEFT, ds_cstr (&pair_name));
  
      }
  
 -  tab_hline (table, TAL_2, 0, table->nc - 1, 1);
 -  tab_vline (table, TAL_2, 2, 0, table->nr - 1);
 +  tab_hline (table, TAL_2, 0, tab_nc (table) - 1, 1);
 +  tab_vline (table, TAL_2, 2, 0, tab_nr (table) - 1);
  
  
    tab_submit (table);
@@@ -306,9 -306,9 +306,9 @@@ show_tests_box (const struct wilcoxon_s
                )
  {
    size_t i;
 -  struct tab_table *table = tab_create (1 + t2s->n_pairs, exact ? 5 : 3, 0);
 +  struct tab_table *table = tab_create (1 + t2s->n_pairs, exact ? 5 : 3);
  
 -  tab_dim (table, tab_natural_dimensions, NULL);
 +  tab_dim (table, tab_natural_dimensions, NULL, NULL);
  
    tab_title (table, _("Test Statistics"));
  
  
    /* Vertical lines inside the box */
    tab_box (table, 0, 0, -1, TAL_1,
 -         0, 0, table->nc - 1, tab_nr (table) - 1 );
 +         0, 0, tab_nc (table) - 1, tab_nr (table) - 1 );
  
    /* Box around entire table */
    tab_box (table, TAL_2, TAL_2, -1, -1,
 -         0, 0, table->nc - 1, tab_nr (table) - 1 );
 +         0, 0, tab_nc (table) - 1, tab_nr (table) - 1 );
  
  
    tab_text (table,  0, 1,  TAB_LEFT, _("Z"));
-   tab_text (table,  0, 2,  TAB_LEFT, _("Asymp. Sig (2-tailed)"));
+   tab_text (table,  0, 2,  TAB_LEFT, _("Asymp. Sig. (2-tailed)"));
  
    if ( exact )
      {
-       tab_text (table,  0, 3,  TAB_LEFT, _("Exact Sig (2-tailed)"));
-       tab_text (table,  0, 4,  TAB_LEFT, _("Exact Sig (1-tailed)"));
+       tab_text (table,  0, 3,  TAB_LEFT, _("Exact Sig. (2-tailed)"));
+       tab_text (table,  0, 4,  TAB_LEFT, _("Exact Sig. (1-tailed)"));
  
  #if 0
        tab_text (table,  0, 5,  TAB_LEFT, _("Point Probability"));
        }
      }
  
 -  tab_hline (table, TAL_2, 0, table->nc - 1, 1);
 -  tab_vline (table, TAL_2, 1, 0, table->nr - 1);
 +  tab_hline (table, TAL_2, 0, tab_nc (table) - 1, 1);
 +  tab_vline (table, TAL_2, 1, 0, tab_nr (table) - 1);
  
  
    tab_submit (table);
index 411022d43d40563ef11ade555e17ee004d74448a,0771ade3a9e92dccc0c3063a7c14d13014c010e3..9b5fd0430b56879fd92e1996c732cbe16e0c2068
@@@ -29,7 -29,6 +29,7 @@@
  #include <language/command.h>
  #include <language/lexer/lexer.h>
  #include <libpspp/assertion.h>
 +#include <libpspp/cast.h>
  #include <libpspp/message.h>
  #include <libpspp/message.h>
  #include <libpspp/str.h>
@@@ -63,16 -62,14 +63,16 @@@ struct syntax_file_sourc
  static const char *
  name (const struct getl_interface *s)
  {
 -  const struct syntax_file_source *sfs = (const struct syntax_file_source *) s;
 +  const struct syntax_file_source *sfs = UP_CAST (s, struct syntax_file_source,
 +                                                  parent);
    return sfs->fn;
  }
  
  static int
  line_number (const struct getl_interface *s)
  {
 -  const struct syntax_file_source *sfs = (const struct syntax_file_source *) s;
 +  const struct syntax_file_source *sfs = UP_CAST (s, struct syntax_file_source,
 +                                                  parent);
    return sfs->ln;
  }
  
@@@ -83,8 -80,7 +83,8 @@@ static boo
  read_syntax_file (struct getl_interface *s,
                    struct string *line)
  {
 -  struct syntax_file_source *sfs = (struct syntax_file_source *) s;
 +  struct syntax_file_source *sfs = UP_CAST (s, struct syntax_file_source,
 +                                            parent);
  
    /* Open file, if not yet opened. */
    if (sfs->syntax_file == NULL)
    do
      {
        sfs->ln++;
+       ds_clear (line);
        if (!ds_read_line (line, sfs->syntax_file, SIZE_MAX))
          {
            if (ferror (sfs->syntax_file))
  static void
  syntax_close (struct getl_interface *s)
  {
 -  struct syntax_file_source *sfs = (struct syntax_file_source *) s;
 +  struct syntax_file_source *sfs = UP_CAST (s, struct syntax_file_source,
 +                                            parent);
  
    if (sfs->syntax_file && EOF == fn_close (sfs->fn, sfs->syntax_file))
      msg (MW, _("Closing `%s': %s."), sfs->fn, strerror (errno));
@@@ -155,6 -151,6 +156,6 @@@ create_syntax_file_source (const char *
    ss->parent.name = name ;
    ss->parent.location = line_number;
  
 -  return (struct getl_interface *) ss;
 +  return &ss->parent;
  }
  
diff --combined src/libpspp/str.c
index c954a722c89987d371eee5084a9b52c452af0ee8,afe32de9f2049bfcc5a80ac7a95b36b348be4cb7..8243aa0690aad24a9b519db9bd1838724d32d729
@@@ -23,7 -23,6 +23,7 @@@
  #include <stdint.h>
  #include <stdlib.h>
  
 +#include <libpspp/cast.h>
  #include <libpspp/message.h>
  #include <libpspp/pool.h>
  
@@@ -1215,7 -1214,7 +1215,7 @@@ ds_capacity (const struct string *st
  char *
  ds_cstr (const struct string *st_)
  {
 -  struct string *st = (struct string *) st_;
 +  struct string *st = CONST_CAST (struct string *, st_);
    if (st->ss.string == NULL)
      ds_extend (st, 1);
    st->ss.string[st->ss.length] = '\0';
@@@ -1442,3 -1441,25 +1442,25 @@@ ds_relocate (struct string *st
        free ((char *) rel);
      }
  }
\f
+ /* Operations on uint8_t "strings" */
+ /* Copies buffer SRC, of SRC_SIZE bytes, to DST, of DST_SIZE bytes.
+    DST is truncated to DST_SIZE bytes or padded on the right with
+    copies of PAD as needed. */
+ void
+ u8_buf_copy_rpad (uint8_t *dst, size_t dst_size,
+                 const uint8_t *src, size_t src_size,
+                 char pad)
+ {
+   if (src_size >= dst_size)
+     memmove (dst, src, dst_size);
+   else
+     {
+       memmove (dst, src, src_size);
+       memset (&dst[src_size], pad, dst_size - src_size);
+     }
+ }
diff --combined src/output/automake.mk
index 34aac525b8f47288317f56c666f91670dd4f37a8,ec92c559e044723180977db31ca43708c7f637bd..eb9f7b78e78ddcd9194020fe18049ca2676cf6bc
@@@ -1,41 -1,35 +1,43 @@@
  ## Process this file with automake to produce Makefile.in  -*- makefile -*-
  
 -
 -include $(top_srcdir)/src/output/charts/automake.mk
 -
  noinst_LTLIBRARIES += src/output/liboutput.la 
  
 -output_sources = \
 +src_output_liboutput_la_CPPFLAGS = $(LIBXML2_CFLAGS) $(AM_CPPFLAGS) 
 +
 +src_output_liboutput_la_SOURCES = \
        src/output/afm.c \
        src/output/afm.h \
        src/output/ascii.c \
 +      src/output/chart.c \
 +      src/output/chart.h \
 +      src/output/charts/box-whisker.c \
 +      src/output/charts/box-whisker.h \
 +      src/output/charts/cartesian.c \
 +      src/output/charts/cartesian.h \
 +      src/output/charts/np-plot.c \
 +      src/output/charts/np-plot.h \
 +      src/output/charts/piechart.c \
 +      src/output/charts/piechart.h \
 +      src/output/charts/plot-chart.c \
 +      src/output/charts/plot-chart.h \
 +      src/output/charts/plot-hist.c \
 +      src/output/charts/plot-hist.h \
++      src/output/charts/roc-chart.c \
++      src/output/charts/roc-chart.h \
        src/output/html.c \
        src/output/htmlP.h \
        src/output/journal.c \
        src/output/journal.h \
 +      src/output/manager.c \
 +      src/output/manager.h \
 +      src/output/odt.c \
        src/output/output.c \
        src/output/output.h \
        src/output/postscript.c \
 -      src/output/manager.c \
 -      src/output/manager.h \
 -      src/output/chart.h \
 -      src/output/table.c src/output/table.h
 -
 -
 -if WITHCHARTS
 -src_output_liboutput_la_SOURCES = $(output_sources) src/output/chart.c
 -
 -EXTRA_DIST += src/output/dummy-chart.c
 -else
 -src_output_liboutput_la_SOURCES = $(output_sources) src/output/dummy-chart.c
 -
 -EXTRA_DIST += src/output/chart.c
 +      src/output/table.c \
 +      src/output/table.h
 +if HAVE_CAIRO
 +src_output_liboutput_la_SOURCES += src/output/cairo.c
  endif
  
  EXTRA_DIST += src/output/OChangeLog
diff --combined src/output/cairo.c
index a2d5fef9d1ddfd36914ef6f3fa22e55fdcb48424,0000000000000000000000000000000000000000..b0b4f7d3e1fe0dd0be16577171e21faccc3f4c2c
mode 100644,000000..100644
--- /dev/null
@@@ -1,911 -1,0 +1,911 @@@
-   chart_geometry_free (x->cairo);
 +/* PSPP - a program for statistical analysis.
 +   Copyright (C) 2009 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
 +   the Free Software Foundation, either version 3 of the License, or
 +   (at your option) any later version.
 +
 +   This program is distributed in the hope that it will be useful,
 +   but WITHOUT ANY WARRANTY; without even the implied warranty of
 +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +   GNU General Public License for more details.
 +
 +   You should have received a copy of the GNU General Public License
 +   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 +
 +#include <config.h>
 +
 +#include <output/cairo.h>
 +
 +#include <libpspp/assertion.h>
 +#include <libpspp/start-date.h>
 +#include <libpspp/version.h>
 +#include <output/afm.h>
 +#include <output/chart-provider.h>
 +#include <output/manager.h>
 +#include <output/output.h>
 +
 +#include <cairo/cairo-pdf.h>
 +#include <cairo/cairo-ps.h>
 +#include <cairo/cairo-svg.h>
 +#include <cairo/cairo.h>
 +#include <pango/pango-font.h>
 +#include <pango/pango-layout.h>
 +#include <pango/pango.h>
 +#include <pango/pangocairo.h>
 +#include <stdlib.h>
 +
 +#include "error.h"
 +#include "intprops.h"
 +#include "minmax.h"
 +#include "xalloc.h"
 +
 +#include "gettext.h"
 +#define _(msgid) gettext (msgid)
 +
 +/* Cairo driver options: (defaults listed first)
 +
 +   output-file="pspp.pdf"
 +   output-type=pdf|ps|png|svg
 +   paper-size=letter (see "papersize" file)
 +   orientation=portrait|landscape
 +   headers=on|off
 +
 +   left-margin=0.5in
 +   right-margin=0.5in
 +   top-margin=0.5in
 +   bottom-margin=0.5in
 +
 +   prop-font=serif
 +   emph-font=serif italic
 +   fixed-font=monospace
 +   font-size=10000
 +
 +   line-gutter=1pt
 +   line-spacing=1pt
 +   line-width=0.5pt
 + */
 +
 +/* Measurements as we present to the rest of PSPP. */
 +#define XR_POINT PANGO_SCALE
 +#define XR_INCH (XR_POINT * 72)
 +
 +/* Conversions to and from points. */
 +static double
 +xr_to_pt (int x)
 +{
 +  return x / (double) XR_POINT;
 +}
 +
 +static int
 +pt_to_xr (double x)
 +{
 +  return x * XR_POINT + 0.5;
 +}
 +
 +/* Output types. */
 +enum xr_output_type
 +  {
 +    XR_PDF,
 +    XR_PS,
 +    XR_SVG
 +  };
 +
 +/* A font for use with Cairo. */
 +struct xr_font
 +  {
 +    char *string;
 +    PangoFontDescription *desc;
 +    PangoLayout *layout;
 +    PangoFontMetrics *metrics;
 +  };
 +
 +/* Cairo output driver extension record. */
 +struct xr_driver_ext
 +  {
 +    cairo_t *cairo;
 +    struct xr_font fonts[OUTP_FONT_CNT];
 +
 +    bool draw_headers;          /* Draw headers at top of page? */
 +    int page_number;          /* Current page number. */
 +
 +    int line_gutter;          /* Space around lines. */
 +    int line_space;           /* Space between lines. */
 +    int line_width;           /* Width of lines. */
 +  };
 +
 +struct xr_driver_options
 +  {
 +    struct outp_driver *driver;
 +
 +    char *file_name;            /* Output file name. */
 +    enum xr_output_type file_type; /* Type of output file. */
 +
 +
 +    bool portrait;              /* Portrait mode? */
 +
 +    int paper_width;            /* Width of paper before dropping margins. */
 +    int paper_length;           /* Length of paper before dropping margins. */
 +    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. */
 +  };
 +
 +static bool handle_option (void *options, const char *key,
 +                           const struct string *val);
 +static void draw_headers (struct outp_driver *this);
 +
 +static bool load_font (struct outp_driver *this, struct xr_font *);
 +static void free_font (struct xr_font *);
 +static int text_width (struct outp_driver *, const char *, enum outp_font);
 +\f
 +/* Driver initialization. */
 +
 +static struct outp_driver *
 +xr_allocate (const char *name, int types)
 +{
 +  struct outp_driver *this;
 +  struct xr_driver_ext *x;
 +  size_t i;
 +
 +  this = outp_allocate_driver (&cairo_class, name, types);
 +  this->width = this->length = 0;
 +  this->font_height = XR_POINT * 10;
 +  this->ext = x = xzalloc (sizeof *x);
 +  x->cairo = NULL;
 +  x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
 +  x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
 +  x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
 +  for (i = 0; i < OUTP_FONT_CNT; i++)
 +    {
 +      struct xr_font *font = &x->fonts[i];
 +      font->desc = NULL;
 +      font->metrics = NULL;
 +      font->layout = NULL;
 +    }
 +  x->draw_headers = true;
 +  x->page_number = 0;
 +  x->line_gutter = XR_POINT;
 +  x->line_space = XR_POINT;
 +  x->line_width = XR_POINT / 2;
 +
 +  return this;
 +}
 +
 +static bool
 +xr_set_cairo (struct outp_driver *this, cairo_t *cairo)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  int i;
 +
 +  x->cairo = cairo;
 +
 +  cairo_set_line_width (x->cairo, xr_to_pt (x->line_width));
 +
 +  for (i = 0; i < OUTP_FONT_CNT; i++)
 +    if (!load_font (this, &x->fonts[i]))
 +      return false;
 +
 +  this->fixed_width = text_width (this, "0", OUTP_FIXED);
 +  this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
 +
 +  this->horiz_line_width[OUTP_L_NONE] = 0;
 +  this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
 +  this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
 +                                           + 2 * x->line_width);
 +  memcpy (this->vert_line_width, this->horiz_line_width,
 +          sizeof this->vert_line_width);
 +
 +  return true;
 +}
 +
 +struct outp_driver *
 +xr_create_driver (cairo_t *cairo)
 +{
 +  struct outp_driver *this;
 +
 +  this = xr_allocate ("cairo", 0);
 +  this->width = INT_MAX / 8;
 +  this->length = INT_MAX / 8;
 +  if (!xr_set_cairo (this, cairo))
 +    {
 +      this->class->close_driver (this);
 +      outp_free_driver (this);
 +      return NULL;
 +    }
 +  return this;
 +}
 +
 +static bool
 +xr_open_driver (const char *name, int types, struct substring option_string)
 +{
 +  struct outp_driver *this;
 +  struct xr_driver_ext *x;
 +  struct xr_driver_options options;
 +  cairo_surface_t *surface;
 +  cairo_status_t status;
 +  double width_pt, length_pt;
 +
 +  this = xr_allocate (name, types);
 +  x = this->ext;
 +
 +  options.driver = this;
 +  options.file_name = xstrdup ("pspp.pdf");
 +  options.file_type = XR_PDF;
 +  options.portrait = true;
 +  outp_get_paper_size ("", &options.paper_width, &options.paper_length);
 +  options.left_margin = XR_INCH / 2;
 +  options.right_margin = XR_INCH / 2;
 +  options.top_margin = XR_INCH / 2;
 +  options.bottom_margin = XR_INCH / 2;
 +
 +  outp_parse_options (this->name, option_string, handle_option, &options);
 +
 +  width_pt = options.paper_width / 1000.0;
 +  length_pt = options.paper_length / 1000.0;
 +  if (options.portrait)
 +    {
 +      this->width = pt_to_xr (width_pt);
 +      this->length = pt_to_xr (length_pt);
 +    }
 +  else
 +    {
 +      this->width = pt_to_xr (width_pt);
 +      this->length = pt_to_xr (length_pt);
 +    }
 +  if (x->draw_headers)
 +    options.top_margin += 3 * this->font_height;
 +  this->width -= options.left_margin + options.right_margin;
 +  this->length -= options.top_margin + options.bottom_margin;
 +
 +  if (options.file_type == XR_PDF)
 +    surface = cairo_pdf_surface_create (options.file_name,
 +                                        width_pt, length_pt);
 +  else if (options.file_type == XR_PS)
 +    surface = cairo_ps_surface_create (options.file_name, width_pt, length_pt);
 +  else if (options.file_type == XR_SVG)
 +    surface = cairo_svg_surface_create (options.file_name,
 +                                        width_pt, length_pt);
 +  else
 +    NOT_REACHED ();
 +
 +  status = cairo_surface_status (surface);
 +  if (status != CAIRO_STATUS_SUCCESS)
 +    {
 +      error (0, 0, _("opening output file \"%s\": %s"),
 +             options.file_name, cairo_status_to_string (status));
 +      cairo_surface_destroy (surface);
 +      goto error;
 +    }
 +
 +  x->cairo = cairo_create (surface);
 +  cairo_surface_destroy (surface);
 +
 +  cairo_translate (x->cairo,
 +                   xr_to_pt (options.left_margin),
 +                   xr_to_pt (options.top_margin));
 +
 +  if (this->length / this->font_height < 15)
 +    {
 +      error (0, 0, _("The defined page is not long "
 +                     "enough to hold margins and headers, plus least 15 "
 +                     "lines of the default fonts.  In fact, there's only "
 +                     "room for %d lines."),
 +             this->length / this->font_height);
 +      goto error;
 +    }
 +
 +  if (!xr_set_cairo (this, x->cairo))
 +    goto error;
 +
 +  outp_register_driver (this);
 +  free (options.file_name);
 +  return true;
 +
 + error:
 +  this->class->close_driver (this);
 +  outp_free_driver (this);
 +  free (options.file_name);
 +  return false;
 +}
 +
 +static bool
 +xr_close_driver (struct outp_driver *this)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  bool ok = true;
 +  size_t i;
 +
 +  if (x->cairo != NULL)
 +    {
 +      cairo_status_t status;
 +
 +      cairo_surface_finish (cairo_get_target (x->cairo));
 +      status = cairo_status (x->cairo);
 +      if (status != CAIRO_STATUS_SUCCESS)
 +        error (0, 0, _("error writing output file for %s driver: %s"),
 +               this->name, cairo_status_to_string (status));
 +      cairo_destroy (x->cairo);
 +    }
 +
 +  for (i = 0; i < OUTP_FONT_CNT; i++)
 +    free_font (&x->fonts[i]);
 +  free (x);
 +
 +  return ok;
 +}
 +
 +/* Generic option types. */
 +enum
 +{
 +  output_file_arg,
 +  output_type_arg,
 +  paper_size_arg,
 +  orientation_arg,
 +  line_style_arg,
 +  boolean_arg,
 +  dimension_arg,
 +  string_arg,
 +  nonneg_int_arg
 +};
 +
 +/* All the options that the Cairo driver supports. */
 +static const struct outp_option option_tab[] =
 +{
 +  {"output-file",             output_file_arg,0},
 +  {"output-type",               output_type_arg,0},
 +  {"paper-size",              paper_size_arg, 0},
 +  {"orientation",             orientation_arg,0},
 +
 +  {"headers",                 boolean_arg,    1},
 +
 +  {"prop-font",               string_arg,     OUTP_PROPORTIONAL},
 +  {"emph-font",               string_arg,     OUTP_EMPHASIS},
 +  {"fixed-font",              string_arg,     OUTP_FIXED},
 +
 +  {"left-margin",             dimension_arg,  0},
 +  {"right-margin",            dimension_arg,  1},
 +  {"top-margin",              dimension_arg,  2},
 +  {"bottom-margin",           dimension_arg,  3},
 +  {"font-size",                       dimension_arg,  4},
 +  {"line-width",              dimension_arg,  5},
 +  {"line-gutter",             dimension_arg,  6},
 +  {"line-width",              dimension_arg,  7},
 +  {NULL, 0, 0},
 +};
 +
 +static bool
 +handle_option (void *options_, const char *key, const struct string *val)
 +{
 +  struct xr_driver_options *options = options_;
 +  struct outp_driver *this = options->driver;
 +  struct xr_driver_ext *x = this->ext;
 +  int subcat;
 +  char *value = ds_cstr (val);
 +
 +  switch (outp_match_keyword (key, option_tab, &subcat))
 +    {
 +    case -1:
 +      error (0, 0,
 +             _("unknown configuration parameter `%s' for %s device "
 +               "driver"), key, this->class->name);
 +      break;
 +    case output_file_arg:
 +      free (options->file_name);
 +      options->file_name = xstrdup (value);
 +      break;
 +    case output_type_arg:
 +      if (!strcmp (value, "pdf"))
 +        options->file_type = XR_PDF;
 +      else if (!strcmp (value, "ps"))
 +        options->file_type = XR_PS;
 +      else if (!strcmp (value, "svg"))
 +        options->file_type = XR_SVG;
 +      else
 +        {
 +          error (0, 0, _("unknown Cairo output type \"%s\""), value);
 +          return false;
 +        }
 +      break;
 +    case paper_size_arg:
 +      outp_get_paper_size (value,
 +                           &options->paper_width, &options->paper_length);
 +      break;
 +    case orientation_arg:
 +      if (!strcmp (value, "portrait"))
 +      options->portrait = true;
 +      else if (!strcmp (value, "landscape"))
 +      options->portrait = false;
 +      else
 +      error (0, 0, _("unknown orientation `%s' (valid orientations are "
 +                       "`portrait' and `landscape')"), value);
 +      break;
 +    case boolean_arg:
 +      if (!strcmp (value, "on") || !strcmp (value, "true")
 +          || !strcmp (value, "yes") || atoi (value))
 +        x->draw_headers = true;
 +      else if (!strcmp (value, "off") || !strcmp (value, "false")
 +               || !strcmp (value, "no") || !strcmp (value, "0"))
 +        x->draw_headers = false;
 +      else
 +        {
 +          error (0, 0, _("boolean value expected for %s"), key);
 +          return false;
 +        }
 +      break;
 +    case dimension_arg:
 +      {
 +      int dimension = outp_evaluate_dimension (value);
 +
 +      if (dimension <= 0)
 +          break;
 +      switch (subcat)
 +        {
 +        case 0:
 +          options->left_margin = dimension;
 +          break;
 +        case 1:
 +          options->right_margin = dimension;
 +          break;
 +        case 2:
 +          options->top_margin = dimension;
 +          break;
 +        case 3:
 +          options->bottom_margin = dimension;
 +          break;
 +        case 4:
 +          this->font_height = dimension;
 +          break;
 +        case 5:
 +          x->line_width = dimension;
 +          break;
 +        case 6:
 +          x->line_gutter = dimension;
 +          break;
 +        case 7:
 +          x->line_width = dimension;
 +          break;
 +        default:
 +          NOT_REACHED ();
 +        }
 +      }
 +      break;
 +    case string_arg:
 +      free (x->fonts[subcat].string);
 +      x->fonts[subcat].string = ds_xstrdup (val);
 +      break;
 +    default:
 +      NOT_REACHED ();
 +    }
 +
 +  return true;
 +}
 +\f
 +/* Basic file operations. */
 +
 +static void
 +xr_open_page (struct outp_driver *this)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +
 +  x->page_number++;
 +
 +  if (x->draw_headers)
 +    draw_headers (this);
 +}
 +
 +static void
 +xr_close_page (struct outp_driver *this)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  cairo_show_page (x->cairo);
 +}
 +
 +static void
 +xr_output_chart (struct outp_driver *this, const struct chart *chart)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  struct chart_geometry geom;
 +
 +  outp_eject_page (this);
 +  outp_open_page (this);
 +
 +  cairo_save (x->cairo);
 +  cairo_translate (x->cairo, 0.0, xr_to_pt (this->length));
 +  cairo_scale (x->cairo, 1.0, -1.0);
 +  chart_geometry_init (x->cairo, &geom,
 +                       xr_to_pt (this->width), xr_to_pt (this->length));
 +  chart_draw (chart, x->cairo, &geom);
++  chart_geometry_free (x->cairo, &geom);
 +  cairo_restore (x->cairo);
 +
 +  outp_close_page (this);
 +}
 +\f
 +/* Draws a line from (x0,y0) to (x1,y1). */
 +static void
 +dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  cairo_new_path (x->cairo);
 +  cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
 +  cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
 +  cairo_stroke (x->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,
 +   shortening it to X2...X3 if SHORTEN is true. */
 +static void
 +horz_line (struct outp_driver *this,
 +           int x0, int x1, int x2, int x3, int y,
 +           enum outp_line_style left, enum outp_line_style right,
 +           bool shorten)
 +{
 +  if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
 +    dump_line (this, x0, y, x3, y);
 +  else
 +    {
 +      if (left != OUTP_L_NONE)
 +        dump_line (this, x0, y, shorten ? x1 : x2, y);
 +      if (right != OUTP_L_NONE)
 +        dump_line (this, shorten ? x2 : x1, y, x3, y);
 +    }
 +}
 +
 +/* Draws a vertical line Y0...Y2 at X if TOP says so,
 +   shortening it to Y0...Y1 if SHORTEN is true.
 +   Draws a vertical line Y1...Y3 at X if BOTTOM says so,
 +   shortening it to Y2...Y3 if SHORTEN is true. */
 +static void
 +vert_line (struct outp_driver *this,
 +           int y0, int y1, int y2, int y3, int x,
 +           enum outp_line_style top, enum outp_line_style bottom,
 +           bool shorten)
 +{
 +  if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
 +    dump_line (this, x, y0, x, y3);
 +  else
 +    {
 +      if (top != OUTP_L_NONE)
 +        dump_line (this, x, y0, x, shorten ? y1 : y2);
 +      if (bottom != OUTP_L_NONE)
 +        dump_line (this, x, shorten ? y2 : y1, x, y3);
 +    }
 +}
 +
 +/* Draws a generalized intersection of lines in the rectangle
 +   (X0,Y0)-(X3,Y3).  The line coming from the top to the center
 +   is of style TOP, from left to center of style LEFT, from
 +   bottom to center of style BOTTOM, and from right to center of
 +   style RIGHT. */
 +static void
 +xr_line (struct outp_driver *this,
 +         int x0, int y0, int x3, int y3,
 +         enum outp_line_style top, enum outp_line_style left,
 +         enum outp_line_style bottom, enum outp_line_style right)
 +{
 +  /* The algorithm here is somewhat subtle, to allow it to handle
 +     all the kinds of intersections that we need.
 +
 +     Three additional ordinates are assigned along the x axis.  The
 +     first is xc, midway between x0 and x3.  The others are x1 and
 +     x2; for a single vertical line these are equal to xc, and for
 +     a double vertical line they are the ordinates of the left and
 +     right half of the double line.
 +
 +     yc, y1, and y2 are assigned similarly along the y axis.
 +
 +     The following diagram shows the coordinate system and output
 +     for double top and bottom lines, single left line, and no
 +     right line:
 +
 +                 x0       x1 xc  x2      x3
 +               y0 ________________________
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +     y1 = y2 = yc |#########     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +               y3 |________#_____#_______|
 +  */
 +  struct xr_driver_ext *ext = this->ext;
 +
 +  /* Offset from center of each line in a pair of double lines. */
 +  int double_line_ofs = (ext->line_space + ext->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 == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
 +  bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
 +
 +  /* When horizontal lines are doubled,
 +     the left-side line along y1 normally runs from x0 to x2,
 +     and the right-side line along y1 from x3 to x1.
 +     If the top-side line is also doubled, we shorten the y1 lines,
 +     so that the left-side line runs only to x1,
 +     and the right-side line only to x2.
 +     Otherwise, the horizontal line at y = y1 below would cut off
 +     the intersection, which looks ugly:
 +               x0       x1     x2      x3
 +             y0 ________________________
 +                |        #     #       |
 +                |        #     #       |
 +                |        #     #       |
 +                |        #     #       |
 +             y1 |#########     ########|
 +                |                      |
 +                |                      |
 +             y2 |######################|
 +                |                      |
 +                |                      |
 +             y3 |______________________|
 +     It is more of a judgment call when the horizontal line is
 +     single.  We actually choose to cut off the line anyhow, as
 +     shown in the first diagram above.
 +  */
 +  bool shorten_y1_lines = top == OUTP_L_DOUBLE;
 +  bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
 +  bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
 +  int horz_line_ofs = double_vert ? double_line_ofs : 0;
 +  int xc = (x0 + x3) / 2;
 +  int x1 = xc - horz_line_ofs;
 +  int x2 = xc + horz_line_ofs;
 +
 +  bool shorten_x1_lines = left == OUTP_L_DOUBLE;
 +  bool shorten_x2_lines = right == OUTP_L_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;
 +  int y1 = yc - vert_line_ofs;
 +  int y2 = yc + vert_line_ofs;
 +
 +  if (!double_horz)
 +    horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
 +  else
 +    {
 +      horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
 +      horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
 +    }
 +
 +  if (!double_vert)
 +    vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
 +  else
 +    {
 +      vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
 +      vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
 +    }
 +}
 +
 +/* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
 +   and with the given JUSTIFICATION for THIS driver. */
 +static int
 +draw_text (struct outp_driver *this,
 +           const char *string, int x, int y, int max_width,
 +           enum outp_justification justification)
 +{
 +  struct outp_text text;
 +  int width;
 +
 +  text.font = OUTP_PROPORTIONAL;
 +  text.justification = justification;
 +  text.string = ss_cstr (string);
 +  text.h = max_width;
 +  text.v = this->font_height;
 +  text.x = x;
 +  text.y = y;
 +  this->class->text_metrics (this, &text, &width, NULL);
 +  this->class->text_draw (this, &text);
 +  return width;
 +}
 +
 +/* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
 +   and with the given JUSTIFICATION for THIS driver. */
 +static int
 +text_width (struct outp_driver *this, const char *string, enum outp_font font)
 +{
 +  struct outp_text text;
 +  int width;
 +
 +  text.font = font;
 +  text.justification = OUTP_LEFT;
 +  text.string = ss_cstr (string);
 +  text.h = INT_MAX;
 +  text.v = this->font_height;
 +  text.x = 0;
 +  text.y = 0;
 +  this->class->text_metrics (this, &text, &width, NULL);
 +  return width;
 +}
 +
 +/* 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 outp_driver *this,
 +                  const char *left, const char *right,
 +                  int x0, int x1, int y)
 +{
 +  int right_width = 0;
 +  if (right != NULL)
 +    right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
 +                   + this->prop_em_width);
 +  if (left != NULL)
 +    draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
 +}
 +
 +/* Draw top of page headers for THIS driver. */
 +static void
 +draw_headers (struct outp_driver *this)
 +{
 +  struct xr_driver_ext *ext = this->ext;
 +  char *r1, *r2;
 +  int x0, x1;
 +  int y;
 +
 +  y = -3 * this->font_height;
 +  x0 = this->prop_em_width;
 +  x1 = this->width - this->prop_em_width;
 +
 +  /* Draw box. */
 +  cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
 +                   xr_to_pt (2 * (this->font_height
 +                                  + ext->line_width + ext->line_gutter)));
 +  cairo_save (ext->cairo);
 +  cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
 +  cairo_fill_preserve (ext->cairo);
 +  cairo_restore (ext->cairo);
 +  cairo_stroke (ext->cairo);
 +
 +  y += ext->line_width + ext->line_gutter;
 +
 +  r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
 +  r2 = xasprintf ("%s - %s", version, host_system);
 +
 +  draw_header_line (this, outp_title, r1, x0, x1, y);
 +  y += this->font_height;
 +
 +  draw_header_line (this, outp_subtitle, r2, x0, x1, y);
 +
 +  free (r1);
 +  free (r2);
 +}
 +\f
 +/* Format TEXT on THIS driver.
 +   If DRAW is nonzero, draw the text.
 +   The width of the widest line is stored into *WIDTH, if WIDTH
 +   is nonnull.
 +   The total height of the text written is stored into *HEIGHT,
 +   if HEIGHT is nonnull. */
 +static void
 +text (struct outp_driver *this, const struct outp_text *text, bool draw,
 +      int *width, int *height)
 +{
 +  struct xr_driver_ext *ext = this->ext;
 +  struct xr_font *font = &ext->fonts[text->font];
 +
 +  pango_layout_set_text (font->layout,
 +                         text->string.string, text->string.length);
 +  pango_layout_set_alignment (
 +    font->layout,
 +    (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
 +     : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
 +     : PANGO_ALIGN_CENTER));
 +  pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
 +  pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
 +  /* XXX need to limit number of lines to those that fit in text->v. */
 +
 +  if (draw)
 +    {
 +      int x = text->x;
 +      if (text->justification != OUTP_LEFT && text->h != INT_MAX)
 +        {
 +          int w, h, excess;
 +          pango_layout_get_size (font->layout, &w, &h);
 +          excess = text->h - w;
 +          if (excess > 0)
 +            {
 +              if (text->justification == OUTP_CENTER)
 +                x += excess / 2;
 +              else
 +                x += excess;
 +            }
 +        }
 +      cairo_save (ext->cairo);
 +      cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
 +      pango_cairo_show_layout (ext->cairo, font->layout);
 +      cairo_restore (ext->cairo);
 +    }
 +
 +  if (width != NULL || height != NULL)
 +    {
 +      int w, h;
 +      pango_layout_get_size (font->layout, &w, &h);
 +      if (width != NULL)
 +        *width = w;
 +      if (height != NULL)
 +        *height = h;
 +    }
 +}
 +
 +static void
 +xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
 +                 int *width, int *height)
 +{
 +  text (this, t, false, width, height);
 +}
 +
 +static void
 +xr_text_draw (struct outp_driver *this, const struct outp_text *t)
 +{
 +  text (this, t, true, NULL, NULL);
 +}
 +\f
 +/* Attempts to load FONT, initializing its other members based on
 +   its 'string' member and the information in THIS.  Returns true
 +   if successful, otherwise false. */
 +static bool
 +load_font (struct outp_driver *this, struct xr_font *font)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  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, this->font_height);
 +
 +  font->layout = pango_cairo_create_layout (x->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
 +free_font (struct xr_font *font)
 +{
 +  free (font->string);
 +  if (font->desc != NULL)
 +    pango_font_description_free (font->desc);
 +  pango_font_metrics_unref (font->metrics);
 +  g_object_unref (font->layout);
 +}
 +
 +/* Cairo driver class. */
 +const struct outp_class cairo_class =
 +{
 +  "cairo",
 +  0,
 +
 +  xr_open_driver,
 +  xr_close_driver,
 +
 +  xr_open_page,
 +  xr_close_page,
 +  NULL,
 +
 +  xr_output_chart,
 +
 +  NULL,
 +
 +  xr_line,
 +  xr_text_metrics,
 +  xr_text_draw,
 +};
index 6065036d0f26b4416048e334d2b5c1b02a98dd81,0000000000000000000000000000000000000000..9becb6f5ed2e200e69474e6059dc8d5bb259ff55
mode 100644,000000..100644
--- /dev/null
@@@ -1,85 -1,0 +1,89 @@@
- void chart_geometry_free (cairo_t *);
 +/* PSPP - a program for statistical analysis.
 +   Copyright (C) 2004, 2009 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
 +   the Free Software Foundation, either version 3 of the License, or
 +   (at your option) any later version.
 +
 +   This program is distributed in the hope that it will be useful,
 +   but WITHOUT ANY WARRANTY; without even the implied warranty of
 +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +   GNU General Public License for more details.
 +
 +   You should have received a copy of the GNU General Public License
 +   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 +
 +#ifndef OUTPUT_CHART_PROVIDER_H
 +#define OUTPUT_CHART_PROVIDER_H 1
 +
 +#include <cairo/cairo.h>
 +#include <stdbool.h>
 +#include <stdint.h>
 +#include <output/chart.h>
 +
 +struct chart_colour
 +  {
 +    uint8_t red;
 +    uint8_t green;
 +    uint8_t blue;
 +  };
 +
 +/* The geometry of a chart. */
 +struct chart_geometry
 +  {
 +    int data_top   ;
 +    int data_right ;
 +    int data_bottom;
 +    int data_left  ;
 +
 +    int abscissa_top;
 +
 +    int ordinate_right ;
 +
 +    int title_bottom ;
 +
++    /* Legend. */
 +    int legend_left ;
 +    int legend_right ;
++    const char **dataset;
++    int n_datasets;
 +
 +    /* Default font size for the plot. */
 +    double font_size;
 +
 +    struct chart_colour fill_colour;
 +
 +    /* Stuff Particular to Cartesians (and Boxplots ) */
 +    double ordinate_scale;
 +    double abscissa_scale;
 +    double x_min;
 +    double x_max;
 +    double y_min;
 +    double y_max;
++    bool in_path;
 +  };
 +
 +struct chart_class
 +  {
 +    void (*draw) (const struct chart *, cairo_t *, struct chart_geometry *);
 +    void (*destroy) (struct chart *);
 +  };
 +
 +struct chart
 +  {
 +    const struct chart_class *class;
 +    int ref_cnt;
 +  };
 +
 +void chart_init (struct chart *, const struct chart_class *);
 +
 +void chart_geometry_init (cairo_t *, struct chart_geometry *,
 +                          double width, double length);
++void chart_geometry_free (cairo_t *, struct chart_geometry *);
 +
 +void chart_draw (const struct chart *, cairo_t *, struct chart_geometry *);
 +char *chart_draw_png (const struct chart *, const char *file_name_template,
 +                      int number);
 +
 +#endif /* output/chart-provider.h */
diff --combined src/output/chart.c
index 1f85d8e485d2303f1cbce08523247d854583a1e9,e324901cc4df1d445f3fc8c7a4b64296bca55bca..e31422bc6bb35799243d68337d41914ae059c430
@@@ -1,5 -1,5 +1,5 @@@
  /* PSPP - a program for statistical analysis.
 -   Copyright (C) 2004 Free Software Foundation, Inc.
 +   Copyright (C) 2004, 2009 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 <config.h>
  
  #include <output/chart.h>
 +#include <output/chart-provider.h>
  
  #include <assert.h>
 +#include <cairo/cairo.h>
  #include <errno.h>
  #include <float.h>
  #include <math.h>
@@@ -29,6 -27,8 +29,6 @@@
  #include <stdlib.h>
  #include <string.h>
  
 -#include <plot.h>
 -
  #include <libpspp/str.h>
  #include <output/manager.h>
  #include <output/output.h>
  
  extern struct som_table_class tab_table_class;
  
 -struct chart *
 -chart_create(void)
 +void
 +chart_init (struct chart *chart, const struct chart_class *class)
  {
 -  struct chart *chart;
 -  struct outp_driver *d;
 -
 -  d = outp_drivers (NULL);
 -  if (d == NULL)
 -    return NULL;
 -
 -  chart = xmalloc (sizeof *chart);
 -  chart->lp = NULL;
 -  d->class->initialise_chart(d, chart);
 -  if (!chart->lp)
 -    {
 -      free (chart);
 -      return NULL;
 -    }
 -
 -  if (pl_openpl_r (chart->lp) < 0)      /* open Plotter */
 -    return NULL;
 -
 -  pl_fspace_r (chart->lp, 0.0, 0.0, 1000.0, 1000.0); /* set coordinate system */
 -  pl_flinewidth_r (chart->lp, 0.25);    /* set line thickness */
 -  pl_pencolorname_r (chart->lp, "black");
 -
 -  pl_erase_r (chart->lp);               /* erase graphics display */
 -  pl_filltype_r(chart->lp,0);
 -
 -  pl_savestate_r(chart->lp);
 -
 -  /* Set default chartetry */
 -  chart->data_top =   900;
 -  chart->data_right = 800;
 -  chart->data_bottom = 120;
 -  chart->data_left = 150;
 -  chart->abscissa_top = 70;
 -  chart->ordinate_right = 120;
 -  chart->title_bottom = 920;
 -  chart->legend_left = 810;
 -  chart->legend_right = 1000;
 -  chart->font_size = 0;
 -  chart->in_path = false;
 -  chart->dataset = NULL;
 -  chart->n_datasets = 0;
 -  strcpy(chart->fill_colour,"red");
 -
 -  /* Get default font size */
 -  if ( !chart->font_size)
 -    chart->font_size = pl_fontsize_r(chart->lp, -1);
 -
 -  /* Draw the data area */
 -  pl_box_r(chart->lp,
 -         chart->data_left, chart->data_bottom,
 -         chart->data_right, chart->data_top);
 +  chart->class = class;
 +  chart->ref_cnt = 1;
 +}
  
 -  return chart;
 +void
 +chart_geometry_init (cairo_t *cr, struct chart_geometry *geom,
 +                     double width, double length)
 +{
 +  /* Set default chartetry. */
 +  geom->data_top = 0.900 * length;
 +  geom->data_right = 0.800 * width;
 +  geom->data_bottom = 0.120 * length;
 +  geom->data_left = 0.150 * width;
 +  geom->abscissa_top = 0.070 * length;
 +  geom->ordinate_right = 0.120 * width;
 +  geom->title_bottom = 0.920 * length;
 +  geom->legend_left = 0.810 * width;
 +  geom->legend_right = width;
 +  geom->font_size = 15.0;
++  geom->in_path = false;
++  geom->dataset = NULL;
++  geom->n_datasets = 0;
 +
 +  geom->fill_colour.red = 255;
 +  geom->fill_colour.green = 0;
 +  geom->fill_colour.blue = 0;
 +
 +  cairo_set_line_width (cr, 1.0);
 +
 +  cairo_rectangle (cr, geom->data_left, geom->data_bottom,
 +                   geom->data_right - geom->data_left,
 +                   geom->data_top - geom->data_bottom);
 +  cairo_stroke (cr);
  }
  
  void
- chart_geometry_free (cairo_t *cr UNUSED)
 -chart_submit(struct chart *chart)
++chart_geometry_free (cairo_t *cr UNUSED, struct chart_geometry *geom)
  {
 -  struct som_entity s;
 -  struct outp_driver *d;
 -
 -  if ( ! chart )
 -     return ;
 -
 -  pl_restorestate_r(chart->lp);
 -
 -  s.class = &tab_table_class;
 -  s.ext = chart;
 -  s.type = SOM_CHART;
 -  som_submit (&s);
+   int i;
 -  if (pl_closepl_r (chart->lp) < 0)     /* close Plotter */
 -    {
 -      fprintf (stderr, "Couldn't close Plotter\n");
 -    }
 -
 -  pl_deletepl_r(chart->lp);
++  for (i = 0 ; i < geom->n_datasets; ++i)
++    free (geom->dataset[i]);
++  free (geom->dataset);
 +}
  
 -  pl_deleteplparams(chart->pl_params);
 +void
 +chart_draw (const struct chart *chart, cairo_t *cr,
 +            struct chart_geometry *geom)
 +{
 +  chart->class->draw (chart, cr, geom);
 +}
  
 -  d = outp_drivers (NULL);
 -  d->class->finalise_chart(d, chart);
 +char *
 +chart_draw_png (const struct chart *chart, const char *file_name_template,
 +                int number)
 +{
 +  const int width = 640;
 +  const int length = 480;
 +
 +  struct chart_geometry geom;
 +  cairo_surface_t *surface;
 +  cairo_status_t status;
 +  const char *number_pos;
 +  char *file_name;
 +  cairo_t *cr;
 +
 +  number_pos = strchr (file_name_template, '#');
 +  if (number_pos != NULL)
 +    file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
 +                           file_name_template, number, number_pos + 1);
 +  else
 +    file_name = xstrdup (file_name_template);
 +
 +  surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
 +  cr = cairo_create (surface);
 +
 +  cairo_translate (cr, 0.0, length);
 +  cairo_scale (cr, 1.0, -1.0);
 +
 +  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);
 +
 +  chart_geometry_init (cr, &geom, width, length);
 +  chart_draw (chart, cr, &geom);
-   chart_geometry_free (cr);
++  chart_geometry_free (cr, &geom);
 +
 +  status = cairo_surface_write_to_png (surface, file_name);
 +  if (status != CAIRO_STATUS_SUCCESS)
 +    error (0, 0, _("writing output file \"%s\": %s"),
 +           file_name, cairo_status_to_string (status));
 +
 +  cairo_destroy (cr);
 +  cairo_surface_destroy (surface);
 +
 +  return file_name;
 +}
  
 -  for (i = 0 ; i < chart->n_datasets; ++i)
 -    free (chart->dataset[i]);
 -  free (chart->dataset);
  
 -  free(chart);
 +struct chart *
 +chart_ref (const struct chart *chart_)
 +{
 +  struct chart *chart = CONST_CAST (struct chart *, chart_);
 +  chart->ref_cnt++;
 +  return chart;
  }
  
  void
 -chart_init_separate (struct chart *ch, const char *type,
 -                     const char *file_name_tmpl, int number)
 +chart_unref (struct chart *chart)
  {
 -  FILE *fp;
 -  int number_pos;
 -
 -  number_pos = strchr (file_name_tmpl, '#') - file_name_tmpl;
 -  ch->file_name = xasprintf ("%.*s%d%s",
 -                             number_pos, file_name_tmpl,
 -                             number,
 -                             file_name_tmpl + number_pos + 1);
 -  fp = fopen (ch->file_name, "wb");
 -  if (fp == NULL)
 +  if (chart != NULL)
      {
 -      error (0, errno, _("creating \"%s\""), ch->file_name);
 -      free (ch->file_name);
 -      ch->file_name = NULL;
 -      return;
 +      assert (chart->ref_cnt > 0);
 +      if (--chart->ref_cnt == 0)
 +        chart->class->destroy (chart);
      }
 -
 -  ch->pl_params = pl_newplparams ();
 -  ch->lp = pl_newpl_r (type, 0, fp, stderr, ch->pl_params);
  }
  
  void
 -chart_finalise_separate (struct chart *ch)
 +chart_submit (struct chart *chart)
  {
 -  free (ch->file_name);
 +#ifdef HAVE_CAIRO
 +  struct outp_driver *d;
 +
 +  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
 +    if (d->class->output_chart != NULL)
 +      d->class->output_chart (d, chart);
 +#endif
 +
 +  chart_unref (chart);
  }
index 74840c30c5254e949d610a839d54047a0b46a093,cb2f346b1b4134a0092cb322e3fe2a7a16177c55..eabcf516e8e1cdc7b84a148646c3aef38de956bc
@@@ -1,5 -1,5 +1,5 @@@
  /* PSPP - a program for statistical analysis.
 -   Copyright (C) 2004 Free Software Foundation, Inc.
 +   Copyright (C) 2004, 2009 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 <config.h>
  
 +#include <output/charts/cartesian.h>
 +
 +#include <cairo/cairo.h>
  #include <math.h>
  #include <assert.h>
  
  #include <output/chart.h>
 -
 +#include <output/chart-provider.h>
  #include <output/charts/plot-chart.h>
 -#include <output/charts/cartesian.h>
  #include <libpspp/compiler.h>
  
- struct dataset
++#include "xalloc.h"
+ /* Start a new vector called NAME */
+ void
 -chart_vector_start (struct chart *ch, const char *name)
++chart_vector_start (cairo_t *cr, struct chart_geometry *geom, const char *name)
  {
-   int n_data;
-   const char *label;
- };
 -  if ( ! ch )
 -    return ;
++  const struct chart_colour *colour;
  
 -  pl_savestate_r (ch->lp);
++  cairo_save (cr);
  
- #define DATASETS 2
 -  pl_colorname_r (ch->lp, data_colour [ch->n_datasets % N_CHART_COLOURS]);
++  colour = &data_colour[geom->n_datasets % N_CHART_COLOURS];
++  cairo_set_source_rgb (cr,
++                        colour->red / 255.0,
++                        colour->green / 255.0,
++                        colour->blue / 255.0);
  
- static const struct dataset dataset[DATASETS] =
-   {
-     { 13, "male"},
-     { 11, "female"},
-   };
 -  ch->n_datasets++;
 -  ch->dataset = xrealloc (ch->dataset, ch->n_datasets * sizeof (*ch->dataset));
++  geom->n_datasets++;
++  geom->dataset = xrealloc (geom->dataset,
++                            geom->n_datasets * sizeof (*geom->dataset));
  
 -  ch->dataset[ch->n_datasets - 1] = strdup (name);
++  geom->dataset[geom->n_datasets - 1] = strdup (name);
+ }
  
  /* Plot a data point */
  void
 -chart_datum (struct chart *ch, int dataset UNUSED, double x, double y)
 +chart_datum (cairo_t *cr, const struct chart_geometry *geom,
 +             int dataset UNUSED, double x, double y)
  {
 -  if ( ! ch )
 -    return ;
 -
 -  {
 -    const double x_pos =
 -      (x - ch->x_min) * ch->abscissa_scale + ch->data_left ;
 +  double x_pos = (x - geom->x_min) * geom->abscissa_scale + geom->data_left;
 +  double y_pos = (y - geom->y_min) * geom->ordinate_scale + geom->data_bottom;
  
 -    const double y_pos =
 -      (y - ch->y_min) * ch->ordinate_scale + ch->data_bottom ;
 -
 -    pl_savestate_r(ch->lp);
 -
 -    pl_fmarker_r(ch->lp, x_pos, y_pos, 6, 15);
 -
 -    pl_restorestate_r(ch->lp);
 -  }
 +  chart_draw_marker (cr, x_pos, y_pos, MARKER_SQUARE, 15);
  }
  
 -chart_vector_end (struct chart *ch)
+ void
 -  pl_endpath_r (ch->lp);
 -  pl_colorname_r (ch->lp, "black");
 -  ch->in_path = false;
 -  pl_restorestate_r (ch->lp);
++chart_vector_end (cairo_t *cr, struct chart_geometry *geom)
+ {
 -chart_vector (struct chart *ch, double x, double y)
++  cairo_stroke (cr);
++  cairo_restore (cr);
++  geom->in_path = false;
+ }
+ /* Plot a data point */
+ void
 -  if ( ! ch )
 -    return ;
 -
 -  {
 -    const double x_pos =
 -      (x - ch->x_min) * ch->abscissa_scale + ch->data_left ;
 -
 -    const double y_pos =
 -      (y - ch->y_min) * ch->ordinate_scale + ch->data_bottom ;
 -
 -    if ( ch->in_path)
 -      pl_fcont_r (ch->lp, x_pos, y_pos);
 -    else
 -      {
 -      pl_fmove_r (ch->lp, x_pos, y_pos);
 -      ch->in_path = true;
 -      }
 -  }
++chart_vector (cairo_t *cr, struct chart_geometry *geom, double x, double y)
+ {
++  const double x_pos =
++    (x - geom->x_min) * geom->abscissa_scale + geom->data_left ;
++
++  const double y_pos =
++    (y - geom->y_min) * geom->ordinate_scale + geom->data_bottom ;
++
++  if (geom->in_path)
++    cairo_line_to (cr, x_pos, y_pos);
++  else
++    {
++      cairo_move_to (cr, x_pos, y_pos);
++      geom->in_path = true;
++    }
+ }
  /* Draw a line with slope SLOPE and intercept INTERCEPT.
     between the points limit1 and limit2.
     If lim_dim is CHART_DIM_Y then the limit{1,2} are on the
     y axis otherwise the x axis
  */
  void
 -chart_line (struct chart *ch, double slope, double intercept,
 +chart_line(cairo_t *cr, const struct chart_geometry *geom,
 +           double slope, double intercept,
           double limit1, double limit2, enum CHART_DIM lim_dim)
  {
    double x1, y1;
 -  double x2, y2 ;
 -
 -  if ( ! ch )
 -    return ;
 -
 +  double x2, y2;
  
    if ( lim_dim == CHART_DIM_Y )
      {
 -      x1 = ( limit1 - intercept ) / slope ;
 -      x2 = ( limit2 - intercept ) / slope ;
 +      x1 = ( limit1 - intercept ) / slope;
 +      x2 = ( limit2 - intercept ) / slope;
        y1 = limit1;
        y2 = limit2;
      }
        y2 = slope * x2 + intercept;
      }
  
 -  y1 = (y1 - ch->y_min) * ch->ordinate_scale + ch->data_bottom ;
 -  y2 = (y2 - ch->y_min) * ch->ordinate_scale + ch->data_bottom ;
 -  x1 = (x1 - ch->x_min) * ch->abscissa_scale + ch->data_left ;
 -  x2 = (x2 - ch->x_min) * ch->abscissa_scale + ch->data_left ;
 -
 -  pl_savestate_r(ch->lp);
 -
 -  pl_fline_r(ch->lp, x1, y1, x2, y2);
 +  y1 = (y1 - geom->y_min) * geom->ordinate_scale + geom->data_bottom;
 +  y2 = (y2 - geom->y_min) * geom->ordinate_scale + geom->data_bottom;
 +  x1 = (x1 - geom->x_min) * geom->abscissa_scale + geom->data_left;
 +  x2 = (x2 - geom->x_min) * geom->abscissa_scale + geom->data_left;
  
 -  pl_restorestate_r(ch->lp);
 +  cairo_move_to (cr, x1, y1);
 +  cairo_line_to (cr, x2, y2);
 +  cairo_stroke (cr);
  }
index feda1b6fb724d79201c1ee154d47827aa6e8c652,0874b9cc61d4d8f507188a00f07c44ca614176cf..3c21db6efc444c87cca867bbe184867dd1e707fe
@@@ -1,5 -1,5 +1,5 @@@
  /* PSPP - a program for statistical analysis.
 -   Copyright (C) 2004 Free Software Foundation, Inc.
 +   Copyright (C) 2004, 2009 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
@@@ -19,9 -19,6 +19,9 @@@
  #ifndef CARTESIAN_H
  #define CARTESIAN_H
  
 +#include <cairo/cairo.h>
 +#include <libpspp/compiler.h>
 +#include <output/chart.h>
  
  enum CHART_DIM
    {
      CHART_DIM_Y
    };
  
 +struct chart_geometry;
  
 -void chart_vector_start (struct chart *ch, const char *name);
 -void chart_vector (struct chart *ch, double x, double y);
 -void chart_vector_end (struct chart *ch);
++void  chart_vector_start (cairo_t *, struct chart_geometry *,
++                          const char *name);
++void chart_vector_end (cairo_t *, struct chart_geometry *);
++void chart_vector (cairo_t *, struct chart_geometry *, double x, double y);
  /* Plot a data point */
 -void chart_datum (struct chart *ch, int dataset UNUSED, double x, double y);
 +void chart_datum(cairo_t *, const struct chart_geometry *,
 +                 int dataset UNUSED, double x, double y);
  
  /* Draw a line with slope SLOPE and intercept INTERCEPT.
     between the points limit1 and limit2.
     If lim_dim is CHART_DIM_Y then the limit{1,2} are on the
     y axis otherwise the x axis
  */
 -void chart_line (struct chart *ch, double slope, double intercept,
 +void chart_line(cairo_t *, const struct chart_geometry *,
 +                double slope, double intercept,
                double limit1, double limit2, enum CHART_DIM lim_dim);
  
  
index 98e4c866e8a7afeae0b0d2653c2dcdc6103904d2,5641db1213be2cd070d66cffb7daf479823076f6..46884e19dfd3a1d662797be507199dbaa170a2c7
  
  #include <config.h>
  
 -#include <stdio.h>
 -#include <plot.h>
 -#include <stdarg.h>
 -#include <string.h>
 -#include <stdio.h>
 -#include <float.h>
 -#include <assert.h>
 -#include <math.h>
 -
  #include <output/charts/plot-chart.h>
  
 -#include <math/chart-geometry.h>
 -
 -
 +#include <assert.h>
 +#include <float.h>
 +#include <math.h>
 +#include <pango/pango-font.h>
 +#include <pango/pango-layout.h>
 +#include <pango/pango.h>
 +#include <pango/pangocairo.h>
 +#include <stdarg.h>
 +#include <stdio.h>
 +#include <stdio.h>
 +#include <string.h>
  
 -#include <libpspp/str.h>
  #include <libpspp/assertion.h>
 +#include <libpspp/str.h>
 +#include <math/chart-geometry.h>
 +#include <output/chart-provider.h>
  #include <output/manager.h>
  #include <output/output.h>
  
  #include "xalloc.h"
  
 -const char *const data_colour[N_CHART_COLOURS] =
 +const struct chart_colour data_colour[N_CHART_COLOURS] =
    {
 -    "brown",
 -    "red",
 -    "orange",
 -    "yellow",
 -    "green",
 -    "blue",
 -    "violet",
 -    "grey",
 -    "pink"
 +    { 165, 42, 42 },            /* brown */
 +    { 255, 0, 0 },              /* red */
 +    { 255, 165, 0 },            /* orange */
 +    { 255, 255, 0 },            /* yellow */
 +    { 0, 255, 0 },              /* green */
 +    { 0, 0, 255 },              /* blue */
 +    { 238, 130, 238 },          /* violet */
 +    { 190, 190, 190 },          /* grey */
 +    { 255, 192, 203 },          /* pink */
    };
  
 +void
 +chart_draw_marker (cairo_t *cr, double x, double y, enum marker_type marker,
 +                   double size)
 +{
 +  cairo_save (cr);
 +  cairo_translate (cr, x, y);
 +  cairo_scale (cr, size / 2.0, size / 2.0);
 +  cairo_set_line_width (cr, cairo_get_line_width (cr) / (size / 2.0));
 +  switch (marker)
 +    {
 +    case MARKER_CIRCLE:
 +      cairo_arc (cr, 0, 0, 1.0, 0, 2 * M_PI);
 +      cairo_stroke (cr);
 +      break;
 +
 +    case MARKER_ASTERISK:
 +      cairo_move_to (cr, 0, -1.0); /* | */
 +      cairo_line_to (cr, 0, 1.0);
 +      cairo_move_to (cr, -M_SQRT1_2, -M_SQRT1_2); /* / */
 +      cairo_line_to (cr, M_SQRT1_2, M_SQRT1_2);
 +      cairo_move_to (cr, -M_SQRT1_2, M_SQRT1_2); /* \ */
 +      cairo_line_to (cr, M_SQRT1_2, -M_SQRT1_2);
 +      cairo_stroke (cr);
 +      break;
 +
 +    case MARKER_SQUARE:
 +      cairo_rectangle (cr, -1.0, -1.0, 2.0, 2.0);
 +      cairo_stroke (cr);
 +      break;
 +    }
 +  cairo_restore (cr);
 +}
 +
 +void
 +chart_label (cairo_t *cr, int horz_justify, int vert_justify, double font_size,
 +             const char *string)
 +{
 +  PangoFontDescription *desc;
 +  PangoLayout *layout;
 +  double x, y;
 +
 +  desc = pango_font_description_from_string ("sans serif");
 +  if (desc == NULL)
 +    {
 +      cairo_new_path (cr);
 +      return;
 +    }
 +  pango_font_description_set_absolute_size (desc, font_size * PANGO_SCALE);
 +
 +  cairo_save (cr);
 +  cairo_get_current_point (cr, &x, &y);
 +  cairo_translate (cr, x, y);
 +  cairo_move_to (cr, 0, 0);
 +  cairo_scale (cr, 1.0, -1.0);
 +
 +  layout = pango_cairo_create_layout (cr);
 +  pango_layout_set_font_description (layout, desc);
 +  pango_layout_set_text (layout, string, -1);
 +  if (horz_justify != 'l')
 +    {
 +      int width_pango;
 +      double width;
 +
 +      pango_layout_get_size (layout, &width_pango, NULL);
 +      width = (double) width_pango / PANGO_SCALE;
 +      if (horz_justify == 'r')
 +        cairo_rel_move_to (cr, -width, 0);
 +      else
 +        cairo_rel_move_to (cr, -width / 2.0, 0);
 +    }
 +  if (vert_justify == 'x')
 +    {
 +      int baseline_pango = pango_layout_get_baseline (layout);
 +      double baseline = (double) baseline_pango / PANGO_SCALE;
 +      cairo_rel_move_to (cr, 0, -baseline);
 +    }
 +  else if (vert_justify != 't')
 +    {
 +      int height_pango;
 +      double height;
 +
 +      pango_layout_get_size (layout, NULL, &height_pango);
 +      height = (double) height_pango / PANGO_SCALE;
 +      if (vert_justify == 'b')
 +        cairo_rel_move_to (cr, 0, -height);
 +      else if (vert_justify == 'c')
 +        cairo_rel_move_to (cr, 0, -height / 2.0);
 +    }
 +  pango_cairo_show_layout (cr, layout);
 +  g_object_unref (layout);
  
 +  cairo_restore (cr);
 +
 +  cairo_new_path (cr);
 +
 +  pango_font_description_free (desc);
 +}
  
  /* Draw a tick mark at position
     If label is non zero, then print it at the tick mark
  */
  void
 -draw_tick(struct chart *chart,
 -        enum tick_orientation orientation,
 -        double position,
 -        const char *label, ...)
 +draw_tick (cairo_t *cr, const struct chart_geometry *geom,
 +           enum tick_orientation orientation,
 +           double position,
 +           const char *label, ...)
  {
    const int tickSize = 10;
 +  double x, y;
  
 -  assert(chart);
 -
 -  pl_savestate_r(chart->lp);
 -
 -  pl_move_r(chart->lp, chart->data_left, chart->data_bottom);
 +  cairo_move_to (cr, geom->data_left, geom->data_bottom);
  
 -  if ( orientation == TICK_ABSCISSA )
 -    pl_flinerel_r(chart->lp, position, 0, position, -tickSize);
 -  else if (orientation == TICK_ORDINATE )
 -      pl_flinerel_r(chart->lp, 0, position, -tickSize, position);
 +  if (orientation == TICK_ABSCISSA)
 +    {
 +      cairo_rel_move_to (cr, position, 0);
 +      cairo_rel_line_to (cr, 0, -tickSize);
 +    }
 +  else if (orientation == TICK_ORDINATE)
 +    {
 +      cairo_rel_move_to (cr, 0, position);
 +      cairo_rel_line_to (cr, -tickSize, 0);
 +    }
    else
      NOT_REACHED ();
 +  cairo_get_current_point (cr, &x, &y);
  
 -  if ( label ) {
 -    char buf[10];
 -    va_list ap;
 -    va_start(ap,label);
 -    vsnprintf(buf,10,label,ap);
 -
 -    if ( orientation == TICK_ABSCISSA )
 -      pl_alabel_r(chart->lp, 'c','t', buf);
 -    else if (orientation == TICK_ORDINATE )
 -      {
 -      if ( fabs(position) < DBL_EPSILON )
 -          pl_moverel_r(chart->lp, 0, 10);
 -
 -      pl_alabel_r(chart->lp, 'r','c', buf);
 -      }
 -
 -    va_end(ap);
 -  }
 +  cairo_stroke (cr);
  
 -  pl_restorestate_r(chart->lp);
 +  if (label != NULL)
 +    {
 +      va_list ap;
 +      char *s;
 +
 +      cairo_move_to (cr, x, y);
 +
 +      va_start (ap, label);
 +      s = xvasprintf (label, ap);
 +      if (orientation == TICK_ABSCISSA)
 +        chart_label (cr, 'c', 't', geom->font_size, s);
 +      else if (orientation == TICK_ORDINATE)
 +        {
 +          if (fabs (position) < DBL_EPSILON)
 +          cairo_rel_move_to (cr, 0, 10);
 +          chart_label (cr, 'r', 'c', geom->font_size, s);
 +        }
 +      free (s);
 +      va_end (ap);
 +    }
  }
  
  
  /* Write the title on a chart*/
  void
 -chart_write_title(struct chart *chart, const char *title, ...)
 +chart_write_title (cairo_t *cr, const struct chart_geometry *geom,
 +                   const char *title, ...)
  {
    va_list ap;
 -  char buf[100];
 -
 -  if ( ! chart )
 -        return ;
 +  char *s;
  
 -  pl_savestate_r(chart->lp);
 -  pl_ffontsize_r(chart->lp,chart->font_size * 1.5);
 -  pl_move_r(chart->lp,chart->data_left, chart->title_bottom);
 +  cairo_save (cr);
 +  cairo_move_to (cr, geom->data_left, geom->title_bottom);
  
 -  va_start(ap,title);
 -  vsnprintf(buf,100,title,ap);
 -  pl_alabel_r(chart->lp,0,0,buf);
 -  va_end(ap);
 +  va_start(ap, title);
 +  s = xvasprintf (title, ap);
 +  chart_label (cr, 'l', 'x', geom->font_size * 1.5, s);
 +  free (s);
 +  va_end (ap);
  
 -  pl_restorestate_r(chart->lp);
 +  cairo_restore (cr);
  }
  
  
  /* Set the scale for the abscissa */
  void
 -chart_write_xscale(struct chart *ch, double min, double max, int ticks)
 +chart_write_xscale (cairo_t *cr, struct chart_geometry *geom,
 +                    double min, double max, int ticks)
  {
    double x;
  
    const double tick_interval =
 -    chart_rounded_tick( (max - min) / (double) ticks);
 -
 -  assert ( ch );
 -
 +    chart_rounded_tick ((max - min) / (double) ticks);
  
 -  ch->x_max = ceil( max / tick_interval ) * tick_interval ;
 -  ch->x_min = floor ( min / tick_interval ) * tick_interval ;
 -
 -
 -  ch->abscissa_scale = fabs(ch->data_right - ch->data_left) /
 -    fabs(ch->x_max - ch->x_min);
 -
 -  for(x = ch->x_min ; x <= ch->x_max; x += tick_interval )
 -    {
 -      draw_tick (ch, TICK_ABSCISSA,
 -               (x - ch->x_min) * ch->abscissa_scale, "%g", x);
 -    }
 +  geom->x_max = ceil (max / tick_interval) * tick_interval;
 +  geom->x_min = floor (min / tick_interval) * tick_interval;
 +  geom->abscissa_scale = fabs(geom->data_right - geom->data_left) /
 +    fabs(geom->x_max - geom->x_min);
  
 +  for (x = geom->x_min; x <= geom->x_max; x += tick_interval)
 +    draw_tick (cr, geom, TICK_ABSCISSA,
 +               (x - geom->x_min) * geom->abscissa_scale, "%g", x);
  }
  
  
  /* Set the scale for the ordinate */
  void
 -chart_write_yscale(struct chart *ch, double smin, double smax, int ticks)
 +chart_write_yscale (cairo_t *cr, struct chart_geometry *geom,
 +                    double smin, double smax, int ticks)
  {
    double y;
  
    const double tick_interval =
 -    chart_rounded_tick(smax - smin) / (double) ticks);
 +    chart_rounded_tick ((smax - smin) / (double) ticks);
  
 -  if ( !ch )
 -        return;
 +  geom->y_max = ceil (smax / tick_interval) * tick_interval;
 +  geom->y_min = floor (smin / tick_interval) * tick_interval;
  
 -  ch->y_max = ceil  ( smax / tick_interval ) * tick_interval ;
 -  ch->y_min = floor ( smin / tick_interval ) * tick_interval ;
 +  geom->ordinate_scale =
 +    (fabs (geom->data_top - geom->data_bottom)
 +     / fabs (geom->y_max - geom->y_min));
  
 -  ch->ordinate_scale =
 -    fabs(ch->data_top -  ch->data_bottom) / fabs(ch->y_max - ch->y_min) ;
 -
 -  for(y = ch->y_min ; y <= ch->y_max; y += tick_interval )
 -    {
 -    draw_tick (ch, TICK_ORDINATE,
 -             (y - ch->y_min) * ch->ordinate_scale, "%g", y);
 -    }
 +  for (y = geom->y_min; y <= geom->y_max; y += tick_interval)
 +    draw_tick (cr, geom, TICK_ORDINATE,
 +             (y - geom->y_min) * geom->ordinate_scale, "%g", y);
  }
  
 -
  /* Write the abscissa label */
  void
 -chart_write_xlabel(struct chart *ch, const char *label)
 +chart_write_xlabel (cairo_t *cr, const struct chart_geometry *geom,
 +                    const char *label)
  {
 -  if ( ! ch )
 -    return ;
 -
 -  pl_savestate_r(ch->lp);
 -
 -  pl_move_r(ch->lp,ch->data_left, ch->abscissa_top);
 -  pl_alabel_r(ch->lp,0,'t',label);
 -
 -  pl_restorestate_r(ch->lp);
 -
 +  cairo_move_to (cr, geom->data_left, geom->abscissa_top);
 +  chart_label (cr, 'l', 't', geom->font_size, label);
  }
  
 -
 -
  /* Write the ordinate label */
  void
 -chart_write_ylabel(struct chart *ch, const char *label)
 +chart_write_ylabel (cairo_t *cr, const struct chart_geometry *geom,
 +                    const char *label)
  {
 -  if ( ! ch )
 -    return ;
 -
 -  pl_savestate_r(ch->lp);
 -
 -  pl_move_r(ch->lp, ch->data_bottom, ch->ordinate_right);
 -  pl_textangle_r(ch->lp, 90);
 -  pl_alabel_r(ch->lp, 0, 0, label);
 -
 -  pl_restorestate_r(ch->lp);
 +  cairo_save (cr);
 +  cairo_translate (cr, -geom->data_bottom, -geom->ordinate_right);
 +  cairo_move_to (cr, 0, 0);
 +  cairo_rotate (cr, M_PI / 2.0);
 +  chart_label (cr, 'l', 'x', geom->font_size, label);
 +  cairo_restore (cr);
  }
 -chart_write_legend (struct chart *ch)
+ void
 -  const int vstep = ch->font_size * 2;
++chart_write_legend (cairo_t *cr, const struct chart_geometry *geom)
+ {
+   int i;
 -  const int legend_top = ch->data_top;
++  const int vstep = geom->font_size * 2;
+   const int xpad = 10;
+   const int ypad = 10;
+   const int swatch = 20;
 -    (vstep * ch->n_datasets + 2 * ypad );
++  const int legend_top = geom->data_top;
+   const int legend_bottom = legend_top -
 -  if ( ! ch )
 -    return ;
++    (vstep * geom->n_datasets + 2 * ypad );
 -  pl_savestate_r (ch->lp);
++  cairo_save (cr);
 -  pl_box_r (ch->lp, ch->legend_left, legend_top,
 -          ch->legend_right - xpad, legend_bottom);
 -
 -  for (i = 0 ; i < ch->n_datasets ; ++i )
++  cairo_rectangle (cr, geom->legend_left, legend_top,
++                   geom->legend_right - xpad - geom->legend_left,
++                   legend_bottom - legend_top);
++  cairo_stroke (cr);
 -      const int ypos = vstep * (i + 1);
 -      const int xpos = ch->legend_left + xpad;
 -      pl_move_r (ch->lp, xpos, legend_top - ypos);
 -
 -      pl_savestate_r(ch->lp);
 -       pl_fillcolorname_r (ch->lp, data_colour [ i % N_CHART_COLOURS]);
 -
 -       pl_pentype_r (ch->lp, 1);
 -       pl_filltype_r (ch->lp, 1);
 -       pl_boxrel_r (ch->lp, 0, 0, swatch, swatch);
 -
 -
 -      pl_moverel_r (ch->lp, swatch, 0);
 -      pl_alabel_r (ch->lp, 0, 0, ch->dataset[i]);
 -
 -      pl_restorestate_r (ch->lp);
++  for (i = 0 ; i < geom->n_datasets ; ++i )
+     {
 -  pl_restorestate_r (ch->lp);
++      const int ypos = legend_top - vstep * (i + 1);
++      const int xpos = geom->legend_left + xpad;
++      const struct chart_colour *colour;
++
++      cairo_move_to (cr, xpos, ypos);
++
++      cairo_save (cr);
++      colour = &data_colour [ i % N_CHART_COLOURS];
++      cairo_set_source_rgb (cr,
++                            colour->red / 255.0,
++                            colour->green / 255.0,
++                            colour->blue / 255.0);
++      cairo_rectangle (cr, xpos, ypos, swatch, swatch);
++      cairo_fill_preserve (cr);
++      cairo_stroke (cr);
++      cairo_restore (cr);
++
++      cairo_move_to (cr, xpos + swatch * 1.5, ypos);
++      chart_label (cr, 'l', 'x', geom->font_size, geom->dataset[i]);
+     }
++  cairo_restore (cr);
+ }
index 03788f007507a0e2e0ad55e8c650460991e78a2a,f4cc5bbf06c3f91650c4988b15e76e36f8efc74a..896b630b137e7245f679c15c5437e7b8a47168fe
@@@ -1,5 -1,5 +1,5 @@@
  /* PSPP - a program for statistical analysis.
 -   Copyright (C) 2004 Free Software Foundation, Inc.
 +   Copyright (C) 2004, 2009 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
     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>. */
  
 +#ifndef PLOT_CHART_H
 +#define PLOT_CHART_H
 +
 +#include <cairo/cairo.h>
  #include <stdio.h>
  #include <stdarg.h>
  #include <string.h>
  
  #include <math/chart-geometry.h>
  #include <output/chart.h>
 +#include <output/chart-provider.h>
  
 +#include <libpspp/compiler.h>
  #include <libpspp/str.h>
  #include <output/manager.h>
  #include <output/output.h>
  
 -#include "xalloc.h"
 -
 -#ifndef PLOT_CHART_H
 -#define PLOT_CHART_H
 -
  #define N_CHART_COLOURS 9
 -extern const char *const data_colour[];
 +extern const struct chart_colour data_colour[];
  
  enum tick_orientation
    {
      TICK_ORDINATE
    };
  
 +struct chart_geometry;
 +
 +
 +enum marker_type
 +  {
 +    MARKER_CIRCLE,              /* Hollow circle. */
 +    MARKER_ASTERISK,            /* Asterisk (*). */
 +    MARKER_SQUARE               /* Hollow square. */
 +  };
 +
 +void chart_draw_marker (cairo_t *, double x, double y, enum marker_type,
 +                        double size);
 +
 +void chart_label (cairo_t *, int horz_justify, int vert_justify,
 +                  double font_size, const char *);
  
  /* Draw a tick mark at position
     If label is non zero, then print it at the tick mark
  */
 -void draw_tick(struct chart *chart,
 +void draw_tick(cairo_t *, const struct chart_geometry *,
          enum tick_orientation orientation,
          double position,
 -             const char *label, ...);
 +             const char *label, ...)
 +  PRINTF_FORMAT (5, 6);
  
  
  /* Write the title on a chart*/
 -void   chart_write_title(struct chart *chart, const char *title, ...);
 +void   chart_write_title(cairo_t *, const struct chart_geometry *,
 +                         const char *title, ...)
 +  PRINTF_FORMAT (3, 4);
  
  
  /* Set the scale for the abscissa */
 -void  chart_write_xscale(struct chart *ch, double min, double max, int ticks);
 +void  chart_write_xscale(cairo_t *, struct chart_geometry *,
 +                         double min, double max, int ticks);
  
  
  /* Set the scale for the ordinate */
 -void  chart_write_yscale(struct chart *ch, double smin, double smax, int ticks);
 +void  chart_write_yscale(cairo_t *, struct chart_geometry *,
 +                         double smin, double smax, int ticks);
  
 -void chart_write_xlabel(struct chart *ch, const char *label) ;
 +void chart_write_xlabel(cairo_t *, const struct chart_geometry *,
 +                        const char *label) ;
  
  /* Write the ordinate label */
 -void  chart_write_ylabel(struct chart *ch, const char *label);
 +void  chart_write_ylabel(cairo_t *, const struct chart_geometry *,
 +                         const char *label);
  
 -void chart_write_legend (struct chart *ch);
++void chart_write_legend (cairo_t *, const struct chart_geometry *);
  #endif
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2094ede545c79edb0c264a2c63c0630cfef1339f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,148 @@@
++/* PSPP - a program for statistical analysis.
++   Copyright (C) 2009 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
++   the Free Software Foundation, either version 3 of the License, or
++   (at your option) any later version.
++
++   This program is distributed in the hope that it will be useful,
++   but WITHOUT ANY WARRANTY; without even the implied warranty of
++   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++   GNU General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
++
++#include <config.h>
++
++#include <output/charts/roc-chart.h>
++
++#include <output/chart-provider.h>
++#include <output/charts/cartesian.h>
++#include <output/charts/plot-chart.h>
++#include <data/casereader.h>
++#include <language/stats/roc.h>
++
++#include "xalloc.h"
++
++#include "gettext.h"
++#define _(msgid) gettext (msgid)
++
++struct roc_var
++  {
++    char *name;
++    struct casereader *cutpoint_reader;
++  };
++
++struct roc_chart
++  {
++    struct chart chart;
++    bool reference;
++    struct roc_var *vars;
++    size_t n_vars;
++    size_t allocated_vars;
++  };
++
++static const struct chart_class roc_chart_class;
++
++struct roc_chart *
++roc_chart_create (bool reference)
++{
++  struct roc_chart *rc = xmalloc (sizeof *rc);
++  chart_init (&rc->chart, &roc_chart_class);
++  rc->reference = reference;
++  rc->vars = NULL;
++  rc->n_vars = 0;
++  rc->allocated_vars = 0;
++  return rc;
++}
++
++void
++roc_chart_add_var (struct roc_chart *rc, const char *var_name,
++                   const struct casereader *cutpoint_reader)
++{
++  struct roc_var *rv;
++
++  if (rc->n_vars >= rc->allocated_vars)
++    rc->vars = x2nrealloc (rc->vars, &rc->allocated_vars, sizeof *rc->vars);
++
++  rv = &rc->vars[rc->n_vars++];
++  rv->name = xstrdup (var_name);
++  rv->cutpoint_reader = casereader_clone (cutpoint_reader);
++}
++
++struct chart *
++roc_chart_get_chart (struct roc_chart *rc)
++{
++  return &rc->chart;
++}
++
++static void
++roc_chart_draw (const struct chart *chart, cairo_t *cr,
++                struct chart_geometry *geom)
++{
++  const struct roc_chart *rc = UP_CAST (chart, struct roc_chart, chart);
++  size_t i;
++
++  chart_write_title (cr, geom, _("ROC Curve"));
++  chart_write_xlabel (cr, geom, _("1 - Specificity"));
++  chart_write_ylabel (cr, geom, _("Sensitivity"));
++
++  chart_write_xscale (cr, geom, 0, 1, 5);
++  chart_write_yscale (cr, geom, 0, 1, 5);
++
++  if ( rc->reference )
++    {
++      chart_line (cr, geom, 1.0, 0,
++                0.0, 1.0,
++                CHART_DIM_X);
++    }
++
++  for (i = 0; i < rc->n_vars; ++i)
++    {
++      const struct roc_var *rv = &rc->vars[i];
++      struct casereader *r = casereader_clone (rv->cutpoint_reader);
++      struct ccase *cc;
++
++      chart_vector_start (cr, geom, rv->name);
++      for (; (cc = casereader_read (r)) != NULL; case_unref (cc))
++      {
++        double se = case_data_idx (cc, ROC_TP)->f;
++        double sp = case_data_idx (cc, ROC_TN)->f;
++
++        se /= case_data_idx (cc, ROC_FN)->f + case_data_idx (cc, ROC_TP)->f ;
++        sp /= case_data_idx (cc, ROC_TN)->f + case_data_idx (cc, ROC_FP)->f ;
++
++        chart_vector (cr, geom, 1 - sp, se);
++      }
++      chart_vector_end (cr, geom);
++      casereader_destroy (r);
++    }
++
++  chart_write_legend (cr, geom);
++}
++
++static void
++roc_chart_destroy (struct chart *chart)
++{
++  struct roc_chart *rc = UP_CAST (chart, struct roc_chart, chart);
++  size_t i;
++
++  for (i = 0; i < rc->n_vars; i++)
++    {
++      struct roc_var *rv = &rc->vars[i];
++      free (rv->name);
++      casereader_destroy (rv->cutpoint_reader);
++    }
++  free (rc->vars);
++  free (rc);
++}
++
++static const struct chart_class roc_chart_class =
++  {
++    roc_chart_draw,
++    roc_chart_destroy
++  };
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..dca842071a667f4fe05c379096977202d5755ebf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,29 @@@
++/* PSPP - a program for statistical analysis.
++   Copyright (C) 2009 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
++   the Free Software Foundation, either version 3 of the License, or
++   (at your option) any later version.
++
++   This program is distributed in the hope that it will be useful,
++   but WITHOUT ANY WARRANTY; without even the implied warranty of
++   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++   GNU General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
++
++#ifndef OUTPUT_CHARTS_ROC_CHART_H
++#define OUTPUT_CHARTS_ROC_CHART_H 1
++
++#include <stdbool.h>
++
++struct casereader;
++
++struct roc_chart *roc_chart_create (bool reference);
++void roc_chart_add_var (struct roc_chart *, const char *var_name,
++                        const struct casereader *cutpoint_reader);
++struct chart *roc_chart_get_chart (struct roc_chart *);
++
++#endif /* output/charts/roc-chart.h */
diff --combined src/output/table.c
index c94532e81e6f31f4a71d9facf0b6b1c5322343e8,72edf17f08b7f6e9e0dec91ad8b56f5b9e87b88b..f8736e629c0148279ae8ed18b120c519d7de5271
@@@ -29,6 -29,7 +29,7 @@@
  #include <data/data-out.h>
  #include <data/format.h>
  #include <data/value.h>
+ #include <data/dictionary.h>
  #include <libpspp/assertion.h>
  #include <libpspp/compiler.h>
  #include <libpspp/misc.h>
@@@ -36,7 -37,6 +37,7 @@@
  
  #include <data/settings.h>
  
 +#include "error.h"
  #include "minmax.h"
  #include "xalloc.h"
  
@@@ -44,6 -44,7 +45,6 @@@
  #define _(msgid) gettext (msgid)
  \f
  const struct som_table_class tab_table_class;
 -static char *command_name;
  
  /* Returns the font to use for a cell with the given OPTIONS. */
  static enum outp_font
@@@ -56,13 -57,13 +57,13 @@@ options_to_font (unsigned options
  
  /* Creates a table with NC columns and NR rows. */
  struct tab_table *
 -tab_create (int nc, int nr, int reallocable UNUSED)
 +tab_create (int nc, int nr)
  {
    struct tab_table *t;
  
    t = pool_create_container (struct tab_table, container);
 +  t->ref_cnt = 1;
    t->col_style = TAB_COL_NONE;
 -  t->col_group = 0;
    t->title = NULL;
    t->flags = SOMF_NONE;
    t->nr = nr;
    memset (t->rv, UCHAR_MAX, nr * (nc + 1));
  
    t->dim = NULL;
 -  t->w = t->h = NULL;
    t->col_ofs = t->row_ofs = 0;
  
    return t;
  }
  
 -/* Destroys table T. */
 +/* Increases T's reference count and, if this causes T's
 +   reference count to reach 0, destroys T. */
  void
  tab_destroy (struct tab_table *t)
  {
 -  assert (t != NULL);
 +  assert (t->ref_cnt > 0);
 +  if (--t->ref_cnt > 0)
 +    return;
 +  if (t->dim_free != NULL)
 +    t->dim_free (t->dim_aux);
    free (t->title);
    pool_destroy (t->container);
  }
  
 +/* Increases T's reference count. */
 +void
 +tab_ref (struct tab_table *t)
 +{
 +  assert (t->ref_cnt > 0);
 +  t->ref_cnt++;
 +}
 +
  /* Sets the width and height of a table, in columns and rows,
     respectively.  Use only to reduce the size of a table, since it
     does not change the amount of allocated memory. */
@@@ -121,7 -110,7 +122,7 @@@ tab_resize (struct tab_table *t, int nc
      }
    if (nr != -1)
      {
 -      assert (nr + t->row_ofs <= t->nr);
 +      assert (nr + t->row_ofs <= tab_nr (t));
        t->nr = nr + t->row_ofs;
      }
  }
@@@ -144,16 -133,16 +145,16 @@@ tab_realloc (struct tab_table *t, int n
      tab_offset (t, 0, 0);
  
    if (nc == -1)
 -    nc = t->nc;
 +    nc = tab_nc (t);
    if (nr == -1)
 -    nr = t->nr;
 +    nr = tab_nr (t);
  
 -  assert (nc == t->nc);
 +  assert (nc == tab_nc (t));
  
    if (nc > t->cf)
      {
 -      int mr1 = MIN (nr, t->nr);
 -      int mc1 = MIN (nc, t->nc);
 +      int mr1 = MIN (nr, tab_nr (t));
 +      int mc1 = MIN (nc, tab_nc (t));
  
        struct substring *new_cc;
        unsigned char *new_ct;
        new_ct = pool_malloc (t->container, nr * nc);
        for (r = 0; r < mr1; r++)
        {
 -        memcpy (&new_cc[r * nc], &t->cc[r * t->nc], mc1 * sizeof *t->cc);
 -        memcpy (&new_ct[r * nc], &t->ct[r * t->nc], mc1);
 -        memset (&new_ct[r * nc + t->nc], TAB_EMPTY, nc - t->nc);
 +        memcpy (&new_cc[r * nc], &t->cc[r * tab_nc (t)], mc1 * sizeof *t->cc);
 +        memcpy (&new_ct[r * nc], &t->ct[r * tab_nc (t)], mc1);
 +        memset (&new_ct[r * nc + tab_nc (t)], TAB_EMPTY, nc - tab_nc (t));
        }
        pool_free (t->container, t->cc);
        pool_free (t->container, t->ct);
        t->ct = new_ct;
        t->cf = nc;
      }
 -  else if (nr != t->nr)
 +  else if (nr != tab_nr (t))
      {
        t->cc = pool_nrealloc (t->container, t->cc, nr * nc, sizeof *t->cc);
        t->ct = pool_realloc (t->container, t->ct, nr * nc);
        t->rh = pool_nrealloc (t->container, t->rh, nc, nr + 1);
        t->rv = pool_nrealloc (t->container, t->rv, nr, nc + 1);
  
 -      if (nr > t->nr)
 +      if (nr > tab_nr (t))
        {
 -        memset (&t->rh[nc * (t->nr + 1)], TAL_0, (nr - t->nr) * nc);
 -        memset (&t->rv[(nc + 1) * t->nr], UCHAR_MAX,
 -                  (nr - t->nr) * (nc + 1));
 +        memset (&t->rh[nc * (tab_nr (t) + 1)], TAL_0, (nr - tab_nr (t)) * nc);
 +        memset (&t->rv[(nc + 1) * tab_nr (t)], UCHAR_MAX,
 +                  (nr - tab_nr (t)) * (nc + 1));
        }
      }
  
 -  memset (&t->ct[nc * t->nr], TAB_EMPTY, nc * (nr - t->nr));
 +  memset (&t->ct[nc * tab_nr (t)], TAB_EMPTY, nc * (nr - tab_nr (t)));
  
    t->nr = nr;
    t->nc = nc;
@@@ -221,12 -210,14 +222,12 @@@ tab_headers (struct tab_table *table, i
  /* Set up table T so that, when it is an appropriate size, it will be
     displayed across the page in columns.
  
 -   STYLE is a TAB_COL_* constant.  GROUP is the number of rows to take
 -   as a unit. */
 +   STYLE is a TAB_COL_* constant. */
  void
 -tab_columns (struct tab_table *t, int style, int group)
 +tab_columns (struct tab_table *t, int style)
  {
    assert (t != NULL);
    t->col_style = style;
 -  t->col_group = group;
  }
  \f
  /* Rules. */
@@@ -239,16 -230,16 +240,16 @@@ tab_vline (struct tab_table *t, int sty
    assert (t != NULL);
  
  #if DEBUGGING
 -  if (x + t->col_ofs < 0 || x + t->col_ofs > t->nc
 -      || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= t->nr
 -      || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= t->nr)
 +  if (x + t->col_ofs < 0 || x + t->col_ofs > tab_nc (t)
 +      || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= tab_nr (t)
 +      || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= tab_nr (t))
      {
        printf (_("bad vline: x=%d+%d=%d y=(%d+%d=%d,%d+%d=%d) in "
                "table size (%d,%d)\n"),
              x, t->col_ofs, x + t->col_ofs,
              y1, t->row_ofs, y1 + t->row_ofs,
              y2, t->row_ofs, y2 + t->row_ofs,
 -            t->nc, t->nr);
 +            tab_nc (t), tab_nr (t));
        return;
      }
  #endif
    y2 += t->row_ofs;
  
    assert (x  > 0);
 -  assert (x  < t->nc);
 +  assert (x  < tab_nc (t));
    assert (y1 >= 0);
    assert (y2 >= y1);
 -  assert (y2 <=  t->nr);
 +  assert (y2 <=  tab_nr (t));
  
    if (style != -1)
      {
@@@ -283,10 -274,10 +284,10 @@@ tab_hline (struct tab_table * t, int st
    y += t->row_ofs;
  
    assert (y >= 0);
 -  assert (y <= t->nr);
 +  assert (y <= tab_nr (t));
    assert (x2 >= x1 );
    assert (x1 >= 0 );
 -  assert (x2 < t->nc);
 +  assert (x2 < tab_nc (t));
  
    if (style != -1)
      {
@@@ -309,10 -300,10 +310,10 @@@ tab_box (struct tab_table *t, int f_h, 
    assert (t != NULL);
  
  #if DEBUGGING
 -  if (x1 + t->col_ofs < 0 || x1 + t->col_ofs >= t->nc
 -      || x2 + t->col_ofs < 0 || x2 + t->col_ofs >= t->nc
 -      || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= t->nr
 -      || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= t->nr)
 +  if (x1 + t->col_ofs < 0 || x1 + t->col_ofs >= tab_nc (t)
 +      || x2 + t->col_ofs < 0 || x2 + t->col_ofs >= tab_nc (t)
 +      || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= tab_nr (t)
 +      || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= tab_nr (t))
      {
        printf (_("bad box: (%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) "
                "in table size (%d,%d)\n"),
              y1, t->row_ofs, y1 + t->row_ofs,
              x2, t->col_ofs, x2 + t->col_ofs,
              y2, t->row_ofs, y2 + t->row_ofs,
 -            t->nc, t->nr);
 +            tab_nc (t), tab_nr (t));
        NOT_REACHED ();
      }
  #endif
    assert (y2 >= y1);
    assert (x1 >= 0);
    assert (y1 >= 0);
 -  assert (x2 < t->nc);
 -  assert (y2 < t->nr);
 +  assert (x2 < tab_nc (t));
 +  assert (y2 < tab_nr (t));
  
    if (f_h != -1)
      {
@@@ -395,22 -386,12 +396,22 @@@ tab_title (struct tab_table *t, const c
    va_end (args);
  }
  
 -/* Set DIM_FUNC as the dimension function for table T. */
 +/* Set DIM_FUNC, which will be passed auxiliary data AUX, as the
 +   dimension function for table T.
 +
 +   DIM_FUNC must not assume that it is called from the same
 +   context as tab_dim; for example, table T might be kept in
 +   memory and, thus, DIM_FUNC might be called after the currently
 +   running command completes.  If it is non-null, FREE_FUNC is
 +   called when the table is destroyed, to allow any data
 +   allocated for use by DIM_FUNC to be freed.  */
  void
 -tab_dim (struct tab_table *t, tab_dim_func *dim_func, void *aux)
 +tab_dim (struct tab_table *t,
 +         tab_dim_func *dim_func, tab_dim_free_func *free_func, void *aux)
  {
 -  assert (t != NULL && t->dim == NULL);
 +  assert (t->dim == NULL);
    t->dim = dim_func;
 +  t->dim_free = free_func;
    t->dim_aux = aux;
  }
  
     wrapping.  The width will be no larger than the page width minus
     left and right rule widths. */
  int
 -tab_natural_width (struct tab_table *t, struct outp_driver *d, int c)
 +tab_natural_width (const struct tab_rendering *r, int col)
  {
 -  int width;
 +  const struct tab_table *t = r->table;
 +  int width, row, max_width;
  
 -  assert (t != NULL && c >= 0 && c < t->nc);
 -  {
 -    int r;
 +  assert (col >= 0 && col < tab_nc (t));
  
 -    for (width = r = 0; r < t->nr; r++)
 -      {
 -      struct outp_text text;
 -      unsigned char opt = t->ct[c + r * t->cf];
 -        int w;
 +  width = 0;
 +  for (row = 0; row < tab_nr (t); row++)
 +    {
 +      struct outp_text text;
 +      unsigned char opt = t->ct[col + row * t->cf];
 +      int w;
  
 -      if (opt & (TAB_JOIN | TAB_EMPTY))
 -        continue;
 +      if (opt & (TAB_JOIN | TAB_EMPTY))
 +        continue;
  
 -      text.string = t->cc[c + r * t->cf];
 -      text.justification = OUTP_LEFT;
 -        text.font = options_to_font (opt);
 -        text.h = text.v = INT_MAX;
 +      text.string = t->cc[col + row * t->cf];
 +      text.justification = OUTP_LEFT;
 +      text.font = options_to_font (opt);
 +      text.h = text.v = INT_MAX;
  
 -      d->class->text_metrics (d, &text, &w, NULL);
 -      if (w > width)
 -        width = w;
 -      }
 -  }
 +      r->driver->class->text_metrics (r->driver, &text, &w, NULL);
 +      if (w > width)
 +        width = w;
 +    }
  
    if (width == 0)
      {
        /* FIXME: This is an ugly kluge to compensate for the fact
           that we don't let joined cells contribute to column
           widths. */
 -      width = d->prop_em_width * 8;
 +      width = r->driver->prop_em_width * 8;
      }
  
 -  {
 -    const int clamp = d->width - t->wrv[0] - t->wrv[t->nc];
 -
 -    if (width > clamp)
 -      width = clamp;
 -  }
 -
 -  return width;
 +  max_width = r->driver->width - r->wrv[0] - r->wrv[tab_nc (t)];
 +  return MIN (width, max_width);
  }
  
  /* Returns the natural height of row R in table T for driver D, that
     is, the minimum height necessary to display the information in the
     cell at the widths set for each column. */
  int
 -tab_natural_height (struct tab_table *t, struct outp_driver *d, int r)
 +tab_natural_height (const struct tab_rendering *r, int row)
  {
 -  int height;
 +  const struct tab_table *t = r->table;
 +  int height, col;
  
 -  assert (t != NULL && r >= 0 && r < t->nr);
 +  assert (row >= 0 && row < tab_nr (t));
  
 -  {
 -    int c;
 -
 -    for (height = d->font_height, c = 0; c < t->nc; c++)
 -      {
 -      struct outp_text text;
 -      unsigned char opt = t->ct[c + r * t->cf];
 -        int h;
 -
 -      if (opt & (TAB_JOIN | TAB_EMPTY))
 -        continue;
 -
 -      text.string = t->cc[c + r * t->cf];
 -        text.justification = OUTP_LEFT;
 -        text.font = options_to_font (opt);
 -      text.h = t->w[c];
 -        text.v = INT_MAX;
 -      d->class->text_metrics (d, &text, NULL, &h);
 -
 -      if (h > height)
 -        height = h;
 -      }
 -  }
 +  height = r->driver->font_height;
 +  for (col = 0; col < tab_nc (t); col++)
 +    {
 +      struct outp_text text;
 +      unsigned char opt = t->ct[col + row * t->cf];
 +      int h;
 +
 +      if (opt & (TAB_JOIN | TAB_EMPTY))
 +        continue;
 +
 +      text.string = t->cc[col + row * t->cf];
 +      text.justification = OUTP_LEFT;
 +      text.font = options_to_font (opt);
 +      text.h = r->w[col];
 +      text.v = INT_MAX;
 +      r->driver->class->text_metrics (r->driver, &text, NULL, &h);
 +
 +      if (h > height)
 +        height = h;
 +    }
  
    return height;
  }
  /* Callback function to set all columns and rows to their natural
     dimensions.  Not really meant to be called directly.  */
  void
 -tab_natural_dimensions (struct tab_table *t, struct outp_driver *d,
 -                        void *aux UNUSED)
 +tab_natural_dimensions (struct tab_rendering *r, void *aux UNUSED)
  {
 +  const struct tab_table *t = r->table;
    int i;
  
 -  assert (t != NULL);
 -
 -  for (i = 0; i < t->nc; i++)
 -    t->w[i] = tab_natural_width (t, d, i);
 +  for (i = 0; i < tab_nc (t); i++)
 +    r->w[i] = tab_natural_width (r, i);
  
 -  for (i = 0; i < t->nr; i++)
 -    t->h[i] = tab_natural_height (t, d, i);
 +  for (i = 0; i < tab_nr (t); i++)
 +    r->h[i] = tab_natural_height (r, i);
  }
  
  \f
     from V, displayed with format spec F. */
  void
  tab_value (struct tab_table *table, int c, int r, unsigned char opt,
-          const union value *v, const struct fmt_spec *f)
+          const union value *v, const struct dictionary *dict, 
+          const struct fmt_spec *f)
  {
    char *contents;
  
    assert (table != NULL && v != NULL && f != NULL);
  #if DEBUGGING
    if (c + table->col_ofs < 0 || r + table->row_ofs < 0
 -      || c + table->col_ofs >= table->nc
 -      || r + table->row_ofs >= table->nr)
 +      || c + table->col_ofs >= tab_nc (table)
 +      || r + table->row_ofs >= tab_nr (table))
      {
        printf ("tab_value(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
              "(%d,%d)\n",
              c, table->col_ofs, c + table->col_ofs,
              r, table->row_ofs, r + table->row_ofs,
 -            table->nc, table->nr);
 +            tab_nc (table), tab_nr (table));
        return;
      }
  #endif
  
-   contents = pool_alloc (table->container, f->w);
-   table->cc[c + r * table->cf] = ss_buffer (contents, f->w);
-   table->ct[c + r * table->cf] = opt;
+   contents = data_out_pool (v, dict_get_encoding (dict), f, table->container);
  
-   data_out (v, f, contents);
+   table->cc[c + r * table->cf] = ss_cstr (contents);
+   table->ct[c + r * table->cf] = opt;
  }
  
  /* Sets cell (C,R) in TABLE, with options OPT, to have value VAL
@@@ -547,8 -539,7 +548,7 @@@ voi
  tab_fixed (struct tab_table *table, int c, int r, unsigned char opt,
           double val, int w, int d)
  {
-   char *contents;
-   char buf[40], *cp;
+   char *s, *cp;
  
    struct fmt_spec f;
    union value double_value;
    assert (table != NULL && w <= 40);
  
    assert (c >= 0);
 -  assert (c < table->nc);
 +  assert (c < tab_nc (table));
    assert (r >= 0);
 -  assert (r < table->nr);
 +  assert (r < tab_nr (table));
  
    f = fmt_for_output (FMT_F, w, d);
  
  #if DEBUGGING
    if (c + table->col_ofs < 0 || r + table->row_ofs < 0
 -      || c + table->col_ofs >= table->nc
 -      || r + table->row_ofs >= table->nr)
 +      || c + table->col_ofs >= tab_nc (table)
 +      || r + table->row_ofs >= tab_nr (table))
      {
        printf ("tab_fixed(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
              "(%d,%d)\n",
              c, table->col_ofs, c + table->col_ofs,
              r, table->row_ofs, r + table->row_ofs,
 -            table->nc, table->nr);
 +            tab_nc (table), tab_nr (table));
        return;
      }
  #endif
  
    double_value.f = val;
-   data_out (&double_value, &f, buf);
+   s = data_out_pool (&double_value, LEGACY_NATIVE, &f, table->container);
  
-   cp = buf;
-   while (isspace ((unsigned char) *cp) && cp < &buf[w])
+   cp = s;
+   while (isspace ((unsigned char) *cp) && cp < &s[w])
      cp++;
-   f.w = w - (cp - buf);
+   f.w = w - (cp - s);
  
-   contents = pool_alloc (table->container, f.w);
-   table->cc[c + r * table->cf] = ss_buffer (contents, f.w);
+   table->cc[c + r * table->cf] = ss_buffer (cp, f.w);
    table->ct[c + r * table->cf] = opt;
-   memcpy (contents, cp, f.w);
  }
  
  /* Sets cell (C,R) in TABLE, with options OPT, to have value VAL as
@@@ -598,18 -587,15 +596,15 @@@ voi
  tab_double (struct tab_table *table, int c, int r, unsigned char opt,
           double val, const struct fmt_spec *fmt)
  {
-   int w;
-   char *contents;
-   char buf[40], *cp;
-   union value double_value;
+   struct substring ss;
+   union value double_value ;
  
    assert (table != NULL);
  
    assert (c >= 0);
 -  assert (c < table->nc);
 +  assert (c < tab_nc (table));
    assert (r >= 0);
 -  assert (r < table->nr);
 +  assert (r < tab_nr (table));
  
    if ( fmt == NULL)
      fmt = settings_get_format ();
  
  #if DEBUGGING
    if (c + table->col_ofs < 0 || r + table->row_ofs < 0
 -      || c + table->col_ofs >= table->nc
 -      || r + table->row_ofs >= table->nr)
 +      || c + table->col_ofs >= tab_nc (table)
 +      || r + table->row_ofs >= tab_nr (table))
      {
        printf ("tab_double(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
              "(%d,%d)\n",
              c, table->col_ofs, c + table->col_ofs,
              r, table->row_ofs, r + table->row_ofs,
 -            table->nc, table->nr);
 +            tab_nc (table), tab_nr (table));
        return;
      }
  #endif
  
    double_value.f = val;
-   data_out (&double_value, fmt, buf);
+   ss = ss_cstr (data_out_pool (&double_value, LEGACY_NATIVE, fmt, table->container));
  
-   cp = buf;
-   while (isspace ((unsigned char) *cp) && cp < &buf[fmt->w])
-     cp++;
-   w = fmt->w - (cp - buf);
+   ss_ltrim (&ss, ss_cstr (" "));
  
-   contents = pool_alloc (table->container, w);
-   table->cc[c + r * table->cf] = ss_buffer (contents, w);
+   table->cc[c + r * table->cf] = ss;
    table->ct[c + r * table->cf] = opt;
-   memcpy (contents, cp, w);
  }
  
  
  static void
  do_tab_text (struct tab_table *table, int c, int r, unsigned opt, char *text)
  {
 -  assert (c >= 0);
 -  assert (r >= 0);
 -  assert (c < table->nc);
 -  assert (r < table->nr);
 +  assert (c >= 0 );
 +  assert (r >= 0 );
 +  assert (c < tab_nc (table));
 +  assert (r < tab_nr (table));
  
  #if DEBUGGING
    if (c + table->col_ofs < 0 || r + table->row_ofs < 0
 -      || c + table->col_ofs >= table->nc
 -      || r + table->row_ofs >= table->nr)
 +      || c + table->col_ofs >= tab_nc (table)
 +      || r + table->row_ofs >= tab_nr (table))
      {
        printf ("tab_text(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
              "(%d,%d)\n",
              c, table->col_ofs, c + table->col_ofs,
              r, table->row_ofs, r + table->row_ofs,
 -            table->nc, table->nr);
 +            tab_nc (table), tab_nr (table));
        return;
      }
  #endif
@@@ -704,14 -685,14 +694,14 @@@ do_tab_joint_text (struct tab_table *ta
    assert (y1 + table->row_ofs >= 0);
    assert (y2 >= y1);
    assert (x2 >= x1);
 -  assert (y2 + table->row_ofs < table->nr);
 -  assert (x2 + table->col_ofs < table->nc);
 +  assert (y2 + table->row_ofs < tab_nr (table));
 +  assert (x2 + table->col_ofs < tab_nc (table));
  
  #if DEBUGGING
 -  if (x1 + table->col_ofs < 0 || x1 + table->col_ofs >= table->nc
 -      || y1 + table->row_ofs < 0 || y1 + table->row_ofs >= table->nr
 -      || x2 < x1 || x2 + table->col_ofs >= table->nc
 -      || y2 < y2 || y2 + table->row_ofs >= table->nr)
 +  if (x1 + table->col_ofs < 0 || x1 + table->col_ofs >= tab_nc (table)
 +      || y1 + table->row_ofs < 0 || y1 + table->row_ofs >= tab_nr (table)
 +      || x2 < x1 || x2 + table->col_ofs >= tab_nc (table)
 +      || y2 < y2 || y2 + table->row_ofs >= tab_nr (table))
      {
        printf ("tab_joint_text(): bad cell "
              "(%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) in table size (%d,%d)\n",
              y1, table->row_ofs, y1 + table->row_ofs,
              x2, table->col_ofs, x2 + table->col_ofs,
              y2, table->row_ofs, y2 + table->row_ofs,
 -            table->nc, table->nr);
 +            tab_nc (table), tab_nr (table));
        return;
      }
  #endif
    tab_box (table, -1, -1, TAL_0, TAL_0, x1, y1, x2, y2);
  
    j = pool_alloc (table->container, sizeof *j);
 -  j->hit = 0;
    j->x1 = x1 + table->col_ofs;
    j->y1 = y1 + table->row_ofs;
    j->x2 = ++x2 + table->col_ofs;
@@@ -782,25 -764,50 +772,25 @@@ tab_joint_text_format (struct tab_tabl
                       pool_vasprintf (table->container, format, args));
    va_end (args);
  }
 -
 -/* Sets cell (C,R) in TABLE, with options OPT, to contents STRING. */
 -void
 -tab_raw (struct tab_table *table, int c, int r, unsigned opt,
 -       struct substring *string)
 -{
 -  assert (table != NULL && string != NULL);
 -
 -#if DEBUGGING
 -  if (c + table->col_ofs < 0 || r + table->row_ofs < 0
 -      || c + table->col_ofs >= table->nc
 -      || r + table->row_ofs >= table->nr)
 -    {
 -      printf ("tab_raw(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
 -            "(%d,%d)\n",
 -            c, table->col_ofs, c + table->col_ofs,
 -            r, table->row_ofs, r + table->row_ofs,
 -            table->nc, table->nr);
 -      return;
 -    }
 -#endif
 -
 -  table->cc[c + r * table->cf] = *string;
 -  table->ct[c + r * table->cf] = opt;
 -}
  \f
  /* Miscellaneous. */
  
  /* Sets the widths of all the columns and heights of all the rows in
     table T for driver D. */
  static void
 -nowrap_dim (struct tab_table *t, struct outp_driver *d, void *aux UNUSED)
 +nowrap_dim (struct tab_rendering *r, void *aux UNUSED)
  {
 -  t->w[0] = tab_natural_width (t, d, 0);
 -  t->h[0] = d->font_height;
 +  r->w[0] = tab_natural_width (r, 0);
 +  r->h[0] = r->driver->font_height;
  }
  
  /* Sets the widths of all the columns and heights of all the rows in
     table T for driver D. */
  static void
 -wrap_dim (struct tab_table *t, struct outp_driver *d, void *aux UNUSED)
 +wrap_dim (struct tab_rendering *r, void *aux UNUSED)
  {
 -  t->w[0] = tab_natural_width (t, d, 0);
 -  t->h[0] = tab_natural_height (t, d, 0);
 +  r->w[0] = tab_natural_width (r, 0);
 +  r->h[0] = tab_natural_height (r, 0);
  }
  
  static void
@@@ -808,7 -815,7 +798,7 @@@ do_tab_output_text (struct tab_table *t
  {
    do_tab_text (t, 0, 0, options, text);
    tab_flags (t, SOMF_NO_TITLE | SOMF_NO_SPACING);
 -  tab_dim (t, options & TAT_NOWRAP ? nowrap_dim : wrap_dim, NULL);
 +  tab_dim (t, options & TAT_NOWRAP ? nowrap_dim : wrap_dim, NULL, NULL);
    tab_submit (t);
  }
  
  void
  tab_output_text (int options, const char *text)
  {
 -  struct tab_table *table = tab_create (1, 1, 0);
 +  struct tab_table *table = tab_create (1, 1);
    do_tab_output_text (table, options, pool_strdup (table->container, text));
  }
  
@@@ -832,7 -839,7 +822,7 @@@ tab_output_text_format (int options, co
    struct tab_table *table;
    va_list args;
  
 -  table = tab_create (1, 1, 0);
 +  table = tab_create (1, 1);
  
    va_start (args, format);
    do_tab_output_text (table, options,
@@@ -873,14 -880,14 +863,14 @@@ tab_offset (struct tab_table *t, int co
  
    assert (t != NULL);
  #if DEBUGGING
 -  if (row < -1 || row > t->nr)
 +  if (row < -1 || row > tab_nr (t))
      {
 -      printf ("tab_offset(): row=%d in %d-row table\n", row, t->nr);
 +      printf ("tab_offset(): row=%d in %d-row table\n", row, tab_nr (t));
        NOT_REACHED ();
      }
 -  if (col < -1 || col > t->nc)
 +  if (col < -1 || col > tab_nc (t))
      {
 -      printf ("tab_offset(): col=%d in %d-column table\n", col, t->nc);
 +      printf ("tab_offset(): col=%d in %d-column table\n", col, tab_nc (t));
        NOT_REACHED ();
      }
  #endif
@@@ -902,46 -909,29 +892,46 @@@ tab_next_row (struct tab_table *t
    assert (t != NULL);
    t->cc += t->cf;
    t->ct += t->cf;
 -  if (++t->row_ofs >= t->nr)
 -    tab_realloc (t, -1, t->nr * 4 / 3);
 +  if (++t->row_ofs >= tab_nr (t))
 +    tab_realloc (t, -1, tab_nr (t) * 4 / 3);
  }
  \f
 -static struct tab_table *t;
 -static struct outp_driver *d;
 -static int tab_hit;
 +/* Return the number of columns and rows in the table into N_COLUMNS
 +   and N_ROWS, respectively. */
 +static void
 +tabi_count (struct som_entity *t_, int *n_columns, int *n_rows)
 +{
 +  struct tab_table *t = t_->ext;
 +  *n_columns = t->nc;
 +  *n_rows = t->nr;
 +}
  
 -/* Set the current table to TABLE. */
 +/* Return the column style for this table into STYLE. */
  static void
 -tabi_table (struct som_entity *table)
 +tabi_columns (struct som_entity *t_, int *style)
  {
 -  assert (table != NULL);
 -  assert (table->type == SOM_TABLE);
 +  struct tab_table *t = t_->ext;
 +  *style = t->col_style;
 +}
  
 -  t = table->ext;
 -  tab_offset (t, 0, 0);
 +/* Return the number of header rows/columns on the left, right, top,
 +   and bottom sides into HL, HR, HT, and HB, respectively. */
 +static void
 +tabi_headers (struct som_entity *t_, int *hl, int *hr, int *ht, int *hb)
 +{
 +  struct tab_table *t = t_->ext;
 +  *hl = t->l;
 +  *hr = t->r;
 +  *ht = t->t;
 +  *hb = t->b;
 +}
  
 -  assert (t->w == NULL && t->h == NULL);
 -  t->w = pool_nalloc (t->container, t->nc, sizeof *t->w);
 -  t->h = pool_nalloc (t->container, t->nr, sizeof *t->h);
 -  t->hrh = pool_nmalloc (t->container, t->nr + 1, sizeof *t->hrh);
 -  t->wrv = pool_nmalloc (t->container, t->nc + 1, sizeof *t->wrv);
 +/* Return flags set for the current table into FLAGS. */
 +static void
 +tabi_flags (struct som_entity *t_, unsigned *flags)
 +{
 +  struct tab_table *t = t_->ext;
 +  *flags = t->flags;
  }
  
  /* Returns the line style to use for spacing purposes for a rule
@@@ -963,127 -953,153 +953,127 @@@ rule_to_spacing_type (unsigned char typ
      }
  }
  
 -/* Set the current output device to DRIVER. */
 -static void
 -tabi_driver (struct outp_driver *driver)
 +static void *
 +tabi_render_init (struct som_entity *t_, struct outp_driver *driver,
 +                  int hl, int hr, int ht, int hb)
  {
 -  int c, r;
 +  const struct tab_table *t = t_->ext;
 +  struct tab_rendering *r;
 +  int col, row;
    int i;
  
 -  assert (driver != NULL);
 -  d = driver;
 +  tab_offset (t_->ext, 0, 0);
 +
 +  r = xmalloc (sizeof *r);
 +  r->table = t;
 +  r->driver = driver;
 +  r->w = xnmalloc (tab_nc (t), sizeof *r->w);
 +  r->h = xnmalloc (tab_nr (t), sizeof *r->h);
 +  r->hrh = xnmalloc (tab_nr (t) + 1, sizeof *r->hrh);
 +  r->wrv = xnmalloc (tab_nc (t) + 1, sizeof *r->wrv);
 +  r->l = hl;
 +  r->r = hr;
 +  r->t = ht;
 +  r->b = hb;
  
    /* Figure out sizes of rules. */
 -  for (r = 0; r <= t->nr; r++)
 +  for (row = 0; row <= tab_nr (t); row++)
      {
        int width = 0;
 -      for (c = 0; c < t->nc; c++)
 +      for (col = 0; col < tab_nc (t); col++)
          {
 -          unsigned char rh = t->rh[c + r * t->cf];
 +          unsigned char rh = t->rh[col + row * t->cf];
            int w = driver->horiz_line_width[rule_to_spacing_type (rh)];
            if (w > width)
              width = w;
          }
 -      t->hrh[r] = width;
 +      r->hrh[row] = width;
      }
  
 -  for (c = 0; c <= t->nc; c++)
 +  for (col = 0; col <= tab_nc (t); col++)
      {
        int width = 0;
 -      for (r = 0; r < t->nr; r++)
 +      for (row = 0; row < tab_nr (t); row++)
          {
 -          unsigned char *rv = &t->rv[c + r * (t->cf + 1)];
 +          unsigned char *rv = &t->rv[col + row * (t->cf + 1)];
            int w;
            if (*rv == UCHAR_MAX)
 -            *rv = c != 0 && c != t->nc ? TAL_GAP : TAL_0;
 +            *rv = col != 0 && col != tab_nc (t) ? TAL_GAP : TAL_0;
            w = driver->vert_line_width[rule_to_spacing_type (*rv)];
            if (w > width)
              width = w;
          }
 -      t->wrv[c] = width;
 +      r->wrv[col] = width;
      }
  
 -#if DEBUGGING
 -  for (i = 0; i < t->nr; i++)
 -    t->h[i] = -1;
 -  for (i = 0; i < t->nc; i++)
 -    t->w[i] = -1;
 -#endif
 -
 -  assert (t->dim != NULL);
 -  t->dim (t, d, t->dim_aux);
 +  /* Determine row heights and columns widths. */
 +  for (i = 0; i < tab_nr (t); i++)
 +    r->h[i] = -1;
 +  for (i = 0; i < tab_nc (t); i++)
 +    r->w[i] = -1;
  
 -#if DEBUGGING
 -  {
 -    int error = 0;
 -
 -    for (i = 0; i < t->nr; i++)
 -      {
 -      if (t->h[i] == -1)
 -        {
 -          printf ("Table row %d height not initialized.\n", i);
 -          error = 1;
 -        }
 -      assert (t->h[i] > 0);
 -      }
 +  t->dim (r, t->dim_aux);
  
 -    for (i = 0; i < t->nc; i++)
 -      {
 -      if (t->w[i] == -1)
 -        {
 -          printf ("Table column %d width not initialized.\n", i);
 -          error = 1;
 -        }
 -      assert (t->w[i] > 0);
 -      }
 -  }
 -#endif
 +  for (i = 0; i < tab_nr (t); i++)
 +    if (r->h[i] < 0)
 +      error (0, 0, "height of table row %d is %d (not initialized?)",
 +             i, r->h[i]);
 +  for (i = 0; i < tab_nc (t); i++)
 +    if (r->w[i] < 0)
 +      error (0, 0, "width of table column %d is %d (not initialized?)",
 +             i, r->w[i]);
  
    /* Add up header sizes. */
 -  for (i = 0, t->wl = t->wrv[0]; i < t->l; i++)
 -    t->wl += t->w[i] + t->wrv[i + 1];
 -  for (i = 0, t->ht = t->hrh[0]; i < t->t; i++)
 -    t->ht += t->h[i] + t->hrh[i + 1];
 -  for (i = t->nc - t->r, t->wr = t->wrv[i]; i < t->nc; i++)
 -    t->wr += t->w[i] + t->wrv[i + 1];
 -  for (i = t->nr - t->b, t->hb = t->hrh[i]; i < t->nr; i++)
 -    t->hb += t->h[i] + t->hrh[i + 1];
 +  for (i = 0, r->wl = r->wrv[0]; i < r->l; i++)
 +    r->wl += r->w[i] + r->wrv[i + 1];
 +  for (i = 0, r->ht = r->hrh[0]; i < r->t; i++)
 +    r->ht += r->h[i] + r->hrh[i + 1];
 +  for (i = tab_nc (t) - r->r, r->wr = r->wrv[i]; i < tab_nc (t); i++)
 +    r->wr += r->w[i] + r->wrv[i + 1];
 +  for (i = tab_nr (t) - r->b, r->hb = r->hrh[i]; i < tab_nr (t); i++)
 +    r->hb += r->h[i] + r->hrh[i + 1];
  
    /* Title. */
    if (!(t->flags & SOMF_NO_TITLE))
 -    t->ht += d->font_height;
 +    r->ht += driver->font_height;
 +
 +  return r;
  }
  
 -/* Return the number of columns and rows in the table into N_COLUMNS
 -   and N_ROWS, respectively. */
  static void
 -tabi_count (int *n_columns, int *n_rows)
 +tabi_render_free (void *r_)
  {
 -  assert (n_columns != NULL && n_rows != NULL);
 -  *n_columns = t->nc;
 -  *n_rows = t->nr;
 -}
 +  struct tab_rendering *r = r_;
  
 -static void tabi_cumulate (int cumtype, int start, int *end, int max, int *actual);
 +  free (r->w);
 +  free (r->h);
 +  free (r->hrh);
 +  free (r->wrv);
 +  free (r);
 +}
  
  /* Return the horizontal and vertical size of the entire table,
     including headers, for the current output device, into HORIZ and
     VERT. */
  static void
 -tabi_area (int *horiz, int *vert)
 +tabi_area (void *r_, int *horiz, int *vert)
  {
 -  assert (horiz != NULL && vert != NULL);
 -
 -  {
 -    int w, c;
 -
 -    for (c = t->l + 1, w = t->wl + t->wr + t->w[t->l];
 -       c < t->nc - t->r; c++)
 -      w += t->w[c] + t->wrv[c];
 -    *horiz = w;
 -  }
 -
 -  {
 -    int h, r;
 -    for (r = t->t + 1, h = t->ht + t->hb + t->h[t->t];
 -       r < t->nr - t->b; r++)
 -      h += t->h[r] + t->hrh[r];
 -    *vert = h;
 -  }
 -}
 -
 -/* Return the column style for this table into STYLE. */
 -static void
 -tabi_columns (int *style)
 -{
 -  assert (style != NULL);
 -  *style = t->col_style;
 -}
 -
 -/* Return the number of header rows/columns on the left, right, top,
 -   and bottom sides into HL, HR, HT, and HB, respectively. */
 -static void
 -tabi_headers (int *hl, int *hr, int *ht, int *hb)
 -{
 -  assert (hl != NULL && hr != NULL && ht != NULL && hb != NULL);
 -  *hl = t->l;
 -  *hr = t->r;
 -  *ht = t->t;
 -  *hb = t->b;
 +  struct tab_rendering *r = r_;
 +  const struct tab_table *t = r->table;
 +  int width, col;
 +  int height, row;
 +
 +  width = 0;
 +  for (col = r->l + 1, width = r->wl + r->wr + r->w[tab_l (t)];
 +       col < tab_nc (t) - r->r; col++)
 +    width += r->w[col] + r->wrv[col];
 +  *horiz = width;
 +
 +  height = 0;
 +  for (row = r->t + 1, height = r->ht + r->hb + r->h[tab_t (t)];
 +       row < tab_nr (t) - tab_b (t); row++)
 +    height += r->h[row] + r->hrh[row];
 +  *vert = height;
  }
  
  /* Determines the number of rows or columns (including appropriate
     space the selected rows/columns (including appropriate headers)
     filled. */
  static void
 -tabi_cumulate (int cumtype, int start, int *end, int max, int *actual)
 +tabi_cumulate (void *r_, int cumtype, int start, int *end,
 +               int max, int *actual)
  {
 -  int n;
 -  int *d;
 -  int *r;
 +  const struct tab_rendering *r = r_;
 +  const struct tab_table *t = r->table;
 +  int limit;
 +  int *cells, *rules;
    int total;
 +  int idx;
  
    assert (end != NULL && (cumtype == SOM_ROWS || cumtype == SOM_COLUMNS));
    if (cumtype == SOM_ROWS)
      {
 -      assert (start >= 0 && start < t->nr);
 -      n = t->nr - t->b;
 -      d = &t->h[start];
 -      r = &t->hrh[start + 1];
 -      total = t->ht + t->hb;
 +      assert (start >= 0 && start < tab_nr (t));
 +      limit = tab_nr (t) - r->b;
 +      cells = &r->h[start];
 +      rules = &r->hrh[start + 1];
 +      total = r->ht + r->hb;
      }
    else
      {
 -      assert (start >= 0 && start < t->nc);
 -      n = t->nc - t->r;
 -      d = &t->w[start];
 -      r = &t->wrv[start + 1];
 -      total = t->wl + t->wr;
 +      assert (start >= 0 && start < tab_nc (t));
 +      limit = tab_nc (t) - tab_r (t);
 +      cells = &r->w[start];
 +      rules = &r->wrv[start + 1];
 +      total = r->wl + r->wr;
      }
  
 -  total += *d++;
 +  total += *cells++;
    if (total > max)
      {
        if (end)
        return;
      }
  
 -  {
 -    int x;
 -
 -    for (x = start + 1; x < n; x++)
 -      {
 -      int amt = *d++ + *r++;
 -
 -      total += amt;
 -      if (total > max)
 -        {
 -          total -= amt;
 -          break;
 -        }
 -      }
 -
 -    if (end)
 -      *end = x;
 -
 -    if (actual)
 -      *actual = total;
 -  }
 -}
 -
 -/* Return flags set for the current table into FLAGS. */
 -static void
 -tabi_flags (unsigned *flags)
 -{
 -  assert (flags != NULL);
 -  *flags = t->flags;
 -}
 -
 -/* Returns true if the table will fit in the given page WIDTH,
 -   false otherwise. */
 -static bool
 -tabi_fits_width (int width)
 -{
 -  int i;
 -
 -  for (i = t->l; i < t->nc - t->r; i++)
 -    if (t->wl + t->wr + t->w[i] > width)
 -      return false;
 -
 -  return true;
 -}
 +  for (idx = start + 1; idx < limit; idx++)
 +    {
 +      int amt = *cells++ + *rules++;
  
 -/* Returns true if the table will fit in the given page LENGTH,
 -   false otherwise. */
 -static bool
 -tabi_fits_length (int length)
 -{
 -  int i;
 +      total += amt;
 +      if (total > max)
 +        {
 +          total -= amt;
 +          break;
 +        }
 +    }
  
 -  for (i = t->t; i < t->nr - t->b; i++)
 -    if (t->ht + t->hb + t->h[i] > length)
 -      return false;
 +  if (end)
 +    *end = idx;
  
 -  return true;
 -}
 -
 -/* Sets the number of header rows/columns on the left, right, top,
 -   and bottom sides to HL, HR, HT, and HB, respectively. */
 -static void
 -tabi_set_headers (int hl, int hr, int ht, int hb)
 -{
 -  t->l = hl;
 -  t->r = hr;
 -  t->t = ht;
 -  t->b = hb;
 +  if (actual)
 +    *actual = total;
  }
  
  /* Render title for current table, with major index X and minor index
     Y.  Y may be zero, or X and Y may be zero, but X should be nonzero
     if Y is nonzero. */
  static void
 -tabi_title (int x, int y)
 +tabi_title (void *r_, int x, int y, int table_num, int subtable_num,
 +            const char *command_name)
  {
 -  char buf[1024];
 -  char *cp;
 +  const struct tab_rendering *r = r_;
 +  const struct tab_table *t = r->table;
 +  struct outp_text text;
 +  struct string title;
  
    if (t->flags & SOMF_NO_TITLE)
      return;
  
 -  cp = spprintf (buf, "%d.%d", table_num, subtable_num);
 +  ds_init_empty (&title);
 +  ds_put_format (&title,"%d.%d", table_num, subtable_num);
    if (x && y)
 -    cp = spprintf (cp, "(%d:%d)", x, y);
 +    ds_put_format (&title, "(%d:%d)", x, y);
    else if (x)
 -    cp = spprintf (cp, "(%d)", x);
 +    ds_put_format (&title, "(%d)", x);
    if (command_name != NULL)
 -    cp = spprintf (cp, " %s", command_name);
 -  cp = stpcpy (cp, ".  ");
 +    ds_put_format (&title, " %s", command_name);
 +  ds_put_cstr (&title, ".  ");
    if (t->title != NULL)
 -    {
 -      size_t length = strlen (t->title);
 -      memcpy (cp, t->title, length);
 -      cp += length;
 -    }
 -  *cp = 0;
 -
 -  {
 -    struct outp_text text;
 -
 -    text.font = OUTP_PROPORTIONAL;
 -    text.justification = OUTP_LEFT;
 -    text.string = ss_buffer (buf, cp - buf);
 -    text.h = d->width;
 -    text.v = d->font_height;
 -    text.x = 0;
 -    text.y = d->cp_y;
 -    d->class->text_draw (d, &text);
 -  }
 +    ds_put_cstr (&title, t->title);
 +
 +  text.font = OUTP_PROPORTIONAL;
 +  text.justification = OUTP_LEFT;
 +  text.string = ds_ss (&title);
 +  text.h = r->driver->width;
 +  text.v = r->driver->font_height;
 +  text.x = 0;
 +  text.y = r->driver->cp_y;
 +  r->driver->class->text_draw (r->driver, &text);
 +
 +  ds_destroy (&title);
  }
  
 -static int render_strip (int x, int y, int r, int c1, int c2, int r1, int r2);
 +static int render_strip (const struct tab_rendering *,
 +                         int x, int y, int r, int c1, int c2, int r1, int r2);
  
 -/* Renders columns C0...C1, plus headers, of rows R0...R1,
 -   at the given vertical position Y.
 -   C0 and C1 count vertical rules as columns,
 -   but R0 and R1 do not count horizontal rules as rows.
 -   Returns the vertical position after rendering. */
 -static int
 -render_rows (int y, int c0, int c1, int r0, int r1)
 +static void
 +add_range (int ranges[][2], int *np, int start, int end)
  {
 -  int r;
 -  for (r = r0; r < r1; r++)
 +  int n = *np;
 +  if (n == 0 || start > ranges[n - 1][1])
      {
 -      int x = d->cp_x;
 -      x = render_strip (x, y, r, 0, t->l * 2 + 1, r0, r1);
 -      x = render_strip (x, y, r, c0 * 2 + 1, c1 * 2, r0, r1);
 -      x = render_strip (x, y, r, (t->nc - t->r) * 2, t->nc * 2 + 1, r0, r1);
 -      y += (r & 1) ? t->h[r / 2] : t->hrh[r / 2];
 +      ranges[n][0] = start;
 +      ranges[n][1] = end;
 +      ++*np;
      }
 -  return y;
 +  else
 +    ranges[n - 1][1] = end;
  }
  
  /* Draws table region (C0,R0)-(C1,R1), plus headers, at the
     current position on the current output device.  */
  static void
 -tabi_render (int c0, int r0, int c1, int r1)
 +tabi_render (void *r_, int c0, int r0, int c1, int r1)
  {
 -  int y;
 +  const struct tab_rendering *r = r_;
 +  const struct tab_table *t = r->table;
 +  int rows[3][2], cols[3][2];
 +  int n_row_ranges, n_col_ranges;
 +  int y, i;
 +
 +  /* Rows to render, counting horizontal rules as rows.  */
 +  n_row_ranges = 0;
 +  add_range (rows, &n_row_ranges, 0, tab_t (t) * 2 + 1);
 +  add_range (rows, &n_row_ranges, r0 * 2 + 1, r1 * 2);
 +  add_range (rows, &n_row_ranges, (tab_nr (t) - tab_b (t)) * 2,
 +             tab_nr (t) * 2 + 1);
 +
 +  /* Columns to render, counting vertical rules as columns. */
 +  n_col_ranges = 0;
 +  add_range (cols, &n_col_ranges, 0, r->l * 2 + 1);
 +  add_range (cols, &n_col_ranges, c0 * 2 + 1, c1 * 2);
 +  add_range (cols, &n_col_ranges, (tab_nc (t) - r->r) * 2, tab_nc (t) * 2 + 1);
 +
 +  y = r->driver->cp_y;
 +  if (!(t->flags & SOMF_NO_TITLE))
 +    y += r->driver->font_height;
 +  for (i = 0; i < n_row_ranges; i++)
 +    {
 +      int row;
  
 -  tab_hit++;
 +      for (row = rows[i][0]; row < rows[i][1]; row++)
 +        {
 +          int x, j;
  
 -  y = d->cp_y;
 -  if (!(t->flags & SOMF_NO_TITLE))
 -    y += d->font_height;
 +          x = r->driver->cp_x;
 +          for (j = 0; j < n_col_ranges; j++)
 +            x = render_strip (r, x, y, row,
 +                              cols[j][0], cols[j][1],
 +                              rows[i][0], rows[i][1]);
  
 -  y = render_rows (y, c0, c1, 0, t->t * 2 + 1);
 -  y = render_rows (y, c0, c1, r0 * 2 + 1, r1 * 2);
 -  y = render_rows (y, c0, c1, (t->nr - t->b) * 2, t->nr * 2 + 1);
 +          y += (row & 1) ? r->h[row / 2] : r->hrh[row / 2];
 +        }
 +    }
  }
  
  const struct som_table_class tab_table_class =
    {
 -    tabi_table,
 -    tabi_driver,
 -
      tabi_count,
 -    tabi_area,
 -    NULL,
 -    NULL,
      tabi_columns,
 -    NULL,
      tabi_headers,
 -    NULL,
 -    tabi_cumulate,
      tabi_flags,
 -    tabi_fits_width,
 -    tabi_fits_length,
  
 -    NULL,
 -    NULL,
 -    tabi_set_headers,
 +    tabi_render_init,
 +    tabi_render_free,
  
 +    tabi_area,
 +    tabi_cumulate,
      tabi_title,
      tabi_render,
    };
@@@ -1306,63 -1360,56 +1296,63 @@@ rule_to_draw_type (unsigned char type
  
  /* Returns the horizontal rule at the given column and row. */
  static int
 -get_hrule (int c, int r)
 +get_hrule (const struct tab_table *t, int col, int row)
  {
 -  return t->rh[c + r * t->cf];
 +  return t->rh[col + row * t->cf];
  }
  
  /* Returns the vertical rule at the given column and row. */
  static int
 -get_vrule (int c, int r)
 +get_vrule (const struct tab_table *t, int col, int row)
  {
 -  return t->rv[c + r * (t->cf + 1)];
 +  return t->rv[col + row * (t->cf + 1)];
  }
  
  /* Renders the horizontal rule at the given column and row
     at (X,Y) on the page. */
  static void
 -render_horz_rule (int x, int y, int c, int r)
 +render_horz_rule (const struct tab_rendering *r,
 +                  int x, int y, int col, int row)
  {
 -  enum outp_line_style style = rule_to_draw_type (get_hrule (c, r));
 +  enum outp_line_style style;
 +  style = rule_to_draw_type (get_hrule (r->table, col, row));
    if (style != OUTP_L_NONE)
 -    d->class->line (d, x, y, x + t->w[c], y + t->hrh[r],
 -                    OUTP_L_NONE, style, OUTP_L_NONE, style);
 +    r->driver->class->line (r->driver, x, y, x + r->w[col], y + r->hrh[row],
 +                            OUTP_L_NONE, style, OUTP_L_NONE, style);
  }
  
  /* Renders the vertical rule at the given column and row
     at (X,Y) on the page. */
  static void
 -render_vert_rule (int x, int y, int c, int r)
 +render_vert_rule (const struct tab_rendering *r,
 +                  int x, int y, int col, int row)
  {
 -  enum outp_line_style style = rule_to_draw_type (get_vrule (c, r));
 +  enum outp_line_style style;
 +  style = rule_to_draw_type (get_vrule (r->table, col, row));
    if (style != OUTP_L_NONE)
 -    d->class->line (d, x, y, x + t->wrv[c], y + t->h[r],
 -                    style, OUTP_L_NONE, style, OUTP_L_NONE);
 +    r->driver->class->line (r->driver, x, y, x + r->wrv[col], y + r->h[row],
 +                            style, OUTP_L_NONE, style, OUTP_L_NONE);
  }
  
  /* Renders the rule intersection at the given column and row
     at (X,Y) on the page. */
  static void
 -render_rule_intersection (int x, int y, int c, int r)
 +render_rule_intersection (const struct tab_rendering *r,
 +                          int x, int y, int col, int row)
  {
 +  const struct tab_table *t = r->table;
 +
    /* Bounds of intersection. */
    int x0 = x;
    int y0 = y;
 -  int x1 = x + t->wrv[c];
 -  int y1 = y + t->hrh[r];
 +  int x1 = x + r->wrv[col];
 +  int y1 = y + r->hrh[row];
  
    /* Lines on each side of intersection. */
 -  int top = r > 0 ? get_vrule (c, r - 1) : TAL_0;
 -  int left = c > 0 ? get_hrule (c - 1, r) : TAL_0;
 -  int bottom = r < t->nr ? get_vrule (c, r) : TAL_0;
 -  int right = c < t->nc ? get_hrule (c, r) : TAL_0;
 +  int top = row > 0 ? get_vrule (t, col, row - 1) : TAL_0;
 +  int left = col > 0 ? get_hrule (t, col - 1, row) : TAL_0;
 +  int bottom = row < tab_nr (t) ? get_vrule (t, col, row) : TAL_0;
 +  int right = col < tab_nc (t) ? get_hrule (t, col, row) : TAL_0;
  
    /* Output style for each line. */
    enum outp_line_style o_top = rule_to_draw_type (top);
  
    if (o_top != OUTP_L_NONE || o_left != OUTP_L_NONE
        || o_bottom != OUTP_L_NONE || o_right != OUTP_L_NONE)
 -    d->class->line (d, x0, y0, x1, y1, o_top, o_left, o_bottom, o_right);
 +    r->driver->class->line (r->driver, x0, y0, x1, y1,
 +                            o_top, o_left, o_bottom, o_right);
  }
  
  /* Returns the width of columns C1...C2 exclusive,
     including interior but not exterior rules. */
  static int
 -strip_width (int c1, int c2)
 +strip_width (const struct tab_rendering *r, int c1, int c2)
  {
    int width = 0;
    int c;
  
    for (c = c1; c < c2; c++)
 -    width += t->w[c] + t->wrv[c + 1];
 +    width += r->w[c] + r->wrv[c + 1];
    if (c1 < c2)
 -    width -= t->wrv[c2];
 +    width -= r->wrv[c2];
    return width;
  }
  
  /* Returns the height of rows R1...R2 exclusive,
     including interior but not exterior rules. */
  static int
 -strip_height (int r1, int r2)
 +strip_height (const struct tab_rendering *r, int r1, int r2)
  {
    int height = 0;
 -  int r;
 +  int row;
  
 -  for (r = r1; r < r2; r++)
 -    height += t->h[r] + t->hrh[r + 1];
 +  for (row = r1; row < r2; row++)
 +    height += r->h[row] + r->hrh[row + 1];
    if (r1 < r2)
 -    height -= t->hrh[r2];
 +    height -= r->hrh[r2];
    return height;
  }
  
     page.  Also renders joined cells that extend as far to the
     right as C1 and as far down as R1. */
  static void
 -render_cell (int x, int y, int c, int r, int c1, int r1)
 +render_cell (const struct tab_rendering *r,
 +             int x, int y, int col, int row, int c1, int r1)
  {
 -  const int index = c + (r * t->cf);
 +  const struct tab_table *t = r->table;
 +  const int index = col + (row * t->cf);
    unsigned char type = t->ct[index];
    struct substring *content = &t->cc[index];
  
            text.font = options_to_font (type);
            text.justification = translate_justification (type);
            text.string = *content;
 -          text.h = t->w[c];
 -          text.v = t->h[r];
 +          text.h = r->w[col];
 +          text.v = r->h[row];
            text.x = x;
            text.y = y;
 -          d->class->text_draw (d, &text);
 +          r->driver->class->text_draw (r->driver, &text);
          }
      }
    else
        struct tab_joined_cell *j
          = (struct tab_joined_cell *) ss_data (*content);
  
 -      if (j->hit != tab_hit)
 +      if (j->x1 == col && j->y1 == row)
          {
 -          j->hit = tab_hit;
 -
 -          if (j->x1 == c && j->y1 == r)
 -            {
 -              struct outp_text text;
 -              text.font = options_to_font (type);
 -              text.justification = translate_justification (type);
 -              text.string = j->contents;
 -              text.x = x;
 -              text.y = y;
 -              text.h = strip_width (j->x1, MIN (j->x2, c1));
 -              text.v = strip_height (j->y1, MIN (j->y2, r1));
 -              d->class->text_draw (d, &text);
 -            }
 +          struct outp_text text;
 +          text.font = options_to_font (type);
 +          text.justification = translate_justification (type);
 +          text.string = j->contents;
 +          text.x = x;
 +          text.y = y;
 +          text.h = strip_width (r, j->x1, MIN (j->x2, c1));
 +          text.v = strip_height (r, j->y1, MIN (j->y2, r1));
 +          r->driver->class->text_draw (r->driver, &text);
          }
      }
  }
  
  /* Render contiguous strip consisting of columns C0...C1, exclusive,
 -   on row R, at (X,Y).  Returns X position after rendering.
 +   on row ROW, at (X,Y).  Returns X position after rendering.
     Also renders joined cells that extend beyond that strip,
     cropping them to lie within rendering region (C0,R0)-(C1,R1).
     C0 and C1 count vertical rules as columns.
 -   R counts horizontal rules as rows, but R0 and R1 do not. */
 +   ROW counts horizontal rules as rows, but R0 and R1 do not. */
  static int
 -render_strip (int x, int y, int r, int c0, int c1, int r0 UNUSED, int r1)
 +render_strip (const struct tab_rendering *r,
 +              int x, int y, int row, int c0, int c1, int r0 UNUSED, int r1)
  {
 -  int c;
 +  int col;
  
 -  for (c = c0; c < c1; c++)
 -    if (c & 1)
 +  for (col = c0; col < c1; col++)
 +    if (col & 1)
        {
 -        if (r & 1)
 -          render_cell (x, y, c / 2, r / 2, c1 / 2, r1);
 +        if (row & 1)
 +          render_cell (r, x, y, col / 2, row / 2, c1 / 2, r1);
          else
 -          render_horz_rule (x, y, c / 2, r / 2);
 -        x += t->w[c / 2];
 +          render_horz_rule (r, x, y, col / 2, row / 2);
 +        x += r->w[col / 2];
        }
      else
        {
 -        if (r & 1)
 -          render_vert_rule (x, y, c / 2, r / 2);
 +        if (row & 1)
 +          render_vert_rule (r, x, y, col / 2, row / 2);
          else
 -          render_rule_intersection (x, y, c / 2, r / 2);
 -        x += t->wrv[c / 2];
 +          render_rule_intersection (r, x, y, col / 2, row / 2);
 +        x += r->wrv[col / 2];
        }
  
    return x;
  }
 -
 -/* Sets COMMAND_NAME as the name of the current command,
 -   for embedding in output. */
 -void
 -tab_set_command_name (const char *command_name_)
 -{
 -  free (command_name);
 -  command_name = command_name_ ? xstrdup (command_name_) : NULL;
 -}
diff --combined src/output/table.h
index 84c709644a73195f8b9400c041fcf971176c008b,1748c24c855e71ec8c0212dac76d00662db76f67..f4fbc786f0440ea39da04ebfa0c54070ccec7cd1
@@@ -61,24 -61,23 +61,24 @@@ struct tab_joined_cel
    {
      int x1, y1;
      int x2, y2;
 -    int hit;
      struct substring contents;
    };
  
  struct outp_driver;
  struct tab_table;
 -typedef void tab_dim_func (struct tab_table *, struct outp_driver *,
 -                           void *aux);
 +struct tab_rendering;
 +
 +typedef void tab_dim_func (struct tab_rendering *, void *aux);
 +typedef void tab_dim_free_func (void *aux);
  
  /* A table. */
  struct tab_table
    {
      struct pool *container;
 +    int ref_cnt;                /* Reference count. */
  
      /* Contents. */
      int col_style;            /* Columns: One of TAB_COL_*. */
 -    int col_group;            /* Number of rows per column group. */
      char *title;                /* Table title. */
      unsigned flags;           /* SOMF_*. */
      int nc, nr;                       /* Number of columns, rows. */
      unsigned char *ct;                /* Cell types; unsigned char[nr][nc]. */
      unsigned char *rh;                /* Horiz rules; unsigned char[nr+1][nc]. */
      unsigned char *rv;                /* Vert rules; unsigned char[nr][nc+1]. */
 +
 +    /* Calculating row and column dimensions. */
      tab_dim_func *dim;                /* Calculates cell widths and heights. */
 +    tab_dim_free_func *dim_free; /* Frees space allocated for dim function. */
      void *dim_aux;              /* Auxiliary data for dim function. */
  
 -    /* Calculated during output. */
 -    int *w;                   /* Column widths; [nc]. */
 -    int *h;                   /* Row heights; [nr]. */
 -    int *hrh;                 /* Heights of horizontal rules; [nr+1]. */
 -    int *wrv;                 /* Widths of vertical rules; [nc+1]. */
 -    int wl, wr, ht, hb;               /* Width/height of header rows/columns. */
 -
      /* Editing info. */
      int col_ofs, row_ofs;     /* X and Y offsets. */
    };
  
 -/* Number of rows in TABLE. */
 -#define tab_nr(TABLE) ((TABLE)->nr)
 -
 -/* Number of columns in TABLE. */
 -#define tab_nc(TABLE) ((TABLE)->nc)
 +/* Number of rows or columns in TABLE. */
 +static inline int tab_nr (const struct tab_table *table) { return table->nr; }
 +static inline int tab_nc (const struct tab_table *table) { return table->nc; }
  
 -/* Number of left header columns in TABLE. */
 -#define tab_l(TABLE) ((TABLE)->l)
 +/* Number of left/right/top/bottom header columns/rows in TABLE. */
 +static inline int tab_l (const struct tab_table *table) { return table->l; }
 +static inline int tab_r (const struct tab_table *table) { return table->r; }
 +static inline int tab_t (const struct tab_table *table) { return table->t; }
 +static inline int tab_b (const struct tab_table *table) { return table->b; }
  
 -/* Number of right header columns in TABLE. */
 -#define tab_r(TABLE) ((TABLE)->r)
 +struct tab_rendering
 +  {
 +    const struct tab_table *table;
 +    struct outp_driver *driver;
  
 -/* Number of top header rows in TABLE. */
 -#define tab_t(TABLE) ((TABLE)->t)
 +    int *w;                   /* Column widths; [nc]. */
 +    int *h;                   /* Row heights; [nr]. */
 +    int *hrh;                 /* Heights of horizontal rules; [nr+1]. */
 +    int *wrv;                 /* Widths of vertical rules; [nc+1]. */
  
 -/* Number of bottom header rows in TABLE. */
 -#define tab_b(TABLE) ((TABLE)->b)
 +    /* These fields would be redundant with those in struct tab_table, except
 +       that a table will be rendered with fewer header rows or columns than
 +       requested when we are pressed for space. */
 +    int l, r, t, b;           /* Number of header rows/columns. */
 +    int wl, wr, ht, hb;               /* Width/height of header rows/columns. */
 +  };
  
  /* Tables. */
 -struct tab_table *tab_create (int nc, int nr, int reallocable);
 +struct tab_table *tab_create (int nc, int nr);
  void tab_destroy (struct tab_table *);
 +void tab_ref (struct tab_table *);
  void tab_resize (struct tab_table *, int nc, int nr);
  void tab_realloc (struct tab_table *, int nc, int nr);
  void tab_headers (struct tab_table *, int l, int r, int t, int b);
 -void tab_columns (struct tab_table *, int style, int group);
 +void tab_columns (struct tab_table *, int style);
  void tab_title (struct tab_table *, const char *, ...)
       PRINTF_FORMAT (2, 3);
  void tab_flags (struct tab_table *, unsigned);
@@@ -140,10 -133,9 +140,10 @@@ void tab_submit (struct tab_table *)
  
  /* Dimensioning. */
  tab_dim_func tab_natural_dimensions;
 -int tab_natural_width (struct tab_table *t, struct outp_driver *d, int c);
 -int tab_natural_height (struct tab_table *t, struct outp_driver *d, int r);
 -void tab_dim (struct tab_table *, tab_dim_func *, void *aux);
 +int tab_natural_width (const struct tab_rendering *, int c);
 +int tab_natural_height (const struct tab_rendering *, int r);
 +void tab_dim (struct tab_table *,
 +              tab_dim_func *, tab_dim_free_func *, void *aux);
  
  /* Rules. */
  void tab_hline (struct tab_table *, int style, int x1, int x2, int y);
@@@ -161,9 -153,11 +161,11 @@@ enu
  
  /* Cells. */
  struct fmt_spec;
+ struct dictionary;
  union value;
  void tab_value (struct tab_table *, int c, int r, unsigned char opt,
-               const union value *, const struct fmt_spec *);
+               const union value *, const struct dictionary *dict,
+               const struct fmt_spec *);
  
  void tab_fixed (struct tab_table *, int c, int r, unsigned char opt,
                double v, int w, int d);
@@@ -182,6 -176,11 +184,6 @@@ void tab_joint_text_format (struct tab_
                              unsigned opt, const char *, ...)
       PRINTF_FORMAT (7, 8);
  
 -/* Cell low-level access. */
 -#define tab_alloc(TABLE, AMT) pool_alloc ((TABLE)->container, (AMT))
 -void tab_raw (struct tab_table *, int c, int r, unsigned opt,
 -            struct substring *);
 -
  /* Editing. */
  void tab_offset (struct tab_table *, int col, int row);
  void tab_next_row (struct tab_table *);
@@@ -195,5 -194,8 +197,5 @@@ void tab_output_text (int options, cons
  void tab_output_text_format (int options, const char *, ...)
       PRINTF_FORMAT (2, 3);
  
 -/* Embedding the command name in the output. */
 -void tab_set_command_name (const char *);
 -
  #endif /* tab_h */
  
diff --combined src/ui/gui/find-dialog.c
index 3d3aed8813c9628b672bd6d853d9385af7caec41,11ec0603d4dfd98ee65954b1fed34af49feb4bc2..0c16bfa8140c0a78f4036c2c2ef2e3eeadb5023a
@@@ -34,7 -34,6 +34,7 @@@ which match particular strings *
  #include <ctype.h>
  #include <sys/types.h>
  #include <regex.h>
 +#include <libpspp/cast.h>
  #include <libpspp/message.h>
  
  #include <gtk/gtk.h>
@@@ -220,7 -219,8 +220,8 @@@ find_dialog (GObject *o, gpointer data
                "data-store", &ds,
                NULL);
  
-   fd.dict = vs->dict;
+   g_object_get (vs, "dictionary", &fd.dict, NULL);
    fd.data = ds->datasheet;
  
    fd.variable_entry        = get_widget_assert (fd.xml, "find-variable-entry");
@@@ -428,6 -428,7 +429,7 @@@ struct comparato
  {
    const struct variable *var;
    enum string_cmp_flags flags;
+   const PsppireDict *dict;
  
    bool (*compare) (const struct comparator *,
                   const union value *);
@@@ -493,20 -494,24 +495,24 @@@ static boo
  string_value_compare (const struct comparator *cmptr,
                      const union value *val)
  {
+   bool found;
+   char *text;
    const struct string_comparator *ssc =
      (const struct string_comparator *) cmptr;
  
    int width = var_get_width (cmptr->var);
-   const char *text = value_str (val, width);
+   g_return_val_if_fail (width > 0, false);
    assert ( ! (cmptr->flags & STR_CMP_LABELS));
  
-   g_return_val_if_fail (width > 0, false);
+   text = value_to_text (*val, cmptr->dict, *var_get_write_format (cmptr->var));
  
    if ( cmptr->flags & STR_CMP_SUBSTR)
-     return (NULL != g_strstr_len (text, width, ssc->pattern));
+     found =  (NULL != g_strstr_len (text, width, ssc->pattern));
    else
-     return (0 == strncmp (text, ssc->pattern, width));
+     found = (0 == strncmp (text, ssc->pattern, width));
+   free (text);
+   return found;
  }
  
  
@@@ -527,9 -532,9 +533,9 @@@ regexp_value_compare (const struct comp
  
    g_return_val_if_fail (width > 0, false);
  
+   text = value_to_text (*val, cmptr->dict, *var_get_write_format (cmptr->var));
    /* We must remove trailing whitespace, otherwise $ will not match where
       one would expect */
-   text = g_strndup (value_str (val, width), width);
    g_strchomp (text);
  
    retval = (0 == regexec (&rec->re, text, 0, 0, 0));
@@@ -565,8 -570,7 +571,8 @@@ regexp_label_compare (const struct comp
  static void
  regexp_destroy (struct comparator *cmptr)
  {
 -  struct regexp_comparator *rec = (struct regexp_comparator *) cmptr;
 +  struct regexp_comparator *rec
 +    = UP_CAST (cmptr, struct regexp_comparator, parent);
  
    regfree (&rec->re);
  }
  static void
  cmptr_value_destroy (struct comparator *cmptr)
  {
 -  struct value_comparator *vc = (struct value_comparator *) cmptr;
 +  struct value_comparator *vc
 +    = UP_CAST (cmptr, struct value_comparator, parent);
    value_destroy (&vc->pattern, var_get_width (cmptr->var));
  }
  
  
  static struct comparator *
- value_comparator_create (const struct variable *var, const char *target)
+ value_comparator_create (const struct variable *var, const PsppireDict *dict, const char *target)
  {
-   const struct fmt_spec *fmt;
-   int width ;
    struct value_comparator *vc = xzalloc (sizeof (*vc));
 -  struct comparator *cmptr = (struct comparator *) vc;
 +  struct comparator *cmptr = &vc->parent;
  
    cmptr->flags = 0;
    cmptr->var = var;
    cmptr->compare  = value_compare ;
    cmptr->destroy = cmptr_value_destroy;
+   cmptr->dict = dict;
  
-   width = var_get_width (var);
-   fmt = var_get_write_format (var);
-   value_init (&vc->pattern, width);
-   if ( ! data_in (ss_cstr (target),
-                   LEGACY_NATIVE,
-                 fmt->type,
-                 0, 0, 0,
-                 &vc->pattern, width) )
-     {
-       value_destroy (&vc->pattern, width);
-       free (vc);
-       return NULL;
-     }
+   text_to_value (target, dict, var, &vc->pattern);
  
    return cmptr;
  }
  
  static struct comparator *
- string_comparator_create (const struct variable *var, const char *target,
+ string_comparator_create (const struct variable *var, const PsppireDict *dict, 
+                         const char *target,
                          enum string_cmp_flags flags)
  {
    struct string_comparator *ssc = xzalloc (sizeof (*ssc));
 -  struct comparator *cmptr = (struct comparator *) ssc;
 +  struct comparator *cmptr = &ssc->parent;
  
    cmptr->flags = flags;
    cmptr->var = var;
+   cmptr->dict = dict;
  
    if ( flags & STR_CMP_LABELS)
      cmptr->compare = string_label_compare;
  
  
  static struct comparator *
- regexp_comparator_create (const struct variable *var, const char *target,
+ regexp_comparator_create (const struct variable *var, const PsppireDict *dict, const char *target,
                          enum string_cmp_flags flags)
  {
    int code;
    struct regexp_comparator *rec = xzalloc (sizeof (*rec));
 -  struct comparator *cmptr = (struct comparator *) rec;
 +  struct comparator *cmptr = &rec->parent;
  
    cmptr->flags = flags;
    cmptr->var = var;
+   cmptr->dict = dict;
    cmptr->compare  = (flags & STR_CMP_LABELS)
      ? regexp_label_compare : regexp_value_compare ;
  
@@@ -692,16 -683,16 +686,16 @@@ comparator_destroy (struct comparator *
  
  
  static struct comparator *
- comparator_factory (const struct variable *var, const char *str,
+ comparator_factory (const struct variable *var, const PsppireDict *dict, const char *str,
                    enum string_cmp_flags flags)
  {
    if ( flags & STR_CMP_REGEXP )
-     return regexp_comparator_create (var, str, flags);
+     return regexp_comparator_create (var, dict, str, flags);
  
    if ( flags & (STR_CMP_SUBSTR | STR_CMP_LABELS) )
-     return string_comparator_create (var, str, flags);
+     return string_comparator_create (var, dict, str, flags);
  
-   return value_comparator_create (var, str);
+   return value_comparator_create (var, dict, str);
  }
  
  
@@@ -747,7 -738,7 +741,7 @@@ find_value (const struct find_dialog *f
      casenumber i;
      const struct casenum_iterator *ip = get_iteration_params (fd);
      struct comparator *cmptr =
-       comparator_factory (var, target_string, flags);
+       comparator_factory (var, fd->dict, target_string, flags);
  
      value_init (&val, width);
      if ( ! cmptr)
index ed06da91a91af9fa2dc927d21c491f42431a65b6,d11eca618d84c9c18c86e5dc7bd184b6f9771a75..606a950475fc6ecc1d4833fb7eea46e9faf8ccdf
@@@ -1,5 -1,5 +1,5 @@@
  /* PSPPIRE - a graphical user interface for PSPP.
 -   Copyright (C) 2008  Free Software Foundation
 +   Copyright (C) 2008, 2009  Free Software Foundation
  
     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 <gtk/gtkaction.h>
  #include <gtk/gtktextbuffer.h>
  #include "psppire-window.h"
+ #include "psppire.h"
  #include <gtk/gtk.h>
  
  extern int viewer_length;
  extern int viewer_width ;
  
  
 -#define OUTPUT_FILE_NAME "psppire.txt"
 -
 -
 -
  G_BEGIN_DECLS
  
  #define PSPPIRE_OUTPUT_WINDOW_TYPE            (psppire_output_window_get_type ())
@@@ -51,13 -56,9 +52,13 @@@ struct _PsppireOutputWindo
    PsppireWindow parent;
  
    /* <private> */
 -  GtkTextBuffer *buffer;  /* The buffer which contains the text */
 -  GtkWidget *textview ;
 -  FILE *fp;               /* The file it's viewing */
 +  GtkLayout *output;
 +  int max_width;
 +  int y;
 +
 +  GtkTreeView *overview;
 +  int last_table_num;
 +  GtkTreeIter last_top_level;
  };
  
  struct _PsppireOutputWindowClass
@@@ -69,7 -70,9 +70,7 @@@
  GType      psppire_output_window_get_type        (void);
  GtkWidget* psppire_output_window_new             (void);
  
 -
 -void psppire_output_window_reload (void);
 -
 +void psppire_output_window_setup (void);
  
  G_END_DECLS