Bug #21128. Reviewed by John Darrington.
[pspp-builds.git] / src / output / output.c
index 3f79a6a02afb0bf7094b5c258aceee9de380b774..842e1c15c63bc383219c4a5de027883b2c086aff 100644 (file)
@@ -1,37 +1,38 @@
-/* PSPP - computes sample statistics.
-   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
-   Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-9, 2000, 2007 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 2 of the
-   License, or (at your option) any later version.
+   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.
+   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, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
-#include "output.h"
-#include "message.h"
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
+
 #include <ctype.h>
-#include "alloc.h"
-#include "message.h"
-#include "filename.h"
-#include "htmlP.h"
+#include <errno.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <data/file-name.h>
+#include <data/settings.h>
+#include <libpspp/alloc.h>
+#include <libpspp/misc.h>
+#include <libpspp/str.h>
+#include <output/htmlP.h>
+#include <output/output.h>
+
+#include "error.h"
 #include "intprops.h"
-#include "misc.h"
-#include "settings.h"
-#include "str.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
@@ -61,15 +62,22 @@ struct outp_names
 struct outp_defn
   {
     char *key;
-    char *value;
+    struct string value;
     struct outp_defn *next, *prev;
   };
 
 static struct outp_defn *outp_macros;
 static struct outp_names *outp_configure_vec;
 
-struct outp_driver_class_list *outp_class_list;
-struct outp_driver *outp_driver_list;
+/* A list of driver classes. */
+struct outp_driver_class_list
+  {
+    const struct outp_class *class;
+    struct outp_driver_class_list *next;
+  };
+
+static struct outp_driver_class_list *outp_class_list;
+static struct outp_driver *outp_driver_list;
 
 char *outp_title;
 char *outp_subtitle;
@@ -79,18 +87,16 @@ char *outp_subtitle;
 static int disabled_devices;
 
 static void destroy_driver (struct outp_driver *);
-static void configure_driver_line (char *);
-static void configure_driver (const char *, const char *,
-                              const char *, const char *);
+static void configure_driver (const struct substring, const struct substring,
+                              const struct substring, const struct substring);
 
 /* Add a class to the class list. */
 static void
