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