Bug #21128. Reviewed by John Darrington.
[pspp-builds.git] / src / output / output.c
index 5a24d89547a8ea054eb5dd3b5721a7dca761fe0b..842e1c15c63bc383219c4a5de027883b2c086aff 100644 (file)
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
-#include "output.h"
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
+
 #include <ctype.h>
-#include <libpspp/alloc.h>
+#include <errno.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+
 #include <data/file-name.h>
-#include "htmlP.h"
-#include "intprops.h"
-#include <libpspp/misc.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 "gettext.h"
 #define _(msgid) gettext (msgid)
@@ -803,275 +808,274 @@ outp_match_keyword (const char *s, const struct outp_option *tab, int *subcat)
   return -1;
 }
 
-/* Encapsulate two characters in a single int. */
-#define TWO_CHARS(A, B)                                \
-       ((A) + ((B)<<8))
-
-/* 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 */
-int
-outp_evaluate_dimension (char *dimen, char **tail)
+/* 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 *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
+  struct unit
     {
+      char name[3];
       double factor;
+    };
 
-      /* 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:
-           error (0, 0,
-                   _("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;
-  error (0, 0, _("bad dimension \"%s\""), dimen);
+  static const struct unit units[] =
+    {
+      {"pt", 72000 / 72},
+      {"pc", 72000 / 72 * 12.0},
+      {"in", 72000},
+      {"cm", 72000 / 2.54},
+      {"mm", 72000 / 25.4},
+      {"", 72000 / 25.4},
+    };
+
+  const struct unit *p;
+
+  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 are assumed to be
+   millimeters unless otherwise specified.  Returns 0 on
+   error. */
+int
+outp_evaluate_dimension (const char *dimen)
+{
+  double raw, factor;
+  char *tail;
+
+  /* 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 true on
-   success. */
+   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
-internal_get_paper_size (char *size, int *h, int *v)
+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)
+  /* Width. */
+  raw_h = strtod (size, &tail);
+  if (raw_h <= 0.0)
+    return false;
+
+  /* Delimiter. */
+  tail += strspn (tail, CC_SPACES "x,");
+
+  /* Length. */
+  raw_v = strtod (tail, &tail);
+  if (raw_v <= 0.0)
+    return false;
+
+  /* Unit. */
+  factor = parse_unit (tail);
+  if (factor == 0.0)
     return false;
-  while (isspace ((unsigned char) *tail))
-    tail++;
-  if (*tail == 'x')
-    tail++;
-  else if (*tail == 'b' && tail[1] == 'y')
-    tail += 2;
-  else
-    {
-      error (0, 0, _("`x' expected in paper size `%s'"), size);
-      return false;
-    }
-  *v = outp_evaluate_dimension (tail, &tail);
-  if (tail == NULL)
-    return 0;
-  while (isspace ((unsigned char) *tail))
-    tail++;
-  if (*tail)
-    {
-      error (0, 0, _("trailing garbage `%s' on paper size `%s'"), tail, size);
-      return false;
-    }
 
+  *h = raw_h * factor + .5;
+  *v = raw_v * factor + .5;
   return true;
 }
 
-/* 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 true
-   on success.  May modify SIZE. */
-/* Don't read further unless you've got a strong stomach. */
-bool
-outp_get_paper_size (char *size, int *h, int *v)
+static bool
+get_standard_paper_size (struct substring name, int *h, int *v)
 {
-  struct paper_size
+  static const char *sizes[][2] =
     {
-      char *name;
-      int use;
-      int h, v;
+      {"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"},
     };
 
-  FILE *f;
-  char *pprsz_fn;
-
-  struct string line;
-  int line_number = 0;
-
-  bool free_it = false;
-  bool result = false;
-  char *ep;
-
-  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)
-    {
-      error (0, 0, _("paper size name cannot be empty"));
-      return 0;
-    }
-
-  ep++;
-  if (*ep)
-    *ep = 0;
-
-  pprsz_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_PAPERSIZE_FILE",
-                                               "papersize"),
-                            fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
-                                               config_path));
+  size_t i;
 
-  ds_init_empty (&line);
+  for (i = 0; i < sizeof sizes / sizeof *sizes; i++)
+    if (ss_equals_case (ss_cstr (sizes[i][0]), name))
+      {
+        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;
+}
 
-  if (pprsz_fn == NULL)
-    {
-      error (0, 0, _("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;
 
-  f = fopen (pprsz_fn, "r");
-  if (!f)
+  file = fopen (file_name, "r");
+  if (file == NULL)
     {
-      error (0, errno, _("error opening \"%s\""), pprsz_fn);
-      goto exit;
+      error (0, errno, _("error opening \"%s\""), file_name);
+      return false;
     }
 
   for (;;)
     {
-      struct substring p, name;
+      struct substring name;
 
-      if (!ds_read_config_line (&line, &line_number, f))
+      if (!ds_read_config_line (&line, &line_number, file))
        {
-         if (ferror (f))
-           error (0, errno, _("error reading \"%s\""), pprsz_fn);
+         if (ferror (file))
+           error (0, errno, _("error reading \"%s\""), file_name);
          break;
        }
 
-      p = ds_ss (&line);
-      ss_ltrim (&p, ss_cstr (CC_SPACES));
-      if (!ss_match_char (&p, '"') || !ss_get_until (&p, '"', &name))
-       goto lex_error;
-      if (ss_compare (name, ss_cstr (size)))
-       continue;
+      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;
+        }
+    }
 
-      ss_ltrim (&p, ss_cstr (CC_SPACES));
-      if (ss_match_char (&p, '='))
-       {
-          if (free_it)
-            free (size);
-          ss_trim (&p, ss_cstr (CC_SPACES));
-          size = ss_xstrdup (p);
-         free_it = true;
-         continue;
-       }
-      size = ss_data (p);
-      break;
+  fclose (file);
+  ds_destroy (&line);
+  error (0, 0, _("paper size file \"%s\" does not state a paper size"),
+         file_name);
+  return false;
+}
 
-    lex_error:
-      error_at_line (0, 0, pprsz_fn, line_number,
-                     _("syntax error in paper size definition"));
-    }
+/* 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;
+}
 
-  /* We found the one we want! */
-  result = internal_get_paper_size (size, h, v);
+/* 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;
 
-exit:
-  ds_destroy (&line);
-  if (free_it)
-    free (size);
+  s = ss_cstr (size);
+  ss_trim (&s, ss_cstr (CC_SPACES));
 
-  if (!result)
-    error (0, 0, _("error reading paper size definition file"));
+  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
+    {
+      /* Check against standard paper sizes. */
+      ok = get_standard_paper_size (s, h, v);
+    }
 
-  return result;
+  /* 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