-add_class (struct outp_class *class)
+add_class (const struct outp_class *class)
 {
   struct outp_driver_class_list *new_list = xmalloc (sizeof *new_list);
 
   new_list->class = class;
-  new_list->ref_count = 0;
 
   if (!outp_class_list)
     {
@@ -148,8 +154,8 @@ add_name (char *bp, char *ep, int source)
   outp_configure_vec = n;
 }
 
-/* Checks that outp_configure_vec is empty, bitches & clears it if it
-   isn't. */
+/* Checks that outp_configure_vec is empty, complains and clears
+   it if it isn't. */
 static void
 check_configure_vec (void)
 {
@@ -157,9 +163,10 @@ check_configure_vec (void)
 
   for (n = outp_configure_vec; n; n = n->next)
     if (n->source == OUTP_S_COMMAND_LINE)
-      msg (ME, _("Unknown output driver `%s'."), n->name);
+      error (0, 0, _("unknown output driver `%s'"), n->name);
     else
-      msg (IE, _("Output driver `%s' referenced but never defined."), n->name);
+      error (0, 0, _("output driver `%s' referenced but never defined"),
+             n->name);
   outp_configure_clear ();
 }
 
@@ -200,7 +207,7 @@ find_defn_value (const char *key)
 
   for (d = outp_macros; d; d = d->next)
     if (!strcmp (key, d->key))
-      return d->value;
+      return ds_cstr (&d->value);
   if (!strcmp (key, "viewwidth"))
     {
       sprintf (buf, "%d", get_viewwidth ());
@@ -220,23 +227,12 @@ void
 outp_init (void)
 {
   extern struct outp_class ascii_class;
-#if !NO_POSTSCRIPT
   extern struct outp_class postscript_class;
-  extern struct outp_class epsf_class;
-#endif
-#if !NO_HTML
-  extern struct outp_class html_class;
-#endif
 
   char def[] = "default";
 
-#if !NO_HTML
   add_class (&html_class);
-#endif
-#if !NO_POSTSCRIPT
-  add_class (&epsf_class);
   add_class (&postscript_class);
-#endif
   add_class (&ascii_class);
 
   add_name (def, &def[strlen (def)], OUTP_S_INIT_FILE);
@@ -252,19 +248,19 @@ delete_macros (void)
     {
       next = d->next;
       free (d->key);
-      free (d->value);
+      ds_destroy (&d->value);
       free (d);
     }
 }
 
 static void
-init_default_drivers (void) 
+init_default_drivers (void)
 {
-  msg (MM, _("Using default output driver configuration."));
-  configure_driver ("list-ascii", "ascii", "listing",
-                    "length=66 width=79 char-set=ascii "
-                    "output-file=\"pspp.list\" "
-                    "bold-on=\"\" italic-on=\"\" bold-italic-on=\"\"");
+  error (0, 0, _("using default output driver configuration"));
+  configure_driver (ss_cstr ("list"),
+                    ss_cstr ("ascii"),
+                    ss_cstr ("listing"),
+                    ss_cstr ("length=66 width=79 output-file=\"pspp.list\""));
 }
 
 /* Reads the initialization file; initializes
@@ -278,45 +274,41 @@ outp_read_devices (void)
 
   FILE *f = NULL;
   struct string line;
-  struct file_locator where;
+  int line_number;
 
   init_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_INIT_FILE",
                                               "devices"),
                            fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
-                                              config_path),
-                           NULL);
-  where.filename = init_fn;
-  where.line_number = 0;
-  err_push_file_locator (&where);
+                                              config_path));
 
-  ds_init (&line, 128);
+  ds_init_empty (&line);
 
   if (init_fn == NULL)
     {
-      msg (IE, _("Cannot find output initialization file.  "
-                 "Use `-vvvvv' to view search path."));
+      error (0, 0, _("cannot find output initialization file "
+                     "(use `-vv' to view search path)"));
       goto exit;
     }
 
-  msg (VM (1), _("%s: Opening device description file..."), init_fn);
   f = fopen (init_fn, "r");
   if (f == NULL)
     {
-      msg (IE, _("Opening %s: %s."), init_fn, strerror (errno));
+      error (0, errno, _("cannot open \"%s\""), init_fn);
       goto exit;
     }
 
+  line_number = 0;
   for (;;)
     {
       char *cp;
 
-      if (!ds_get_config_line (f, &line, &where))
+      if (!ds_read_config_line (&line, &line_number, f))
        {
          if (ferror (f))
-           msg (ME, _("Reading %s: %s."), init_fn, strerror (errno));
+           error (0, errno, _("reading \"%s\""), init_fn);
          break;
        }
-      for (cp = ds_c_str (&line); isspace ((unsigned char) *cp); cp++);
+      for (cp = ds_cstr (&line); isspace ((unsigned char) *cp); cp++);
       if (!strncmp ("define", cp, 6) && isspace ((unsigned char) cp[6]))
        outp_configure_macro (&cp[7]);
       else if (*cp)
@@ -330,12 +322,12 @@ outp_read_devices (void)
              struct outp_names *n = search_names (cp, ep);
              if (n)
                {
-                 configure_driver_line (cp);
+                 outp_configure_driver_line (ds_ss (&line));
                  delete_name (n);
                }
            }
          else
-           msg (IS, _("Syntax error."));
+           error_at_line (0, 0, init_fn, line_number, _("syntax error"));
        }
     }
   result = 1;
@@ -343,21 +335,19 @@ outp_read_devices (void)
   check_configure_vec ();
 
 exit:
-  err_pop_file_locator (&where);
   if (f && -1 == fclose (f))
-    msg (MW, _("Closing %s: %s."), init_fn, strerror (errno));
+    error (0, errno, _("error closing \"%s\""), init_fn);
   free (init_fn);
   ds_destroy (&line);
   delete_macros ();
 
-  if (result) 
+  if (result)
     {
-      msg (VM (2), _("Device definition file read successfully."));
-      if (outp_driver_list == NULL) 
-        msg (MW, _("No output drivers are active.")); 
+      if (outp_driver_list == NULL)
+        error (0, 0, _("no active output drivers"));
     }
   else
-    msg (VM (1), _("Error reading device definition file."));
+    error (0, 0, _("error reading device definition file"));
 
   if (!result || outp_driver_list == NULL)
     init_default_drivers ();
@@ -414,12 +404,14 @@ outp_configure_macro (char *bp)
       free (d);
       return;
     }
-  
+
   if (*ep == '=')
     ep++;
   while (isspace ((unsigned char) *ep))
     ep++;
-  d->value = fn_interp_vars (ep, find_defn_value);
+
+  ds_init_cstr (&d->value, ep);
+  fn_interp_vars (ds_ss (&d->value), find_defn_value, &d->value);
   d->next = outp_macros;
   d->prev = NULL;
   if (outp_macros)
@@ -447,10 +439,11 @@ destroy_list (struct outp_driver ** dl)
 void
 outp_done (void)
 {
-  struct outp_driver_class_list *n = outp_class_list ; 
+  struct outp_driver_class_list *n = outp_class_list ;
+  outp_configure_clear ();
   destroy_list (&outp_driver_list);
 
-  while (n) 
+  while (n)
     {
       struct outp_driver_class_list *next = n->next;
       free(n);
@@ -460,7 +453,7 @@ outp_done (void)
 
   free (outp_title);
   outp_title = NULL;
-  
+
   free (outp_subtitle);
   outp_subtitle = NULL;
 }
@@ -488,174 +481,174 @@ outp_list_classes (void)
   putc('\n', stdout);
 }
 
-static int op_token;           /* `=', 'a', 0. */
-static struct string op_tokstr;
-static const char *prog;
+/* Obtains a token from S and advances its position.  Errors are
+   reported against the given DRIVER_NAME.
+   The token is stored in TOKEN.  Returns true if successful,
+   false on syntax error.
 
-/* Parses a token from prog into op_token, op_tokstr.  Sets op_token
-   to '=' on an equals sign, to 'a' on a string or identifier token,
-   or to 0 at end of line.  Returns the new op_token. */
-static int
-tokener (void)
+   Caller is responsible for skipping leading spaces. */
+static bool
+get_option_token (struct substring *s, const char *driver_name,
+                  struct string *token)
 {
-  if (op_token == 0)
+  int c;
+
+  ds_clear (token);
+  c = ss_get_char (s);
+  if (c == EOF)
     {
-      msg (IS, _("Syntax error."));
-      return 0;
+      error (0, 0, _("syntax error parsing options for \"%s\" driver"),
+             driver_name);
+      return false;
     }
-
-  while (isspace ((unsigned char) *prog))
-    prog++;
-  if (!*prog)
+  else if (c == '\'' || c == '"')
     {
-      op_token = 0;
-      return 0;
+      int quote = c;
+
+      for (;;)
+        {
+          c = ss_get_char (s);
+          if (c == quote)
+            break;
+          else if (c == EOF)
+            {
+              error (0, 0,
+                     _("reached end of options inside quoted string "
+                       "parsing options for \"%s\" driver"),
+                     driver_name);
+              return false;
+            }
+          else if (c != '\\')
+            ds_put_char (token, c);
+          else
+            {
+              int out;
+
+              c = ss_get_char (s);
+              switch (c)
+                {
+                case '\'':
+                  out = '\'';
+                  break;
+                case '"':
+                  out = '"';
+                  break;
+                case '\\':
+                  out = '\\';
+                  break;
+                case 'a':
+                  out = '\a';
+                  break;
+                case 'b':
+                  out = '\b';
+                  break;
+                case 'f':
+                  out = '\f';
+                  break;
+                case 'n':
+                  out = '\n';
+                  break;
+                case 'r':
+                  out = '\r';
+                  break;
+                case 't':
+                  out = '\t';
+                  break;
+                case 'v':
+                  out = '\v';
+                  break;
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                  out = c - '0';
+                  while (ss_first (*s) >= '0' && ss_first (*s) <= '7')
+                    out = out * 8 + (ss_get_char (s) - '0');
+                  break;
+                case 'x':
+                case 'X':
+                  out = 0;
+                  while (isxdigit (ss_first (*s)))
+                    {
+                      c = ss_get_char (s);
+                      out *= 16;
+                      if (isdigit (c))
+                        out += c - '0';
+                      else
+                        out += tolower (c) - 'a' + 10;
+                    }
+                  break;
+                default:
+                  error (0, 0, _("syntax error in string constant "
+                                 "parsing options for \"%s\" driver"),
+                         driver_name);
+                  return false;
+                }
+              ds_put_char (token, out);
+            }
+        }
     }
-
-  if (*prog == '=')
-    op_token = *prog++;
   else
     {
-      ds_clear (&op_tokstr);
-
-      if (*prog == '\'' || *prog == '"')
-       {
-         int quote = *prog++;
-
-         while (*prog && *prog != quote)
-           {
-             if (*prog != '\\')
-               ds_putc (&op_tokstr, *prog++);
-             else
-               {
-                 int c;
-                 
-                 prog++;
-                 assert ((int) *prog); /* How could a line end in `\'? */
-                 switch (*prog++)
-                   {
-                   case '\'':
-                     c = '\'';
-                     break;
-                   case '"':
-                     c = '"';
-                     break;
-                   case '?':
-                     c = '?';
-                     break;
-                   case '\\':
-                     c = '\\';
-                     break;
-                   case '}':
-                     c = '}';
-                     break;
-                   case 'a':
-                     c = '\a';
-                     break;
-                   case 'b':
-                     c = '\b';
-                     break;
-                   case 'f':
-                     c = '\f';
-                     break;
-                   case 'n':
-                     c = '\n';
-                     break;
-                   case 'r':
-                     c = '\r';
-                     break;
-                   case 't':
-                     c = '\t';
-                     break;
-                   case 'v':
-                     c = '\v';
-                     break;
-                   case '0':
-                   case '1':
-                   case '2':
-                   case '3':
-                   case '4':
-                   case '5':
-                   case '6':
-                   case '7':
-                     {
-                       c = prog[-1] - '0';
-                       while (*prog >= '0' && *prog <= '7')
-                         c = c * 8 + *prog++ - '0';
-                     }
-                     break;
-                   case 'x':
-                   case 'X':
-                     {
-                       c = 0;
-                       while (isxdigit ((unsigned char) *prog))
-                         {
-                           c *= 16;
-                           if (isdigit ((unsigned char) *prog))
-                             c += *prog - '0';
-                           else
-                             c += (tolower ((unsigned char) (*prog))
-                                   - 'a' + 10);
-                           prog++;
-                         }
-                     }
-                     break;
-                   default:
-                     msg (IS, _("Syntax error in string constant."));
-                      continue;
-                   }
-                 ds_putc (&op_tokstr, (unsigned char) c);
-               }
-           }
-         prog++;
-       }
-      else
-       while (*prog && !isspace ((unsigned char) *prog) && *prog != '=')
-         ds_putc (&op_tokstr, *prog++);
-      op_token = 'a';
+      for (;;)
+        {
+          ds_put_char (token, c);
+
+          c = ss_first (*s);
+          if (c == EOF || c == '=' || isspace (c))
+            break;
+          ss_advance (s, 1);
+        }
     }
 
   return 1;
 }
 
-/* Applies the user-specified options in string S to output driver D
-   (at configuration time). */
-static void
-parse_options (const char *s, struct outp_driver * d)
+bool
+outp_parse_options (struct substring options,
+                    bool (*callback) (struct outp_driver *, const char *key,
+                                      const struct string *value),
+                    struct outp_driver *driver)
 {
-  prog = s;
-  op_token = -1;
+  struct string key = DS_EMPTY_INITIALIZER;
+  struct string value = DS_EMPTY_INITIALIZER;
+  struct substring left = options;
+  bool ok = true;
 
-  ds_init (&op_tokstr, 64);
-  while (tokener ())
+  do
     {
-      char key[65];
+      ss_ltrim (&left, ss_cstr (CC_SPACES));
+      if (ss_is_empty (left))
+        break;
 
-      if (op_token != 'a')
-       {
-         msg (IS, _("Syntax error in options."));
-         break;
-       }
+      if (!get_option_token (&left, driver->name, &key))
+        break;
 
-      ds_truncate (&op_tokstr, 64);
-      strcpy (key, ds_c_str (&op_tokstr));
-
-      tokener ();
-      if (op_token != '=')
+      ss_ltrim (&left, ss_cstr (CC_SPACES));
+      if (!ss_match_char (&left, '='))
        {
-         msg (IS, _("Syntax error in options (`=' expected)."));
+         error (0, 0, _("syntax error expecting `=' "
+                         "parsing options for driver \"%s\""),
+                 driver->name);
          break;
        }
 
-      tokener ();
-      if (op_token != 'a')
-       {
-         msg (IS, _("Syntax error in options (value expected after `=')."));
-         break;
-       }
-      d->class->option (d, key, &op_tokstr);
+      ss_ltrim (&left, ss_cstr (CC_SPACES));
+      if (!get_option_token (&left, driver->name, &value))
+        break;
+
+      ok = callback (driver, ds_cstr (&key), &value);
     }
-  ds_destroy (&op_tokstr);
+  while (ok);
+
+  ds_destroy (&key);
+  ds_destroy (&value);
+
+  return ok;
 }
 
 /* Find the driver in outp_driver_list with name NAME. */
@@ -670,187 +663,123 @@ find_driver (char *name)
   return NULL;
 }
 
-/* Tokenize string S into colon-separated fields, removing leading and
-   trailing whitespace on tokens.  Returns a pointer to the
-   null-terminated token, which is formed by setting a NUL character
-   into the string.  After the first call, subsequent calls should set
-   S to NULL.  CP should be consistent across calls.  Returns NULL
-   after all fields have been used up.
-
-   FIXME: Should ignore colons inside double quotes. */
-static const char *
-colon_tokenize (char *s, char **cp)
-{
-  char *token;
-  
-  if (!s)
-    {
-      s = *cp;
-      if (*s == 0)
-       return NULL;
-    }
-  token = s += strspn (s, " \t\v\r");
-  *cp = strchr (s, ':');
-  if (*cp == NULL)
-    s = *cp = strchr (s, 0);
-  else
-    s = (*cp)++;
-  while (s > token && strchr (" \t\v\r", s[-1]))
-    s--;
-  *s = 0;
-  return token;
-}
-
-/* String S is in format:
-   DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
-   Adds a driver to outp_driver_list pursuant to the specification
-   provided.  */
+/* Adds a driver to outp_driver_list pursuant to the
+   specification provided.  */
 static void
-configure_driver (const char *driver_name, const char *class_name,
-                  const char *device_type, const char *options)
+configure_driver (struct substring driver_name, struct substring class_name,
+                  struct substring device_type, struct substring options)
 {
-  struct outp_driver *d = NULL, *iter;
-  struct outp_driver_class_list *c = NULL;
+  struct outp_driver *d, *iter;
+  struct outp_driver_class_list *c;
 
-  d = xmalloc (sizeof *d);
-  d->class = NULL;
-  d->name = xstrdup (driver_name);
-  d->driver_open = 0;
-  d->page_open = 0;
-  d->next = d->prev = NULL;
-  d->device = OUTP_DEV_NONE;
-  d->ext = NULL;
+  struct substring token;
+  size_t save_idx = 0;
+  int device;
 
+  /* Find class. */
   for (c = outp_class_list; c; c = c->next)
-    if (!strcmp (c->class->name, class_name))
+    if (!ss_compare (ss_cstr (c->class->name), class_name))
       break;
-  if (!c)
-    {
-      msg (IS, _("Unknown output driver class `%s'."), class_name);
-      goto error;
-    }
-  
-  d->class = c->class;
-  if (!c->ref_count && !d->class->open_global (d->class))
-    {
-      msg (IS, _("Can't initialize output driver class `%s'."),
-          d->class->name);
-      goto error;
-    }
-  c->ref_count++;
-  if (!d->class->preopen_driver (d))
+  if (c == NULL)
     {
-      msg (IS, _("Can't initialize output driver `%s' of class `%s'."),
-          d->name, d->class->name);
-      goto error;
+      error (0, 0, _("unknown output driver class `%.*s'"),
+             (int) ss_length (class_name), ss_data (class_name));
+      return;
     }
 
-  /* Device types. */
-  if (device_type != NULL)
-    {
-      char *copy = xstrdup (device_type);
-      char *sp, *type;
+  /* Parse device type. */
+  device = 0;
+  while (ss_tokenize (device_type, ss_cstr (CC_SPACES), &save_idx, &token))
+    if (!ss_compare (token, ss_cstr ("listing")))
+      device |= OUTP_DEV_LISTING;
+    else if (!ss_compare (token, ss_cstr ("screen")))
+      device |= OUTP_DEV_SCREEN;
+    else if (!ss_compare (token, ss_cstr ("printer")))
+      device |= OUTP_DEV_PRINTER;
+    else
+      error (0, 0, _("unknown device type `%.*s'"),
+             (int) ss_length (token), ss_data (token));
 
-      for (type = strtok_r (copy, " \t\r\v", &sp); type;
-          type = strtok_r (NULL, " \t\r\v", &sp))
-       {
-         if (!strcmp (type, "listing"))
-           d->device |= OUTP_DEV_LISTING;
-         else if (!strcmp (type, "screen"))
-           d->device |= OUTP_DEV_SCREEN;
-         else if (!strcmp (type, "printer"))
-           d->device |= OUTP_DEV_PRINTER;
-         else
-           {
-             msg (IS, _("Unknown device type `%s'."), type);
-              free (copy);
-             goto error;
-           }
-       }
-      free (copy);
-    }
-  
-  /* Options. */
-  if (options != NULL)
-    parse_options (options, d);
-  if (!d->class->postopen_driver (d))
+  /* Open the device. */
+  d = xmalloc (sizeof *d);
+  d->next = d->prev = NULL;
+  d->class = c->class;
+  d->name = ss_xstrdup (driver_name);
+  d->page_open = false;
+  d->device = device;
+  d->cp_x = d->cp_y = 0;
+  d->ext = NULL;
+  d->prc = NULL;
+
+  /* Open driver. */
+  if (!d->class->open_driver (d, options))
     {
-      msg (IS, _("Can't complete initialization of output driver `%s' of "
-          "class `%s'."), d->name, d->class->name);
-      goto error;
+      error (0, 0, _("cannot initialize output driver `%s' of class `%s'"),
+             d->name, d->class->name);
+      free (d->name);
+      free (d);
+      return;
     }
 
   /* Find like-named driver and delete. */
   iter = find_driver (d->name);
-  if (iter)
+  if (iter != NULL)
     destroy_driver (iter);
 
   /* Add to list. */
   d->next = outp_driver_list;
   d->prev = NULL;
-  if (outp_driver_list)
+  if (outp_driver_list != NULL)
     outp_driver_list->prev = d;
   outp_driver_list = d;
-  return;
-
-error:
-  if (d)
-    destroy_driver (d);
-  return;
 }
 
-/* String S is in format:
+/* String LINE is in format:
    DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
    Adds a driver to outp_driver_list pursuant to the specification
    provided.  */
-static void
-configure_driver_line (char *s)
+void
+outp_configure_driver_line (struct substring line_)
 {
-  char *cp;
-  const char *driver_name, *class_name, *device_type, *options;
+  struct string line = DS_EMPTY_INITIALIZER;
+  struct substring tokens[4];
+  size_t save_idx;
+  size_t i;
 
-  s = fn_interp_vars (s, find_defn_value);
+  fn_interp_vars (line_, find_defn_value, &line);
 
-  /* Driver name. */
-  driver_name = colon_tokenize (s, &cp);
-  class_name = colon_tokenize (NULL, &cp);
-  device_type = colon_tokenize (NULL, &cp);
-  options = colon_tokenize (NULL, &cp);
-  if (driver_name == NULL || class_name == NULL)
+  save_idx = 0;
+  for (i = 0; i < 4; i++)
     {
-      msg (IS, _("Driver definition line contains fewer fields "
-                 "than expected"));
-      return;
+      struct substring *token = &tokens[i];
+      ds_separate (&line, ss_cstr (i < 3 ? ":" : ""), &save_idx, token);
+      ss_trim (token, ss_cstr (CC_SPACES));
     }
 
-  configure_driver (driver_name, class_name, device_type, options);
+  if (!ss_is_empty (tokens[0]) && !ss_is_empty (tokens[1]))
+    configure_driver (tokens[0], tokens[1], tokens[2], tokens[3]);
+  else
+    error (0, 0,
+           _("driver definition line missing driver name or class name"));
+
+  ds_destroy (&line);
 }
 
 /* Destroys output driver D. */
 static void
 destroy_driver (struct outp_driver *d)
 {
-  if (d->page_open)
-    d->class->close_page (d);
+  outp_close_page (d);
   if (d->class)
     {
       struct outp_driver_class_list *c;
 
-      if (d->driver_open)
-       d->class->close_driver (d);
+      d->class->close_driver (d);
 
       for (c = outp_class_list; c; c = c->next)
        if (c->class == d->class)
          break;
       assert (c != NULL);
-      
-      c->ref_count--;
-      if (c->ref_count == 0)
-       {
-         if (!d->class->close_global (d->class))
-           msg (IS, _("Can't deinitialize output driver class `%s'."),
-                d->class->name);
-       }
     }
   free (d->name);
 
@@ -863,392 +792,290 @@ destroy_driver (struct outp_driver *d)
     outp_driver_list = d->next;
 }
 
-static int
-option_cmp (const void *a, const void *b)
+/* Tries to match S as one of the keywords in TAB, with
+   corresponding information structure INFO.  Returns category
+   code and stores subcategory in *SUBCAT on success.  Returns -1
+   on failure. */
+int
+outp_match_keyword (const char *s, const struct outp_option *tab, int *subcat)
 {
-  const struct outp_option *o1 = a;
-  const struct outp_option *o2 = b;
-  return strcmp (o1->keyword, o2->keyword);
+  for (; tab->keyword != NULL; tab++)
+    if (!strcmp (s, tab->keyword))
+      {
+        *subcat = tab->subcat;
+        return tab->cat;
+      }
+  return -1;
 }
 
-/* Tries to match S as one of the keywords in TAB, with corresponding
-   information structure INFO.  Returns category code or 0 on failure;
-   if category code is negative then stores subcategory in *SUBCAT. */
-int
-outp_match_keyword (const char *s, struct outp_option *tab,
-                   struct outp_option_info *info, int *subcat)
+/* Parses UNIT as a dimensional unit.  Returns the multiplicative
+   factor needed to change a quantity measured in that unit into
+   1/72000" units.  If UNIT is empty, it is treated as
+   millimeters.  If the unit is unrecognized, returns 0. */
+static double
+parse_unit (const char *unit)
 {
-  char *cp;
-  struct outp_option *oip;
-
-  /* Form hash table. */
-  if (NULL == info->initial)
+  struct unit
     {
-      /* Count items. */
-      int count, i;
-      char s[256], *cp;
-      struct outp_option *ptr[255], **oip;
-
-      for (count = 0; tab[count].keyword[0]; count++)
-       ;
-
-      /* Sort items. */
-      qsort (tab, count, sizeof *tab, option_cmp);
-
-      cp = s;
-      oip = ptr;
-      *cp = tab[0].keyword[0];
-      *oip++ = &tab[0];
-      for (i = 0; i < count; i++)
-       if (tab[i].keyword[0] != *cp)
-         {
-           *++cp = tab[i].keyword[0];
-           *oip++ = &tab[i];
-         }
-      *++cp = 0;
-
-      info->initial = xstrdup (s);
-      info->options = xnmalloc (cp - s, sizeof *info->options);
-      memcpy (info->options, ptr, sizeof *info->options * (cp - s));
-    }
-
-  cp = info->initial;
-  oip = *info->options;
+      char name[3];
+      double factor;
+    };
 
-  if (s[0] == 0)
-    return 0;
-  cp = strchr (info->initial, s[0]);
-  if (!cp)
-    return 0;
-#if 0
-  printf (_("Trying to find keyword `%s'...\n"), s);
-#endif
-  oip = info->options[cp - info->initial];
-  while (oip->keyword[0] == s[0])
+  static const struct unit units[] =
     {
-#if 0
-      printf ("- %s\n", oip->keyword);
-#endif
-      if (!strcmp (s, oip->keyword))
-       {
-         if (oip->cat < 0)
-           *subcat = oip->subcat;
-         return oip->cat;
-       }
-      oip++;
-    }
+      {"pt", 72000 / 72},
+      {"pc", 72000 / 72 * 12.0},
+      {"in", 72000},
+      {"cm", 72000 / 2.54},
+      {"mm", 72000 / 25.4},
+      {"", 72000 / 25.4},
+    };
 
