Use "C" locale comparisons for language constructs.
[pspp] / src / output / measure.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "output/measure.h"
20
21 #include <gl/c-strtod.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #if HAVE_LC_PAPER
25 #include <langinfo.h>
26 #endif
27 #include <stdlib.h>
28
29 #include "data/file-name.h"
30 #include "libpspp/str.h"
31
32 #include "gl/c-strcase.h"
33 #include "gl/error.h"
34
35 #include "gettext.h"
36 #define _(msgid) gettext (msgid)
37
38 static double parse_unit (const char *);
39 static bool parse_paper_size (const char *, int *h, int *v);
40 static bool get_standard_paper_size (struct substring name, int *h, int *v);
41 static bool read_paper_conf (const char *file_name, int *h, int *v);
42 static bool get_default_paper_size (int *h, int *v);
43
44 /* Determines the size of a dimensional measurement and returns
45    the size in units of 1/72000".  Units are assumed to be
46    millimeters unless otherwise specified.  Returns -1 on
47    error. */
48 int
49 measure_dimension (const char *dimen)
50 {
51   double raw, factor;
52   char *tail;
53
54   /* Number. */
55   raw = c_strtod (dimen, &tail);
56   if (raw < 0.0)
57     goto syntax_error;
58
59   /* Unit. */
60   factor = parse_unit (tail);
61   if (factor == 0.0)
62     goto syntax_error;
63
64   return raw * factor;
65
66 syntax_error:
67   error (0, 0, _("`%s' is not a valid length."), dimen);
68   return -1;
69 }
70
71 /* Stores the dimensions, in 1/72000" units, of paper identified
72    by SIZE into *H and *V.  SIZE can be the name of a kind of
73    paper ("a4", "letter", ...) or a pair of dimensions
74    ("210x297", "8.5x11in", ...).  Returns true on success, false
75    on failure.  On failure, *H and *V are set for A4 paper. */
76 bool
77 measure_paper (const char *size, int *h, int *v)
78 {
79   struct substring s;
80   bool ok;
81
82   s = ss_cstr (size);
83   ss_trim (&s, ss_cstr (CC_SPACES));
84
85   if (ss_is_empty (s))
86     {
87       /* Treat empty string as default paper size. */
88       ok = get_default_paper_size (h, v);
89     }
90   else if (isdigit (ss_first (s)))
91     {
92       /* Treat string that starts with digit as explicit size. */
93       ok = parse_paper_size (size, h, v);
94       if (!ok)
95         error (0, 0, _("syntax error in paper size `%s'"), size);
96     }
97   else
98     {
99       /* Check against standard paper sizes. */
100       ok = get_standard_paper_size (s, h, v);
101     }
102
103   /* Default to A4 on error. */
104   if (!ok)
105     {
106       *h = 210 * (72000 / 25.4);
107       *v = 297 * (72000 / 25.4);
108     }
109   return ok;
110 }
111 \f
112 /* Parses UNIT as a dimensional unit.  Returns the multiplicative
113    factor needed to change a quantity measured in that unit into
114    1/72000" units.  If UNIT is empty, it is treated as
115    millimeters.  If the unit is unrecognized, returns 0. */
116 static double
117 parse_unit (const char *unit)
118 {
119   struct unit
120     {
121       char name[3];
122       double factor;
123     };
124
125   static const struct unit units[] =
126     {
127       {"pt", 72000 / 72},
128       {"pc", 72000 / 72 * 12.0},
129       {"in", 72000},
130       {"cm", 72000 / 2.54},
131       {"mm", 72000 / 25.4},
132       {"", 72000 / 25.4},
133     };
134
135   const struct unit *p;
136
137   unit += strspn (unit, CC_SPACES);
138   for (p = units; p < units + sizeof units / sizeof *units; p++)
139     if (!c_strcasecmp (unit, p->name))
140       return p->factor;
141   return 0.0;
142 }
143
144 /* Stores the dimensions in 1/72000" units of paper identified by
145    SIZE, which is of form `HORZ x VERT [UNIT]' where HORZ and
146    VERT are numbers and UNIT is an optional unit of measurement,
147    into *H and *V.  Return true on success. */
148 static bool
149 parse_paper_size (const char *size, int *h, int *v)
150 {
151   double raw_h, raw_v, factor;
152   char *tail;
153
154   /* Width. */
155   raw_h = c_strtod (size, &tail);
156   if (raw_h <= 0.0)
157     return false;
158
159   /* Delimiter. */
160   tail += strspn (tail, CC_SPACES "x,");
161
162   /* Length. */
163   raw_v = c_strtod (tail, &tail);
164   if (raw_v <= 0.0)
165     return false;
166
167   /* Unit. */
168   factor = parse_unit (tail);
169   if (factor == 0.0)
170     return false;
171
172   *h = raw_h * factor + .5;
173   *v = raw_v * factor + .5;
174   return true;
175 }
176
177 static bool
178 get_standard_paper_size (struct substring name, int *h, int *v)
179 {
180   static const char *sizes[][2] =
181     {
182       {"a0", "841 x 1189 mm"},
183       {"a1", "594 x 841 mm"},
184       {"a2", "420 x 594 mm"},
185       {"a3", "297 x 420 mm"},
186       {"a4", "210 x 297 mm"},
187       {"a5", "148 x 210 mm"},
188       {"b5", "176 x 250 mm"},
189       {"a6", "105 x 148 mm"},
190       {"a7", "74 x 105 mm"},
191       {"a8", "52 x 74 mm"},
192       {"a9", "37 x 52 mm"},
193       {"a10", "26 x 37 mm"},
194       {"b0", "1000 x 1414 mm"},
195       {"b1", "707 x 1000 mm"},
196       {"b2", "500 x 707 mm"},
197       {"b3", "353 x 500 mm"},
198       {"b4", "250 x 353 mm"},
199       {"letter", "612 x 792 pt"},
200       {"legal", "612 x 1008 pt"},
201       {"executive", "522 x 756 pt"},
202       {"note", "612 x 792 pt"},
203       {"11x17", "792 x 1224 pt"},
204       {"tabloid", "792 x 1224 pt"},
205       {"statement", "396 x 612 pt"},
206       {"halfletter", "396 x 612 pt"},
207       {"halfexecutive", "378 x 522 pt"},
208       {"folio", "612 x 936 pt"},
209       {"quarto", "610 x 780 pt"},
210       {"ledger", "1224 x 792 pt"},
211       {"archA", "648 x 864 pt"},
212       {"archB", "864 x 1296 pt"},
213       {"archC", "1296 x 1728 pt"},
214       {"archD", "1728 x 2592 pt"},
215       {"archE", "2592 x 3456 pt"},
216       {"flsa", "612 x 936 pt"},
217       {"flse", "612 x 936 pt"},
218       {"csheet", "1224 x 1584 pt"},
219       {"dsheet", "1584 x 2448 pt"},
220       {"esheet", "2448 x 3168 pt"},
221     };
222
223   size_t i;
224
225   for (i = 0; i < sizeof sizes / sizeof *sizes; i++)
226     if (ss_equals_case (ss_cstr (sizes[i][0]), name))
227       {
228         bool ok = parse_paper_size (sizes[i][1], h, v);
229         assert (ok);
230         return ok;
231       }
232   error (0, 0, _("unknown paper type `%.*s'"),
233          (int) ss_length (name), ss_data (name));
234   return false;
235 }
236
237 /* Reads file FILE_NAME to find a paper size.  Stores the
238    dimensions, in 1/72000" units, into *H and *V.  Returns true
239    on success, false on failure. */
240 static bool
241 read_paper_conf (const char *file_name, int *h, int *v)
242 {
243   struct string line = DS_EMPTY_INITIALIZER;
244   int line_number = 0;
245   FILE *file;
246
247   file = fopen (file_name, "r");
248   if (file == NULL)
249     {
250       error (0, errno, _("error opening input file `%s'"), file_name);
251       return false;
252     }
253
254   for (;;)
255     {
256       struct substring name;
257
258       if (!ds_read_config_line (&line, &line_number, file))
259         {
260           if (ferror (file))
261             error (0, errno, _("error reading file `%s'"), file_name);
262           break;
263         }
264
265       name = ds_ss (&line);
266       ss_trim (&name, ss_cstr (CC_SPACES));
267       if (!ss_is_empty (name))
268         {
269           bool ok = get_standard_paper_size (name, h, v);
270           fclose (file);
271           ds_destroy (&line);
272           return ok;
273         }
274     }
275
276   fclose (file);
277   ds_destroy (&line);
278   error (0, 0, _("paper size file `%s' does not state a paper size"),
279          file_name);
280   return false;
281 }
282
283 /* The user didn't specify a paper size, so let's choose a
284    default based on his environment.  Stores the
285    dimensions, in 1/72000" units, into *H and *V.  Returns true
286    on success, false on failure. */
287 static bool
288 get_default_paper_size (int *h, int *v)
289 {
290   /* libpaper in Debian (and other distributions?) allows the
291      paper size to be specified in $PAPERSIZE or in a file
292      specified in $PAPERCONF. */
293   if (getenv ("PAPERSIZE") != NULL)
294     return get_standard_paper_size (ss_cstr (getenv ("PAPERSIZE")), h, v);
295   if (getenv ("PAPERCONF") != NULL)
296     return read_paper_conf (getenv ("PAPERCONF"), h, v);
297
298 #if HAVE_LC_PAPER
299   /* LC_PAPER is a non-standard glibc extension. */
300   *h = (int) nl_langinfo(_NL_PAPER_WIDTH) * (72000 / 25.4);
301   *v = (int) nl_langinfo(_NL_PAPER_HEIGHT) * (72000 / 25.4);
302   if (*h > 0 && *v > 0)
303      return true;
304 #endif
305
306   /* libpaper defaults to /etc/papersize. */
307   if (fn_exists ("/etc/papersize"))
308     return read_paper_conf ("/etc/papersize", h, v);
309
310   /* Can't find a default. */
311   return false;
312 }
313