Remove "Written by Ben Pfaff <blp@gnu.org>" lines everywhere.
[pspp-builds.git] / src / output / ascii.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    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, write to the Free Software
16    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17    02110-1301, USA. */
18
19 #include <config.h>
20
21 #include <ctype.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdlib.h>
25
26 #include <data/file-name.h>
27 #include <libpspp/alloc.h>
28 #include <libpspp/assertion.h>
29 #include <libpspp/compiler.h>
30 #include <libpspp/pool.h>
31 #include <libpspp/start-date.h>
32 #include <libpspp/version.h>
33
34 #include "chart.h"
35 #include "error.h"
36 #include "minmax.h"
37 #include "output.h"
38
39 #include "gettext.h"
40 #define _(msgid) gettext (msgid)
41
42 /* ASCII driver options: (defaults listed first)
43
44    output-file="pspp.list"
45    paginate=on|off              Formfeeds are desired?
46    tab-width=8                  Width of a tab; 0 to not use tabs.
47    
48    headers=on|off               Put headers at top of page?
49    emphasis=bold|underline|none Style to use for emphasis.
50    length=66
51    width=130
52    squeeze=off|on               Squeeze multiple newlines into exactly one.
53
54    top-margin=2
55    bottom-margin=2
56
57    box[x]="strng"               Sets box character X (X in base 4: 0-3333).
58  */
59
60 /* Disable messages by failed range checks. */
61 /*#define SUPPRESS_WARNINGS 1 */
62
63 /* Line styles bit shifts. */
64 enum
65   {
66     LNS_TOP = 0,
67     LNS_LEFT = 2,
68     LNS_BOTTOM = 4,
69     LNS_RIGHT = 6,
70
71     LNS_COUNT = 256
72   };
73
74 /* Character attributes. */
75 #define ATTR_EMPHASIS   0x100   /* Bold-face. */
76 #define ATTR_BOX        0x200   /* Line drawing character. */
77
78 /* A line of text. */
79 struct line 
80   {
81     unsigned short *chars;      /* Characters and attributes. */
82     int char_cnt;               /* Length. */
83     int char_cap;               /* Allocated bytes. */
84   };
85
86 /* How to emphasize text. */
87 enum emphasis_style 
88   {
89     EMPH_BOLD,                  /* Overstrike for bold. */
90     EMPH_UNDERLINE,             /* Overstrike for underlining. */
91     EMPH_NONE                   /* No emphasis. */
92   };
93
94 /* ASCII output driver extension record. */
95 struct ascii_driver_ext
96   {
97     struct pool *pool;
98
99     /* User parameters. */
100     bool headers;               /* Print headers at top of page? */
101     bool paginate;              /* Insert formfeeds? */
102     bool squeeze_blank_lines;   /* Squeeze multiple blank lines into one? */
103     enum emphasis_style emphasis; /* How to emphasize text. */
104     int tab_width;              /* Width of a tab; 0 not to use tabs. */
105
106     int page_length;            /* Page length before subtracting margins. */
107     int top_margin;             /* Top margin in lines. */
108     int bottom_margin;          /* Bottom margin in lines. */
109
110     char *box[LNS_COUNT];       /* Line & box drawing characters. */
111
112     /* Internal state. */
113     char *file_name;            /* Output file name. */
114     FILE *file;                 /* Output file. */
115     int page_number;            /* Current page number. */
116     struct line *lines;         /* Page content. */
117     int line_cap;               /* Number of lines allocated. */
118   };
119
120 static int get_default_box_char (size_t idx);
121 static bool handle_option (struct outp_driver *this, const char *key,
122                            const struct string *val);
123
124 static bool
125 ascii_open_driver (struct outp_driver *this, struct substring options)
126 {
127   struct ascii_driver_ext *x;
128   int i;
129
130   this->width = 79;
131   this->font_height = 1;
132   this->prop_em_width = 1;
133   this->fixed_width = 1;
134   for (i = 0; i < OUTP_L_COUNT; i++)
135     this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
136
137   this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
138   x->headers = true;
139   x->paginate = true;
140   x->squeeze_blank_lines = false;
141   x->emphasis = EMPH_BOLD;
142   x->tab_width = 8;
143   x->page_length = 66;
144   x->top_margin = 2;
145   x->bottom_margin = 2;
146   for (i = 0; i < LNS_COUNT; i++)
147     x->box[i] = NULL;
148   x->file_name = pool_strdup (x->pool, "pspp.list");
149   x->file = NULL;
150   x->page_number = 0;
151   x->lines = NULL;
152   x->line_cap = 0;
153
154   if (!outp_parse_options (options, handle_option, this))
155     goto error;
156
157   x->file = pool_fopen (x->pool, x->file_name, "w");
158   if (x->file == NULL)
159     {
160       error (0, errno, _("ascii: opening output file \"%s\""), x->file_name);
161       goto error;
162     }
163
164   this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
165   if (x->headers)
166     this->length -= 3;
167   
168   if (this->width < 59 || this->length < 15)
169     {
170       error (0, 0,
171              _("ascii: page excluding margins and headers "
172                "must be at least 59 characters wide by 15 lines long, but as "
173                "configured is only %d characters by %d lines"),
174              this->width, this->length);
175       return false;
176     }
177
178   for (i = 0; i < LNS_COUNT; i++)
179     if (x->box[i] == NULL) 
180       {
181         char s[2];
182         s[0] = get_default_box_char (i);
183         s[1] = '\0';
184         x->box[i] = pool_strdup (x->pool, s);
185       }
186   
187   return true;
188
189  error:
190   pool_destroy (x->pool);
191   return false;
192 }
193
194 static int
195 get_default_box_char (size_t idx)
196 {
197   /* Disassemble IDX into components. */
198   unsigned top = (idx >> LNS_TOP) & 3;
199   unsigned left = (idx >> LNS_LEFT) & 3;
200   unsigned bottom = (idx >> LNS_BOTTOM) & 3;
201   unsigned right = (idx >> LNS_RIGHT) & 3;
202
203   /* Reassemble components into nibbles in the order TLBR.
204      This makes it easy to read the case labels. */
205   unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
206   switch (value)
207     {
208     case 0x0000:
209       return ' ';
210
211     case 0x0100: case 0x0101: case 0x0001:
212       return '-';
213
214     case 0x1000: case 0x1010: case 0x0010:
215       return '|';
216
217     case 0x0300: case 0x0303: case 0x0003:
218     case 0x0200: case 0x0202: case 0x0002:
219       return '=';
220
221     default:
222       return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
223     }
224 }
225
226 static bool
227 ascii_close_driver (struct outp_driver *this)
228 {
229   struct ascii_driver_ext *x = this->ext;
230   
231   if (fn_close (x->file_name, x->file) != 0)
232     error (0, errno, _("ascii: closing output file \"%s\""), x->file_name);
233   pool_detach_file (x->pool, x->file);
234   pool_destroy (x->pool);
235   
236   return true;
237 }
238
239 /* Generic option types. */
240 enum
241   {
242     boolean_arg,
243     string_arg,
244     nonneg_int_arg,
245     pos_int_arg,
246     output_file_arg
247   };
248
249 static const struct outp_option option_tab[] =
250   {
251     {"headers", boolean_arg, 0},
252     {"paginate", boolean_arg, 1},
253     {"squeeze", boolean_arg, 2},
254
255     {"emphasis", string_arg, 3},
256
257     {"output-file", output_file_arg, 0},
258
259     {"length", pos_int_arg, 0},
260     {"width", pos_int_arg, 1},
261
262     {"top-margin", nonneg_int_arg, 0},
263     {"bottom-margin", nonneg_int_arg, 1},
264     {"tab-width", nonneg_int_arg, 2},
265
266     {NULL, 0, 0},
267   };
268
269 static bool
270 handle_option (struct outp_driver *this, const char *key,
271                const struct string *val)
272 {
273   struct ascii_driver_ext *x = this->ext;
274   int subcat;
275   const char *value;
276
277   value = ds_cstr (val);
278   if (!strncmp (key, "box[", 4))
279     {
280       char *tail;
281       int indx = strtol (&key[4], &tail, 4);
282       if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
283         {
284           error (0, 0, _("ascii: bad index value for `box' key: syntax "
285                          "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
286                          "expressed in base 4"),
287                  LNS_COUNT);
288           return false;
289         }
290       if (x->box[indx] != NULL)
291         error (0, 0, _("ascii: multiple values for %s"), key);
292       x->box[indx] = pool_strdup (x->pool, value);
293       return true;
294     }
295
296   switch (outp_match_keyword (key, option_tab, &subcat))
297     {
298     case -1:
299       error (0, 0, _("ascii: unknown parameter `%s'"), key);
300       break;
301     case output_file_arg:
302       x->file_name = pool_strdup (x->pool, value);
303       break;
304     case pos_int_arg:
305       {
306         char *tail;
307         int arg;
308
309         errno = 0;
310         arg = strtol (value, &tail, 0);
311         if (arg < 1 || errno == ERANGE || *tail)
312           {
313             error (0, 0, _("ascii: positive integer required as `%s' value"),
314                    key);
315             break;
316           }
317         switch (subcat)
318           {
319           case 0:
320             x->page_length = arg;
321             break;
322           case 1:
323             this->width = arg;
324             break;
325           default:
326             NOT_REACHED ();
327           }
328       }
329       break;
330     case string_arg:
331       if (!strcmp (value, "bold"))
332         x->emphasis = EMPH_BOLD;
333       else if (!strcmp (value, "underline"))
334         x->emphasis = EMPH_UNDERLINE;
335       else if (!strcmp (value, "none"))
336         x->emphasis = EMPH_NONE;
337       else 
338         error (0, 0,
339                _("ascii: `emphasis' value must be `bold', "
340                  "`underline', or `none'"));
341       break;
342     case nonneg_int_arg:
343       {
344         char *tail;
345         int arg;
346
347         errno = 0;
348         arg = strtol (value, &tail, 0);
349         if (arg < 0 || errno == ERANGE || *tail)
350           {
351             error (0, 0,
352                    _("ascii: zero or positive integer required as `%s' value"),
353                    key);
354             break;
355           }
356         switch (subcat)
357           {
358           case 0:
359             x->top_margin = arg;
360             break;
361           case 1:
362             x->bottom_margin = arg;
363             break;
364           case 2:
365             x->tab_width = arg;
366             break;
367           default:
368             NOT_REACHED ();
369           }
370       }
371       break;
372     case boolean_arg:
373       {
374         bool setting;
375         if (!strcmp (value, "on") || !strcmp (value, "true")
376             || !strcmp (value, "yes") || atoi (value))
377           setting = true;
378         else if (!strcmp (value, "off") || !strcmp (value, "false")
379                  || !strcmp (value, "no") || !strcmp (value, "0"))
380           setting = false;
381         else
382           {
383             error (0, 0, _("ascii: boolean value expected for `%s'"), key);
384             return false;
385           }
386         switch (subcat)
387           {
388           case 0:
389             x->headers = setting;
390             break;
391           case 1:
392             x->paginate = setting;
393             break;
394           case 2:
395             x->squeeze_blank_lines = setting;
396             break;
397           default:
398             NOT_REACHED ();
399           }
400       }
401       break;
402     default:
403       NOT_REACHED ();
404     }
405
406   return true;
407 }
408
409 static void
410 ascii_open_page (struct outp_driver *this)
411 {
412   struct ascii_driver_ext *x = this->ext;
413   int i;
414
415   x->page_number++;
416
417   if (this->length > x->line_cap)
418     {
419       x->lines = pool_nrealloc (x->pool,
420                                 x->lines, this->length, sizeof *x->lines);
421       for (i = x->line_cap; i < this->length; i++) 
422         {
423           struct line *line = &x->lines[i];
424           line->chars = NULL;
425           line->char_cap = 0;
426         }
427       x->line_cap = this->length;
428     }
429
430   for (i = 0; i < this->length; i++)
431     x->lines[i].char_cnt = 0;
432 }
433
434 /* Ensures that at least the first LENGTH characters of line Y in
435    THIS driver identified X have been cleared out. */
436 static inline void
437 expand_line (struct outp_driver *this, int y, int length)
438 {
439   struct ascii_driver_ext *ext = this->ext;
440   struct line *line = &ext->lines[y];
441   if (line->char_cnt < length) 
442     {
443       int x;
444       if (line->char_cap < length) 
445         {
446           line->char_cap = MIN (length * 2, this->width);
447           line->chars = pool_nrealloc (ext->pool,
448                                        line->chars,
449                                        line->char_cap, sizeof *line->chars); 
450         }
451       for (x = line->char_cnt; x < length; x++)
452         line->chars[x] = ' ';
453       line->char_cnt = length; 
454     }
455 }
456
457 static void
458 ascii_line (struct outp_driver *this, 
459             int x0, int y0, int x1, int y1,
460             enum outp_line_style top, enum outp_line_style left,
461             enum outp_line_style bottom, enum outp_line_style right)
462 {
463   struct ascii_driver_ext *ext = this->ext;
464   int y;
465   unsigned short value;
466
467   assert (this->page_open);
468 #if DEBUGGING
469   if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
470     {
471 #if !SUPPRESS_WARNINGS
472       printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
473               x0, y0, x1, y1, this->width, this->length);
474 #endif
475       return;
476     }
477 #endif
478
479   value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
480            | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
481   for (y = y0; y < y1; y++) 
482     {
483       int x;
484
485       expand_line (this, y, x1);
486       for (x = x0; x < x1; x++)
487         ext->lines[y].chars[x] = value; 
488     }
489 }
490
491 static void
492 text_draw (struct outp_driver *this,
493            enum outp_font font,
494            int x, int y,
495            enum outp_justification justification, int width,
496            const char *string, size_t length)
497 {
498   struct ascii_driver_ext *ext = this->ext;
499   unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
500
501   int line_len;
502
503   switch (justification)
504     {
505     case OUTP_LEFT:
506       break;
507     case OUTP_CENTER:
508       x += (width - length + 1) / 2;
509       break;
510     case OUTP_RIGHT:
511       x += width - length;
512       break;
513     default:
514       NOT_REACHED ();
515     }
516
517   if (y >= this->length || x >= this->width)
518     return;
519
520   if (x + length > this->width)
521     length = this->width - x;
522
523   line_len = x + length;
524
525   expand_line (this, y, line_len);
526   while (length-- > 0)
527     ext->lines[y].chars[x++] = *string++ | attr;
528 }
529
530 /* Divides the text T->S into lines of width T->H.  Sets T->V to the
531    number of lines necessary.  Actually draws the text if DRAW is
532    true. */
533 static void
534 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
535            int *width, int *height)
536 {
537   int max_width;
538   int height_left;
539
540   const char *cp = ss_data (text->string);
541
542   max_width = 0;
543   height_left = text->v;
544
545   while (height_left > 0)
546     {
547       size_t chars_left;
548       size_t line_len;
549       const char *end;
550
551       /* Initially the line is up to text->h characters long. */
552       chars_left = ss_end (text->string) - cp;
553       if (chars_left == 0)
554         break;
555       line_len = MIN (chars_left, text->h);
556
557       /* A new-line terminates the line prematurely. */
558       end = memchr (cp, '\n', line_len);
559       if (end != NULL)
560         line_len = end - cp;
561
562       /* Don't cut off words if it can be avoided. */
563       if (cp + line_len < ss_end (text->string)) 
564         {
565           size_t space_len = line_len;
566           while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
567             space_len--;
568           if (space_len > 0)
569             line_len = space_len;
570         }
571       
572       /* Draw text. */
573       if (draw)
574         text_draw (this,
575                    text->font,
576                    text->x, text->y + (text->v - height_left),
577                    text->justification, text->h,
578                    cp, line_len);
579
580       /* Update. */
581       height_left--;
582       if (line_len > max_width)
583         max_width = line_len;
584
585       /* Next line. */
586       cp += line_len;
587       if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
588         cp++;
589     }
590
591   if (width != NULL)
592     *width = max_width;
593   if (height != NULL)
594     *height = text->v - height_left;
595 }
596
597 static void
598 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
599                     int *width, int *height)
600 {
601   delineate (this, t, false, width, height);
602 }
603
604 static void
605 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
606 {
607   assert (this->page_open);
608   delineate (this, t, true, NULL, NULL);
609 }
610
611 \f
612 /* ascii_close_page () and support routines. */
613
614 /* Writes the LENGTH characters in S to OUT.  */
615 static void
616 output_line (struct outp_driver *this, const struct line *line,
617              struct string *out)
618 {
619   struct ascii_driver_ext *ext = this->ext;
620   const unsigned short *s = line->chars;
621   size_t length;
622
623   for (length = line->char_cnt; length-- > 0; s++)
624     if (*s & ATTR_BOX)
625       ds_put_cstr (out, ext->box[*s & 0xff]);
626     else
627       {
628         if (*s & ATTR_EMPHASIS) 
629           {
630             if (ext->emphasis == EMPH_BOLD)
631               {
632                 ds_put_char (out, *s);
633                 ds_put_char (out, '\b'); 
634               }
635             else if (ext->emphasis == EMPH_UNDERLINE)
636               ds_put_cstr (out, "_\b"); 
637           }
638         ds_put_char (out, *s);
639       }
640 }
641
642 static void
643 append_lr_justified (struct string *out, int width,
644                      const char *left, const char *right)
645 {
646   ds_put_char_multiple (out, ' ', width);
647   if (left != NULL) 
648     {
649       size_t length = MIN (strlen (left), width);
650       memcpy (ds_end (out) - width, left, length); 
651     }
652   if (right != NULL)
653     {
654       size_t length = MIN (strlen (right), width);
655       memcpy (ds_end (out) - length, right, length);
656     }
657   ds_put_char (out, '\n');
658 }
659
660 static void
661 dump_output (struct outp_driver *this, struct string *out) 
662 {
663   struct ascii_driver_ext *x = this->ext;
664   fwrite (ds_data (out), ds_length (out), 1, x->file);
665   ds_clear (out);
666 }
667
668 static void
669 ascii_close_page (struct outp_driver *this)
670 {
671   struct ascii_driver_ext *x = this->ext;
672   struct string out;
673   int line_num;
674  
675   ds_init_empty (&out);
676  
677   ds_put_char_multiple (&out, '\n', x->top_margin);
678   if (x->headers)
679     {
680       char *r1, *r2;
681  
682       r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
683       r2 = xasprintf ("%s - %s" , version, host_system);
684  
685       append_lr_justified (&out, this->width, outp_title, r1);
686       append_lr_justified (&out, this->width, outp_subtitle, r2);
687       ds_put_char (&out, '\n');
688  
689       free (r1);
690       free (r2);
691     }
692   dump_output (this, &out);
693  
694   for (line_num = 0; line_num < this->length; line_num++)
695     {
696  
697       /* Squeeze multiple blank lines into a single blank line if
698          requested. */
699       if (x->squeeze_blank_lines) 
700         {
701           if (line_num >= x->line_cap)
702             break;
703           if (line_num > 0
704               && x->lines[line_num].char_cnt == 0
705               && x->lines[line_num - 1].char_cnt == 0)
706             continue; 
707         }
708  
709       if (line_num < x->line_cap) 
710         output_line (this, &x->lines[line_num], &out); 
711       ds_put_char (&out, '\n');
712       dump_output (this, &out);
713     }
714  
715   ds_put_char_multiple (&out, '\n', x->bottom_margin);
716   if (x->paginate) 
717     ds_put_char (&out, '\f');
718
719   dump_output (this, &out);
720   ds_destroy (&out);
721 }
722
723 static void
724 ascii_chart_initialise (struct outp_driver *d UNUSED, struct chart *ch)
725 {
726   error (0, 0, _("ascii: charts are unsupported by this driver"));
727   ch->lp = 0;
728 }
729
730 static void 
731 ascii_chart_finalise (struct outp_driver *d UNUSED, struct chart *ch UNUSED)
732 {
733   
734 }
735
736 const struct outp_class ascii_class =
737 {
738   "ascii",
739   0,
740
741   ascii_open_driver,
742   ascii_close_driver,
743
744   ascii_open_page,
745   ascii_close_page,
746
747   NULL,
748
749   ascii_line,
750   ascii_text_metrics,
751   ascii_text_draw,
752
753   ascii_chart_initialise,
754   ascii_chart_finalise
755 };