-  return 0;
-}
+  const struct unit *p;
 
-/* Encapsulate two characters in a single int. */
-#define TWO_CHARS(A, B)                                \
-       ((A) + ((B)<<8))
+  unit += strspn (unit, CC_SPACES);
+  for (p = units; p < units + sizeof units / sizeof *units; p++)
+    if (!strcasecmp (unit, p->name))
+      return p->factor;
+  return 0.0;
+}
 
-/* Determines the size of a dimensional measurement and returns the
-   size in units of 1/72000".  Units if not specified explicitly are
-   inches for values under 50, millimeters otherwise.  Returns 0,
-   stores NULL to *TAIL on error; otherwise returns dimension, stores
-   address of next */
+/* Determines the size of a dimensional measurement and returns
+   the size in units of 1/72000".  Units are assumed to be
+   millimeters unless otherwise specified.  Returns 0 on
+   error. */
 int
-outp_evaluate_dimension (char *dimen, char **tail)
+outp_evaluate_dimension (const char *dimen)
 {
-  char *s = dimen;
-  char *ptail;
-  double value;
-
-  value = strtod (s, &ptail);
-  if (ptail == s)
-    goto lossage;
-  if (*ptail == '-')
-    {
-      double b, c;
-      s = &ptail[1];
-      b = strtod (s, &ptail);
-      if (b <= 0.0 || ptail == s)
-       goto lossage;
-      if (*ptail != '/')
-       goto lossage;
-      s = &ptail[1];
-      c = strtod (s, &ptail);
-      if (c <= 0.0 || ptail == s)
-       goto lossage;
-      s = ptail;
-      if (c == 0.0)
-       goto lossage;
-      if (value > 0)
-       value += b / c;
-      else
-       value -= b / c;
-    }
-  else if (*ptail == '/')
-    {
-      double b;
-      s = &ptail[1];
-      b = strtod (s, &ptail);
-      if (b <= 0.0 || ptail == s)
-       goto lossage;
-      s = ptail;
-      value /= b;
-    }
-  else
-    s = ptail;
-  if (*s == 0 || isspace ((unsigned char) *s))
-    {
-      if (value < 50.0)
-       value *= 72000;
-      else
-       value *= 72000 / 25.4;
-    }
-  else
-    {
-      double factor;
+  double raw, factor;
+  char *tail;
 
-      /* Standard TeX units are supported. */
-      if (*s == '"')
-       factor = 72000, s++;
-      else
-       switch (TWO_CHARS (s[0], s[1]))
-         {
-         case TWO_CHARS ('p', 't'):
-           factor = 72000 / 72.27;
-           break;
-         case TWO_CHARS ('p', 'c'):
-           factor = 72000 / 72.27 * 12.0;
-           break;
-         case TWO_CHARS ('i', 'n'):
-           factor = 72000;
-           break;
-         case TWO_CHARS ('b', 'p'):
-           factor = 72000 / 72.0;
-           break;
-         case TWO_CHARS ('c', 'm'):
-           factor = 72000 / 2.54;
-           break;
-         case TWO_CHARS ('m', 'm'):
-           factor = 72000 / 25.4;
-           break;
-         case TWO_CHARS ('d', 'd'):
-           factor = 72000 / 72.27 * 1.0700086;
-           break;
-         case TWO_CHARS ('c', 'c'):
-           factor = 72000 / 72.27 * 12.840104;
-           break;
-         case TWO_CHARS ('s', 'p'):
-           factor = 72000 / 72.27 / 65536.0;
-           break;
-         default:
-           msg (SE, _("Unit \"%s\" is unknown in dimension \"%s\"."), s, dimen);
-           *tail = NULL;
-           return 0;
-         }
-      ptail += 2;
-      value *= factor;
-    }
-  if (value <= 0.0)
-    goto lossage;
-  if (tail)
-    *tail = ptail;
-  return value + 0.5;
-
-lossage:
-  *tail = NULL;
-  msg (SE, _("Bad dimension \"%s\"."), dimen);
+  /* Number. */
+  raw = strtod (dimen, &tail);
+  if (raw <= 0.0)
+    goto syntax_error;
+
+  /* Unit. */
+  factor = parse_unit (tail);
+  if (factor == 0.0)
+    goto syntax_error;
+
+  return raw * factor;
+
+syntax_error:
+  error (0, 0, _("`%s' is not a valid length."), dimen);
   return 0;
 }
 
 /* Stores the dimensions in 1/72000" units of paper identified by
-   SIZE, which is of form `HORZ x VERT' or `HORZ by VERT' where each
-   of HORZ and VERT are dimensions, into *H and *V.  Return nonzero on
-   success. */
-static int
-internal_get_paper_size (char *size, int *h, int *v)
+   SIZE, which is of form `HORZ x VERT [UNIT]' where HORZ and
+   VERT are numbers and UNIT is an optional unit of measurement,
+   into *H and *V.  Return true on success. */
+static bool
+parse_paper_size (const char *size, int *h, int *v)
 {
+  double raw_h, raw_v, factor;
   char *tail;
 
-  while (isspace ((unsigned char) *size))
-    size++;
-  *h = outp_evaluate_dimension (size, &tail);
-  if (tail == NULL)
-    return 0;
-  while (isspace ((unsigned char) *tail))
-    tail++;
-  if (*tail == 'x')
-    tail++;
-  else if (*tail == 'b' && tail[1] == 'y')
-    tail += 2;
-  else
-    {
-      msg (SE, _("`x' expected in paper size `%s'."), size);
-      return 0;
-    }
-  *v = outp_evaluate_dimension (tail, &tail);
-  if (tail == NULL)
-    return 0;
-  while (isspace ((unsigned char) *tail))
-    tail++;
-  if (*tail)
-    {
-      msg (SE, _("Trailing garbage `%s' on paper size `%s'."), tail, size);
-      return 0;
-    }
-  
-  return 1;
-}
+  /* Width. */
+  raw_h = strtod (size, &tail);
+  if (raw_h <= 0.0)
+    return false;
 
