-/* 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 <libpspp/message.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
+
#include <ctype.h>
-#include <libpspp/alloc.h>
-#include <libpspp/message.h>
-#include <data/filename.h>
-#include "htmlP.h"
-#include "intprops.h"
-#include <libpspp/misc.h>
+#include <errno.h>
+#if HAVE_LC_PAPER
+#include <langinfo.h>
+#endif
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <data/file-name.h>
#include <data/settings.h>
+#include <libpspp/misc.h>
#include <libpspp/str.h>
+#include <output/htmlP.h>
+#include <output/output.h>
+
+#include "error.h"
+#include "intprops.h"
+#include "xalloc.h"
#include "gettext.h"
#define _(msgid) gettext (msgid)
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;
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)
{
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)
{
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 ();
}
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 ());
+ sprintf (buf, "%d", settings_get_viewwidth ());
return buf;
}
else if (!strcmp (key, "viewlength"))
{
- sprintf (buf, "%d", get_viewlength ());
+ sprintf (buf, "%d", settings_get_viewlength ());
return buf;
}
else
{
extern struct outp_class ascii_class;
extern struct outp_class postscript_class;
- extern struct outp_class epsf_class;
- extern struct outp_class html_class;
char def[] = "default";
add_class (&html_class);
- add_class (&epsf_class);
add_class (&postscript_class);
add_class (&ascii_class);
{
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
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)
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;
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 ();
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)
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);
free (outp_title);
outp_title = NULL;
-
+
free (outp_subtitle);
outp_subtitle = NULL;
}
void
outp_list_classes (void)
{
- int width = get_viewwidth();
+ int width = settings_get_viewwidth ();
struct outp_driver_class_list *c;
printf (_("Driver classes:\n\t"));
if ((int) strlen (c->class->name) + 1 > width)
{
printf ("\n\t");
- width = get_viewwidth() - 8;
+ width = settings_get_viewwidth () - 8;
}
else
putc (' ', stdout);
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];
-
- if (op_token != 'a')
- {
- msg (IS, _("Syntax error in options."));
- break;
- }
+ ss_ltrim (&left, ss_cstr (CC_SPACES));
+ if (ss_is_empty (left))
+ break;
- ds_truncate (&op_tokstr, 64);
- strcpy (key, ds_c_str (&op_tokstr));
+ if (!get_option_token (&left, driver->name, &key))
+ break;
- 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. */
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)
+ if (c == NULL)
{
- 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))
- {
- 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);
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
struct outp_driver *
outp_drivers (struct outp_driver *d)
{
-#if DEBUGGING
- struct outp_driver *orig_d = d;
-#endif
-
for (;;)
{
if (d == NULL)
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;
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;
}