-/* Stores the dimensions, in 1/72000" units, of paper identified by
-   SIZE into *H and *V.  SIZE may be a pair of dimensions of form `H x
-   V', or it may be a case-insensitive paper identifier, which is
-   looked up in the `papersize' configuration file.  Returns nonzero
-   on success.  May modify SIZE. */
-/* Don't read further unless you've got a strong stomach. */
-int
-outp_get_paper_size (char *size, int *h, int *v)
-{
-  struct paper_size
-    {
-      char *name;
-      int use;
-      int h, v;
-    };
+  /* Delimiter. */
+  tail += strspn (tail, CC_SPACES "x,");
 
-  static struct paper_size cache[4];
-  static int use;
+  /* Length. */
+  raw_v = strtod (tail, &tail);
+  if (raw_v <= 0.0)
+    return false;
 
-  FILE *f;
-  char *pprsz_fn;
+  /* Unit. */
+  factor = parse_unit (tail);
+  if (factor == 0.0)
+    return false;
 
-  struct string line;
-  struct file_locator where;
+  *h = raw_h * factor + .5;
+  *v = raw_v * factor + .5;
+  return true;
+}
 
-  int free_it = 0;
-  int result = 0;
-  int min_value, min_index;
-  char *ep;
-  int i;
-
-  while (isspace ((unsigned char) *size))
-    size++;
-  if (isdigit ((unsigned char) *size))
-    return internal_get_paper_size (size, h, v);
-  ep = size;
-  while (*ep)
-    ep++;
-  while (isspace ((unsigned char) *ep) && ep >= size)
-    ep--;
-  if (ep == size)
+static bool
+get_standard_paper_size (struct substring name, int *h, int *v)
+{
+  static const char *sizes[][2] =
     {
-      msg (SE, _("Paper size name must not be empty."));
-      return 0;
-    }
-  
-  ep++;
-  if (*ep)
-    *ep = 0;
+      {"a0", "841 x 1189 mm"},
+      {"a1", "594 x 841 mm"},
+      {"a2", "420 x 594 mm"},
+      {"a3", "297 x 420 mm"},
+      {"a4", "210 x 297 mm"},
+      {"a5", "148 x 210 mm"},
+      {"b5", "176 x 250 mm"},
+      {"a6", "105 x 148 mm"},
+      {"a7", "74 x 105 mm"},
+      {"a8", "52 x 74 mm"},
+      {"a9", "37 x 52 mm"},
+      {"a10", "26 x 37 mm"},
+      {"b0", "1000 x 1414 mm"},
+      {"b1", "707 x 1000 mm"},
+      {"b2", "500 x 707 mm"},
+      {"b3", "353 x 500 mm"},
+      {"b4", "250 x 353 mm"},
+      {"letter", "612 x 792 pt"},
+      {"legal", "612 x 1008 pt"},
+      {"executive", "522 x 756 pt"},
+      {"note", "612 x 792 pt"},
+      {"11x17", "792 x 1224 pt"},
+      {"tabloid", "792 x 1224 pt"},
+      {"statement", "396 x 612 pt"},
+      {"halfletter", "396 x 612 pt"},
+      {"halfexecutive", "378 x 522 pt"},
+      {"folio", "612 x 936 pt"},
+      {"quarto", "610 x 780 pt"},
+      {"ledger", "1224 x 792 pt"},
+      {"archA", "648 x 864 pt"},
+      {"archB", "864 x 1296 pt"},
+      {"archC", "1296 x 1728 pt"},
+      {"archD", "1728 x 2592 pt"},
+      {"archE", "2592 x 3456 pt"},
+      {"flsa", "612 x 936 pt"},
+      {"flse", "612 x 936 pt"},
+      {"csheet", "1224 x 1584 pt"},
+      {"dsheet", "1584 x 2448 pt"},
+      {"esheet", "2448 x 3168 pt"},
+    };
 
-  use++;
-  for (i = 0; i < 4; i++)
-    if (cache[i].name != NULL && !strcasecmp (cache[i].name, size))
+  size_t i;
+
+  for (i = 0; i < sizeof sizes / sizeof *sizes; i++)
+    if (ss_equals_case (ss_cstr (sizes[i][0]), name))
       {
-       *h = cache[i].h;
-       *v = cache[i].v;
-       cache[i].use = use;
-       return 1;
+        bool ok = parse_paper_size (sizes[i][1], h, v);
+        assert (ok);
+        return ok;
       }
+  error (0, 0, _("unknown paper type `%.*s'"),
+         (int) ss_length (name), ss_data (name));
+  return false;
+}
 
-  pprsz_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_PAPERSIZE_FILE",
-                                               "papersize"),
-                            fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
-                                               config_path),
-                            NULL);
-
-  where.filename = pprsz_fn;
-  where.line_number = 0;
-  err_push_file_locator (&where);
-  ds_init (&line, 128);
-
-  if (pprsz_fn == NULL)
-    {
-      msg (IE, _("Cannot find `papersize' configuration file."));
-      goto exit;
-    }
+/* Reads file FILE_NAME to find a paper size.  Stores the
+   dimensions, in 1/72000" units, into *H and *V.  Returns true
+   on success, false on failure. */
+static bool
+read_paper_conf (const char *file_name, int *h, int *v)
+{
+  struct string line = DS_EMPTY_INITIALIZER;
+  int line_number = 0;
+  FILE *file;
 
-  msg (VM (1), _("%s: Opening paper size definition file..."), pprsz_fn);
-  f = fopen (pprsz_fn, "r");
-  if (!f)
+  file = fopen (file_name, "r");
+  if (file == NULL)
     {
-      msg (IE, _("Opening %s: %s."), pprsz_fn, strerror (errno));
-      goto exit;
+      error (0, errno, _("error opening \"%s\""), file_name);
+      return false;
     }
 
   for (;;)
     {
-      char *cp, *bp, *ep;
+      struct substring name;
 
-      if (!ds_get_config_line (f, &line, &where))
+      if (!ds_read_config_line (&line, &line_number, file))
        {
-         if (ferror (f))
-           msg (ME, _("Reading %s: %s."), pprsz_fn, strerror (errno));
+         if (ferror (file))
+           error (0, errno, _("error reading \"%s\""), file_name);
          break;
        }
-      for (cp = ds_c_str (&line); isspace ((unsigned char) *cp); cp++);
-      if (*cp == 0)
-       continue;
-      if (*cp != '"')
-       goto lex_error;
-      for (bp = ep = cp + 1; *ep && *ep != '"'; ep++);
-      if (!*ep)
-       goto lex_error;
-      *ep = 0;
-      if (0 != strcasecmp (bp, size))
-       continue;
-
-      for (cp = ep + 1; isspace ((unsigned char) *cp); cp++);
-      if (*cp == '=')
-       {
-         size = xmalloc (ep - bp + 1);
-         strcpy (size, bp);
-         free_it = 1;
-         continue;
-       }
-      size = &ep[1];
-      break;
 
-    lex_error:
-      msg (IE, _("Syntax error in paper size definition."));
+      name = ds_ss (&line);
+      ss_trim (&name, ss_cstr (CC_SPACES));
+      if (!ss_is_empty (name))
+        {
+          bool ok = get_standard_paper_size (name, h, v);
+          fclose (file);
+          ds_destroy (&line);
+          return ok;
+        }
     }
 
-  /* We found the one we want! */
-  result = internal_get_paper_size (size, h, v);
-  if (result)
-    {
-      min_value = cache[0].use;
-      min_index = 0;
-      for (i = 1; i < 4; i++)
-       if (cache[0].use < min_value)
-         {
-           min_value = cache[i].use;
-           min_index = i;
-         }
-      free (cache[min_index].name);
-      cache[min_index].name = xstrdup (size);
-      cache[min_index].use = use;
-      cache[min_index].h = *h;
-      cache[min_index].v = *v;
-    }
-
-exit:
-  err_pop_file_locator (&where);
+  fclose (file);
   ds_destroy (&line);
-  if (free_it)
-    free (size);
+  error (0, 0, _("paper size file \"%s\" does not state a paper size"),
+         file_name);
+  return false;
+}
 
-  if (result)
-    msg (VM (2), _("Paper size definition file read successfully."));
+/* The user didn't specify a paper size, so let's choose a
+   default based on his environment.  Stores the
+   dimensions, in 1/72000" units, into *H and *V.  Returns true
+   on success, false on failure. */
+static bool
+get_default_paper_size (int *h, int *v)
+{
+  /* libpaper in Debian (and other distributions?) allows the
+     paper size to be specified in $PAPERSIZE or in a file
+     specified in $PAPERCONF. */
+  if (getenv ("PAPERSIZE") != NULL)
+    return get_standard_paper_size (ss_cstr (getenv ("PAPERSIZE")), h, v);
+  if (getenv ("PAPERCONF") != NULL)
+    return read_paper_conf (getenv ("PAPERCONF"), h, v);
+
+#if HAVE_LC_PAPER
+  /* LC_PAPER is a non-standard glibc extension. */
+  *h = (int) nl_langinfo(_NL_PAPER_WIDTH) * (72000 / 25.4);
+  *v = (int) nl_langinfo(_NL_PAPER_HEIGHT) * (72000 / 25.4);
+  if (*h > 0 && *v > 0)
+     return true;
+#endif
+
+  /* libpaper defaults to /etc/papersize. */
+  if (fn_exists ("/etc/papersize"))
+    return read_paper_conf ("/etc/papersize", h, v);
+
+  /* Can't find a default. */
+  return false;
+}
+
+/* Stores the dimensions, in 1/72000" units, of paper identified
+   by SIZE into *H and *V.  SIZE can be the name of a kind of
+   paper ("a4", "letter", ...) or a pair of dimensions
+   ("210x297", "8.5x11in", ...).  Returns true on success, false
+   on failure.  On failure, *H and *V are set for A4 paper. */
+bool
+outp_get_paper_size (const char *size, int *h, int *v)
+{
+  struct substring s;
+  bool ok;
+
+  s = ss_cstr (size);
+  ss_trim (&s, ss_cstr (CC_SPACES));
+
+  if (ss_is_empty (s))
+    {
+      /* Treat empty string as default paper size. */
+      ok = get_default_paper_size (h, v);
+    }
+  else if (isdigit (ss_first (s)))
+    {
+      /* Treat string that starts with digit as explicit size. */
+      ok = parse_paper_size (size, h, v);
+      if (!ok)
+        error (0, 0, _("syntax error in paper size `%s'"), size);
+    }
   else
-    msg (VM (1), _("Error reading paper size definition file."));
-  
-  return result;
+    {
+      /* Check against standard paper sizes. */
+      ok = get_standard_paper_size (s, h, v);
+    }
+
+  /* Default to A4 on error. */
+  if (!ok)
+    {
+      *h = 210 * (72000 / 25.4);
+      *v = 297 * (72000 / 25.4);
+    }
+  return ok;
 }
 
 /* If D is NULL, returns the first enabled driver if any, NULL if
@@ -1258,10 +1085,6 @@ exit:
 struct outp_driver *
 outp_drivers (struct outp_driver *d)
 {
-#if GLOBAL_DEBUGGING
-  struct outp_driver *orig_d = d;
-#endif
-
   for (;;)
     {
       if (d == NULL)
@@ -1270,19 +1093,17 @@ outp_drivers (struct outp_driver *d)
        d = d->next;
 
       if (d == NULL
-         || (d->driver_open
-             && (d->device == 0
-                 || (d->device & disabled_devices) != d->device)))
+         || (d->device == 0 || (d->device & disabled_devices) != d->device))
        break;
     }
 
   return d;
 }
 
-/* Enables (if ENABLE is nonzero) or disables (if ENABLE is zero) the
+/* Enables (if ENABLE is true) or disables (if ENABLE is false) the
    device(s) given in mask DEVICE. */
 void
-outp_enable_device (int enable, int device)
+outp_enable_device (bool enable, int device)
 {
   if (enable)
     disabled_devices &= ~device;
@@ -1290,40 +1111,67 @@ outp_enable_device (int enable, int device)
     disabled_devices |= device;
 }
 
-/* Ejects the paper on device D, if the page is not blank. */
-int
-outp_eject_page (struct outp_driver *d)
+/* Opens a page on driver D (if one is not open). */
+void
+outp_open_page (struct outp_driver *d)
 {
-  if (d->page_open == 0)
-    return 1;
-  
-  if (d->cp_y != 0)
+  if (!d->page_open)
     {
       d->cp_x = d->cp_y = 0;
 
-      if (d->class->close_page (d) == 0)
-       msg (ME, _("Error closing page on %s device of %s class."),
-            d->name, d->class->name);
-      if (d->class->open_page (d) == 0)
-       {
-         msg (ME, _("Error opening page on %s device of %s class."),
-              d->name, d->class->name);
-         return 0;
-       }
+      d->page_open = true;
+      if (d->class->open_page != NULL)
+        d->class->open_page (d);
+    }
+}
+
+/* Closes the page on driver D (if one is open). */
+void
+outp_close_page (struct outp_driver *d)
+{
+  if (d->page_open)
+    {
+      if (d->class->close_page != NULL)
+        d->class->close_page (d);
+      d->page_open = false;
+    }
+}
+
+/* Ejects the page on device D, if a page is open and non-blank,
+   and opens a new page.  */
+void
+outp_eject_page (struct outp_driver *d)
+{
+  if (d->page_open && d->cp_y != 0)
+    outp_close_page (d);
+  outp_open_page (d);
+}
+
+/* Flushes output to screen devices, so that the user can see
+   output that doesn't fill up an entire page. */
+void
+outp_flush (struct outp_driver *d)
+{
+  if (d->device & OUTP_DEV_SCREEN && d->class->flush != NULL)
+    {
+      outp_close_page (d);
+      d->class->flush (d);
     }
-  return 1;
 }
 
 /* Returns the width of string S, in device units, when output on
    device D. */
 int
-outp_string_width (struct outp_driver *d, const char *s)
+outp_string_width (struct outp_driver *d, const char *s, enum outp_font font)
 {
   struct outp_text text;
+  int width;
 
-  text.options = OUTP_T_JUST_LEFT;
-  ls_init (&text.s, (char *) s, strlen (s));
-  d->class->text_metrics (d, &text);
+  text.font = font;
+  text.justification = OUTP_LEFT;
+  text.string = ss_cstr (s);
+  text.h = text.v = INT_MAX;
+  d->class->text_metrics (d, &text, &width, NULL);
 
-  return text.h;
+  return width;
 }