Fully implement arbitrary delimiters on DATA LIST, extending the half
[pspp-builds.git] / src / ascii.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include "error.h"
22 #include <ctype.h>
23 #include <errno.h>
24 #include <limits.h>
25 #include <stdlib.h>
26 #include "alloc.h"
27 #include "error.h"
28 #include "filename.h"
29 #include "main.h"
30 #include "misc.h"
31 #include "output.h"
32 #include "pool.h"
33 #include "version.h"
34
35 /* ASCII driver options: (defaults listed first)
36
37    output-file="pspp.list"
38    char-set=ascii|latin1
39    form-feed-string="\f"        Written as a formfeed.
40    newline-string=default|"\r\n"|"\n"   
41                                 Written as a newline.
42    paginate=on|off              Formfeeds are desired?
43    tab-width=8                  Width of a tab; 0 to not use tabs.
44    init=""                      Written at beginning of output.
45    done=""                      Written at end of output.
46    
47    headers=on|off               Put headers at top of page?
48    length=66
49    width=130
50    lpi=6                        Only used to determine font size.
51    cpi=10                       
52    squeeze=off|on               Squeeze multiple newlines into exactly one.
53
54    left-margin=0
55    right-margin=0
56    top-margin=2
57    bottom-margin=2
58
59    box[x]="strng"               Sets box character X (X in base 4: 0-3333).
60    italic-on=overstrike|"strng" Turns on italic (underline).
61    italic-off=""|"strng"        Turns off italic; ignored for overstrike.
62    bold-on=overstrike|"strng"   Turns on bold.
63    bold-off=""|"strng"          Turns off bold; ignored for overstrike.
64    bold-italic-on=overstrike|"strng" Turns on bold-italic.
65    bold-italic-off=""|"strng"   Turns off bold-italic; ignored for overstrike.
66    overstrike-style=single|line Can we print a whole line then BS over it, or
67    must we go char by char, as on a terminal?
68    carriage-return-style=bs|cr  Must we return the carriage with a sequence of
69    BSes, or will a single CR do it?
70  */
71
72 /* Disable messages by failed range checks. */
73 /*#define SUPPRESS_WARNINGS 1 */
74
75 /* Character set. */
76 enum
77   {
78     CHS_ASCII,                  /* 7-bit ASCII */
79     CHS_LATIN1                  /* Latin 1; not really supported at the moment */
80   };
81
82 /* Overstrike style. */
83 enum
84   {
85     OVS_SINGLE,                 /* Overstrike each character: "a\b_b\b_c\b_" */
86     OVS_LINE                    /* Overstrike lines: "abc\b\b\b___" (or if
87                                    newline is "\r\n", then "abc\r___").  Easier
88                                    on the printer, doesn't work on a tty. */
89   };
90
91 /* Basic output strings. */
92 enum
93   {
94     OPS_INIT,                   /* Document initialization string. */
95     OPS_DONE,                   /* Document uninit string. */
96     OPS_FORMFEED,               /* Formfeed string. */
97     OPS_NEWLINE,                /* Newline string. */
98
99     OPS_COUNT                   /* Number of output strings. */
100   };
101
102 /* Line styles bit shifts. */
103 enum
104   {
105     LNS_TOP = 0,
106     LNS_LEFT = 2,
107     LNS_BOTTOM = 4,
108     LNS_RIGHT = 6,
109
110     LNS_COUNT = 256
111   };
112
113 /* Carriage return style. */
114 enum
115   {
116     CRS_BS,                     /* Multiple backspaces. */
117     CRS_CR                      /* Single carriage return. */
118   };
119
120 /* Assembles a byte from four taystes. */
121 #define TAYSTE2BYTE(T, L, B, R)                 \
122         (((T) << LNS_TOP)                       \
123          | ((L) << LNS_LEFT)                    \
124          | ((B) << LNS_BOTTOM)                  \
125          | ((R) << LNS_RIGHT))
126
127 /* Extract tayste with shift value S from byte B. */
128 #define BYTE2TAYSTE(B, S)                       \
129         (((B) >> (S)) & 3)
130
131 /* Font style; take one of the first group |'d with one of the second group. */
132 enum
133   {
134     FSTY_ON = 000,              /* Turn font on. */
135     FSTY_OFF = 001,             /* Turn font off. */
136
137     FSTY_ITALIC = 0,            /* Italic font. */
138     FSTY_BOLD = 2,              /* Bold font. */
139     FSTY_BOLD_ITALIC = 4,       /* Bold-italic font. */
140
141     FSTY_COUNT = 6              /* Number of font styles. */
142   };
143
144 /* A line of text. */
145 struct line 
146   {
147     unsigned short *chars;      /* Characters and attributes. */
148     int char_cnt;               /* Length. */
149     int char_cap;               /* Allocated bytes. */
150   };
151
152 /* ASCII output driver extension record. */
153 struct ascii_driver_ext
154   {
155     /* User parameters. */
156     int char_set;               /* CHS_ASCII/CHS_LATIN1; no-op right now. */
157     int headers;                /* 1=print headers at top of page. */
158     int page_length;            /* Page length in lines. */
159     int page_width;             /* Page width in characters. */
160     int lpi;                    /* Lines per inch. */
161     int cpi;                    /* Characters per inch. */
162     int left_margin;            /* Left margin in characters. */
163     int right_margin;           /* Right margin in characters. */
164     int top_margin;             /* Top margin in lines. */
165     int bottom_margin;          /* Bottom margin in lines. */
166     int paginate;               /* 1=insert formfeeds. */
167     int tab_width;              /* Width of a tab; 0 not to use tabs. */
168     struct len_string ops[OPS_COUNT]; /* Basic output strings. */
169     struct len_string box[LNS_COUNT]; /* Line & box drawing characters. */
170     struct len_string fonts[FSTY_COUNT]; /* Font styles; NULL=overstrike. */
171     int overstrike_style;       /* OVS_SINGLE or OVS_LINE. */
172     int carriage_return_style;  /* Carriage return style. */
173     int squeeze_blank_lines;    /* 1=squeeze multiple blank lines into one. */
174
175     /* Internal state. */
176     struct file_ext file;       /* Output file. */
177     int page_number;            /* Current page number. */
178     struct line *lines;         /* Page content. */
179     int lines_cap;              /* Number of lines allocated. */
180     int w, l;                   /* Actual width & length w/o margins, etc. */
181     int cur_font;               /* Current font by OUTP_F_*. */
182 #if GLOBAL_DEBUGGING
183     int debug;                  /* Set by som_text_draw(). */
184 #endif
185   };
186
187 static int postopen (struct file_ext *);
188 static int preclose (struct file_ext *);
189
190 static int
191 ascii_open_global (struct outp_class *this UNUSED)
192 {
193   return 1;
194 }
195
196
197 static int
198 ascii_close_global (struct outp_class *this UNUSED)
199 {
200   return 1;
201 }
202
203 static int *
204 ascii_font_sizes (struct outp_class *this UNUSED, int *n_valid_sizes)
205 {
206   static int valid_sizes[] = {12, 12, 0, 0};
207
208   assert (n_valid_sizes);
209   *n_valid_sizes = 1;
210   return valid_sizes;
211 }
212
213 static int
214 ascii_preopen_driver (struct outp_driver *this)
215 {
216   struct ascii_driver_ext *x;
217   int i;
218   
219   assert (this->driver_open == 0);
220   msg (VM (1), _("ASCII driver initializing as `%s'..."), this->name);
221   this->ext = x = xmalloc (sizeof (struct ascii_driver_ext));
222   x->char_set = CHS_ASCII;
223   x->headers = 1;
224   x->page_length = 66;
225   x->page_width = 79;
226   x->lpi = 6;
227   x->cpi = 10;
228   x->left_margin = 0;
229   x->right_margin = 0;
230   x->top_margin = 2;
231   x->bottom_margin = 2;
232   x->paginate = 1;
233   x->tab_width = 8;
234   for (i = 0; i < OPS_COUNT; i++)
235     ls_null (&x->ops[i]);
236   for (i = 0; i < LNS_COUNT; i++)
237     ls_null (&x->box[i]);
238   for (i = 0; i < FSTY_COUNT; i++)
239     ls_null (&x->fonts[i]);
240   x->overstrike_style = OVS_SINGLE;
241   x->carriage_return_style = CRS_BS;
242   x->squeeze_blank_lines = 0;
243   x->file.filename = NULL;
244   x->file.mode = "wb";
245   x->file.file = NULL;
246   x->file.sequence_no = &x->page_number;
247   x->file.param = x;
248   x->file.postopen = postopen;
249   x->file.preclose = preclose;
250   x->page_number = 0;
251   x->lines = NULL;
252   x->lines_cap = 0;
253   x->cur_font = OUTP_F_R;
254 #if GLOBAL_DEBUGGING
255   x->debug = 0;
256 #endif
257   return 1;
258 }
259
260 static int
261 ascii_postopen_driver (struct outp_driver *this)
262 {
263   struct ascii_driver_ext *x = this->ext;
264   
265   assert (this->driver_open == 0);
266   
267   if (NULL == x->file.filename)
268     x->file.filename = xstrdup ("pspp.list");
269   
270   x->w = x->page_width - x->left_margin - x->right_margin;
271   x->l = (x->page_length - (x->headers ? 3 : 0) - x->top_margin
272           - x->bottom_margin - 1);
273   if (x->w < 59 || x->l < 15)
274     {
275       msg (SE, _("ascii driver: Area of page excluding margins and headers "
276                  "must be at least 59 characters wide by 15 lines long.  Page as "
277                  "configured is only %d characters by %d lines."), x->w, x->l);
278       return 0;
279     }
280   
281   this->res = x->lpi * x->cpi;
282   this->horiz = x->lpi;
283   this->vert = x->cpi;
284   this->width = x->w * this->horiz;
285   this->length = x->l * this->vert;
286   
287   if (ls_null_p (&x->ops[OPS_FORMFEED]))
288     ls_create (&x->ops[OPS_FORMFEED], "\f");
289   if (ls_null_p (&x->ops[OPS_NEWLINE])
290       || !strcmp (ls_c_str (&x->ops[OPS_NEWLINE]), "default"))
291     {
292       ls_create (&x->ops[OPS_NEWLINE], "\n");
293       x->file.mode = "wt";
294     }
295   
296   {
297     int i;
298     
299     for (i = 0; i < LNS_COUNT; i++)
300       {
301         char c[2];
302         c[1] = 0;
303         if (!ls_null_p (&x->box[i]))
304           continue;
305         switch (i)
306           {
307           case TAYSTE2BYTE (0, 0, 0, 0):
308             c[0] = ' ';
309             break;
310
311           case TAYSTE2BYTE (0, 1, 0, 0):
312           case TAYSTE2BYTE (0, 1, 0, 1):
313           case TAYSTE2BYTE (0, 0, 0, 1):
314             c[0] = '-';
315             break;
316
317           case TAYSTE2BYTE (1, 0, 0, 0):
318           case TAYSTE2BYTE (1, 0, 1, 0):
319           case TAYSTE2BYTE (0, 0, 1, 0):
320             c[0] = '|';
321             break;
322
323           case TAYSTE2BYTE (0, 3, 0, 0):
324           case TAYSTE2BYTE (0, 3, 0, 3):
325           case TAYSTE2BYTE (0, 0, 0, 3):
326           case TAYSTE2BYTE (0, 2, 0, 0):
327           case TAYSTE2BYTE (0, 2, 0, 2):
328           case TAYSTE2BYTE (0, 0, 0, 2):
329             c[0] = '=';
330             break;
331
332           case TAYSTE2BYTE (3, 0, 0, 0):
333           case TAYSTE2BYTE (3, 0, 3, 0):
334           case TAYSTE2BYTE (0, 0, 3, 0):
335           case TAYSTE2BYTE (2, 0, 0, 0):
336           case TAYSTE2BYTE (2, 0, 2, 0):
337           case TAYSTE2BYTE (0, 0, 2, 0):
338             c[0] = '#';
339             break;
340
341           default:
342             if (BYTE2TAYSTE (i, LNS_LEFT) > 1
343                 || BYTE2TAYSTE (i, LNS_TOP) > 1
344                 || BYTE2TAYSTE (i, LNS_RIGHT) > 1
345                 || BYTE2TAYSTE (i, LNS_BOTTOM) > 1)
346               c[0] = '#';
347             else
348               c[0] = '+';
349             break;
350           }
351         ls_create (&x->box[i], c);
352       }
353   }
354   
355   {
356     int i;
357     
358     this->cp_x = this->cp_y = 0;
359     this->font_height = this->vert;
360     this->prop_em_width = this->horiz;
361     this->fixed_width = this->horiz;
362
363     this->horiz_line_width[0] = 0;
364     this->vert_line_width[0] = 0;
365     
366     for (i = 1; i < OUTP_L_COUNT; i++)
367       {
368         this->horiz_line_width[i] = this->vert;
369         this->vert_line_width[i] = this->horiz;
370       }
371     
372     for (i = 0; i < (1 << OUTP_L_COUNT); i++)
373       {
374         this->horiz_line_spacing[i] = (i & ~1) ? this->vert : 0;
375         this->vert_line_spacing[i] = (i & ~1) ? this->horiz : 0;
376       }
377   }
378   
379   this->driver_open = 1;
380   msg (VM (2), _("%s: Initialization complete."), this->name);
381
382   return 1;
383 }
384
385 static int
386 ascii_close_driver (struct outp_driver *this)
387 {
388   struct ascii_driver_ext *x = this->ext;
389   int i;
390   
391   assert (this->driver_open == 1);
392   msg (VM (2), _("%s: Beginning closing..."), this->name);
393   
394   x = this->ext;
395   for (i = 0; i < OPS_COUNT; i++)
396     ls_destroy (&x->ops[i]);
397   for (i = 0; i < LNS_COUNT; i++)
398     ls_destroy (&x->box[i]);
399   for (i = 0; i < FSTY_COUNT; i++)
400     ls_destroy (&x->fonts[i]);
401   if (x->lines != NULL) 
402     {
403       int line;
404       
405       for (line = 0; line < x->lines_cap; line++) 
406         free (x->lines[line].chars);
407       free (x->lines); 
408     }
409   fn_close_ext (&x->file);
410   free (x->file.filename);
411   free (x);
412   
413   this->driver_open = 0;
414   msg (VM (3), _("%s: Finished closing."), this->name);
415   
416   return 1;
417 }
418
419 /* Generic option types. */
420 enum
421   {
422     pos_int_arg = -10,
423     nonneg_int_arg,
424     string_arg,
425     font_string_arg,
426     boolean_arg
427   };
428
429 static struct outp_option option_tab[] =
430   {
431     {"headers", boolean_arg, 0},
432     {"output-file", 1, 0},
433     {"char-set", 2, 0},
434     {"length", pos_int_arg, 0},
435     {"width", pos_int_arg, 1},
436     {"lpi", pos_int_arg, 2},
437     {"cpi", pos_int_arg, 3},
438     {"init", string_arg, 0},
439     {"done", string_arg, 1},
440     {"left-margin", nonneg_int_arg, 0},
441     {"right-margin", nonneg_int_arg, 1},
442     {"top-margin", nonneg_int_arg, 2},
443     {"bottom-margin", nonneg_int_arg, 3},
444     {"paginate", boolean_arg, 1},
445     {"form-feed-string", string_arg, 2},
446     {"newline-string", string_arg, 3},
447     {"italic-on", font_string_arg, 0},
448     {"italic-off", font_string_arg, 1},
449     {"bold-on", font_string_arg, 2},
450     {"bold-off", font_string_arg, 3},
451     {"bold-italic-on", font_string_arg, 4},
452     {"bold-italic-off", font_string_arg, 5},
453     {"overstrike-style", 3, 0},
454     {"tab-width", nonneg_int_arg, 4},
455     {"carriage-return-style", 4, 0},
456     {"squeeze", boolean_arg, 2},
457     {"", 0, 0},
458   };
459 static struct outp_option_info option_info;
460
461 static void
462 ascii_option (struct outp_driver *this, const char *key,
463               const struct string *val)
464 {
465   struct ascii_driver_ext *x = this->ext;
466   int cat, subcat;
467   const char *value;
468
469   value = ds_c_str (val);
470   if (!strncmp (key, "box[", 4))
471     {
472       char *tail;
473       int indx = strtol (&key[4], &tail, 4);
474       if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
475         {
476           msg (SE, _("Bad index value for `box' key: syntax is box[INDEX], "
477                "0 <= INDEX < %d decimal, with INDEX expressed in base 4."),
478                LNS_COUNT);
479           return;
480         }
481       if (!ls_null_p (&x->box[indx]))
482         msg (SW, _("Duplicate value for key `%s'."), key);
483       ls_create (&x->box[indx], value);
484       return;
485     }
486
487   cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
488   switch (cat)
489     {
490     case 0:
491       msg (SE, _("Unknown configuration parameter `%s' for ascii device driver."),
492            key);
493       break;
494     case 1:
495       free (x->file.filename);
496       x->file.filename = xstrdup (value);
497       break;
498     case 2:
499       if (!strcmp (value, "ascii"))
500         x->char_set = CHS_ASCII;
501       else if (!strcmp (value, "latin1"))
502         x->char_set = CHS_LATIN1;
503       else
504         msg (SE, _("Unknown character set `%s'.  Valid character sets are "
505              "`ascii' and `latin1'."), value);
506       break;
507     case 3:
508       if (!strcmp (value, "single"))
509         x->overstrike_style = OVS_SINGLE;
510       else if (!strcmp (value, "line"))
511         x->overstrike_style = OVS_LINE;
512       else
513         msg (SE, _("Unknown overstrike style `%s'.  Valid overstrike styles "
514              "are `single' and `line'."), value);
515       break;
516     case 4:
517       if (!strcmp (value, "bs"))
518         x->carriage_return_style = CRS_BS;
519       else if (!strcmp (value, "cr"))
520         x->carriage_return_style = CRS_CR;
521       else
522         msg (SE, _("Unknown carriage return style `%s'.  Valid carriage "
523              "return styles are `cr' and `bs'."), value);
524       break;
525     case pos_int_arg:
526       {
527         char *tail;
528         int arg;
529
530         errno = 0;
531         arg = strtol (value, &tail, 0);
532         if (arg < 1 || errno == ERANGE || *tail)
533           {
534             msg (SE, _("Positive integer required as value for `%s'."), key);
535             break;
536           }
537         switch (subcat)
538           {
539           case 0:
540             x->page_length = arg;
541             break;
542           case 1:
543             x->page_width = arg;
544             break;
545           case 2:
546             x->lpi = arg;
547             break;
548           case 3:
549             x->cpi = arg;
550             break;
551           default:
552             assert (0);
553           }
554       }
555       break;
556     case nonneg_int_arg:
557       {
558         char *tail;
559         int arg;
560
561         errno = 0;
562         arg = strtol (value, &tail, 0);
563         if (arg < 0 || errno == ERANGE || *tail)
564           {
565             msg (SE, _("Zero or positive integer required as value for `%s'."),
566                  key);
567             break;
568           }
569         switch (subcat)
570           {
571           case 0:
572             x->left_margin = arg;
573             break;
574           case 1:
575             x->right_margin = arg;
576             break;
577           case 2:
578             x->top_margin = arg;
579             break;
580           case 3:
581             x->bottom_margin = arg;
582             break;
583           case 4:
584             x->tab_width = arg;
585             break;
586           default:
587             assert (0);
588           }
589       }
590       break;
591     case string_arg:
592       {
593         struct len_string *s;
594         switch (subcat)
595           {
596           case 0:
597             s = &x->ops[OPS_INIT];
598             break;
599           case 1:
600             s = &x->ops[OPS_DONE];
601             break;
602           case 2:
603             s = &x->ops[OPS_FORMFEED];
604             break;
605           case 3:
606             s = &x->ops[OPS_NEWLINE];
607             break;
608           default:
609             assert (0);
610             abort ();
611           }
612         ls_create (s, value);
613       }
614       break;
615     case font_string_arg:
616       {
617         if (!strcmp (value, "overstrike"))
618           {
619             ls_destroy (&x->fonts[subcat]);
620             return;
621           }
622         ls_create (&x->fonts[subcat], value);
623       }
624       break;
625     case boolean_arg:
626       {
627         int setting;
628         if (!strcmp (value, "on") || !strcmp (value, "true")
629             || !strcmp (value, "yes") || atoi (value))
630           setting = 1;
631         else if (!strcmp (value, "off") || !strcmp (value, "false")
632                  || !strcmp (value, "no") || !strcmp (value, "0"))
633           setting = 0;
634         else
635           {
636             msg (SE, _("Boolean value expected for %s."), key);
637             return;
638           }
639         switch (subcat)
640           {
641           case 0:
642             x->headers = setting;
643             break;
644           case 1:
645             x->paginate = setting;
646             break;
647           case 2:
648             x->squeeze_blank_lines = setting;
649             break;
650           default:
651             assert (0);
652           }
653       }
654       break;
655     default:
656       assert (0);
657     }
658 }
659
660 int
661 postopen (struct file_ext *f)
662 {
663   struct ascii_driver_ext *x = f->param;
664   struct len_string *s = &x->ops[OPS_INIT];
665
666   if (!ls_empty_p (s) && fwrite (ls_c_str (s), ls_length (s), 1, f->file) < 1)
667     {
668       msg (ME, _("ASCII output driver: %s: %s"),
669            f->filename, strerror (errno));
670       return 0;
671     }
672   return 1;
673 }
674
675 int
676 preclose (struct file_ext *f)
677 {
678   struct ascii_driver_ext *x = f->param;
679   struct len_string *d = &x->ops[OPS_DONE];
680
681   if (!ls_empty_p (d) && fwrite (ls_c_str (d), ls_length (d), 1, f->file) < 1)
682     {
683       msg (ME, _("ASCII output driver: %s: %s"),
684            f->filename, strerror (errno));
685       return 0;
686     }
687   return 1;
688 }
689
690 static int
691 ascii_open_page (struct outp_driver *this)
692 {
693   struct ascii_driver_ext *x = this->ext;
694   int i;
695
696   assert (this->driver_open && !this->page_open);
697   x->page_number++;
698   if (!fn_open_ext (&x->file))
699     {
700       msg (ME, _("ASCII output driver: %s: %s"), x->file.filename,
701            strerror (errno));
702       return 0;
703     }
704
705   if (x->l > x->lines_cap)
706     {
707       x->lines = xrealloc (x->lines, sizeof *x->lines * x->l);
708       for (i = x->lines_cap; i < x->l; i++) 
709         {
710           struct line *line = &x->lines[i];
711           line->chars = NULL;
712           line->char_cap = 0;
713         }
714       x->lines_cap = x->l;
715     }
716
717   for (i = 0; i < x->l; i++)
718     x->lines[i].char_cnt = 0;
719
720   this->page_open = 1;
721   return 1;
722 }
723
724 /* Ensures that at least the first L characters of line I in the
725    driver identified by struct ascii_driver_ext *X have been cleared out. */
726 static inline void
727 expand_line (struct ascii_driver_ext *x, int i, int l)
728 {
729   struct line *line;
730   int j;
731
732   assert (i < x->lines_cap);
733   line = &x->lines[i];
734   if (l > line->char_cap) 
735     {
736       line->char_cap = l * 2;
737       line->chars = xrealloc (line->chars,
738                               line->char_cap * sizeof *line->chars); 
739     }
740   for (j = line->char_cnt; j < l; j++)
741     line->chars[j] = ' ';
742   line->char_cnt = l;
743 }
744
745 /* Puts line L at (H,K) in the current output page.  Assumes
746    struct ascii_driver_ext named `ext'. */
747 #define draw_line(H, K, L)                              \
748         ext->lines[K].chars[H] = (L) | 0x800
749
750 /* Line styles for each position. */
751 #define T(STYLE) (STYLE<<LNS_TOP)
752 #define L(STYLE) (STYLE<<LNS_LEFT)
753 #define B(STYLE) (STYLE<<LNS_BOTTOM)
754 #define R(STYLE) (STYLE<<LNS_RIGHT)
755
756 static void
757 ascii_line_horz (struct outp_driver *this, const struct rect *r,
758                  const struct color *c UNUSED, int style)
759 {
760   struct ascii_driver_ext *ext = this->ext;
761   int x1 = r->x1 / this->horiz;
762   int x2 = r->x2 / this->horiz;
763   int y1 = r->y1 / this->vert;
764   int x;
765
766   assert (this->driver_open && this->page_open);
767   if (x1 == x2)
768     return;
769 #if GLOBAL_DEBUGGING
770   if (x1 > x2
771       || x1 < 0 || x1 >= ext->w
772       || x2 <= 0 || x2 > ext->w
773       || y1 < 0 || y1 >= ext->l)
774     {
775 #if !SUPPRESS_WARNINGS
776       printf (_("ascii_line_horz: bad hline (%d,%d),%d out of (%d,%d)\n"),
777               x1, x2, y1, ext->w, ext->l);
778 #endif
779       return;
780     }
781 #endif
782
783   if (ext->lines[y1].char_cnt < x2)
784     expand_line (ext, y1, x2);
785
786   for (x = x1; x < x2; x++)
787     draw_line (x, y1, (style << LNS_LEFT) | (style << LNS_RIGHT));
788 }
789
790 static void
791 ascii_line_vert (struct outp_driver *this, const struct rect *r,
792                  const struct color *c UNUSED, int style)
793 {
794   struct ascii_driver_ext *ext = this->ext;
795   int x1 = r->x1 / this->horiz;
796   int y1 = r->y1 / this->vert;
797   int y2 = r->y2 / this->vert;
798   int y;
799
800   assert (this->driver_open && this->page_open);
801   if (y1 == y2)
802     return;
803 #if GLOBAL_DEBUGGING
804   if (y1 > y2
805       || x1 < 0 || x1 >= ext->w
806       || y1 < 0 || y1 >= ext->l
807       || y2 < 0 || y2 > ext->l)
808     {
809 #if !SUPPRESS_WARNINGS
810       printf (_("ascii_line_vert: bad vline %d,(%d,%d) out of (%d,%d)\n"),
811               x1, y1, y2, ext->w, ext->l);
812 #endif
813       return;
814     }
815 #endif
816
817   for (y = y1; y < y2; y++)
818     if (ext->lines[y].char_cnt <= x1)
819       expand_line (ext, y, x1 + 1);
820
821   for (y = y1; y < y2; y++)
822     draw_line (x1, y, (style << LNS_TOP) | (style << LNS_BOTTOM));
823 }
824
825 static void
826 ascii_line_intersection (struct outp_driver *this, const struct rect *r,
827                          const struct color *c UNUSED,
828                          const struct outp_styles *style)
829 {
830   struct ascii_driver_ext *ext = this->ext;
831   int x = r->x1 / this->horiz;
832   int y = r->y1 / this->vert;
833   int l;
834
835   assert (this->driver_open && this->page_open);
836 #if GLOBAL_DEBUGGING
837   if (x < 0 || x >= ext->w || y < 0 || y >= ext->l)
838     {
839 #if !SUPPRESS_WARNINGS
840       printf (_("ascii_line_intersection: bad intsct (%d,%d) out of (%d,%d)\n"),
841               x, y, ext->w, ext->l);
842 #endif
843       return;
844     }
845 #endif
846
847   l = ((style->l << LNS_LEFT) | (style->r << LNS_RIGHT)
848        | (style->t << LNS_TOP) | (style->b << LNS_BOTTOM));
849
850   if (ext->lines[y].char_cnt <= x)
851     expand_line (ext, y, x + 1);
852   draw_line (x, y, l);
853 }
854
855 /* FIXME: Later we could set this up so that for certain devices it
856    performs shading? */
857 static void
858 ascii_box (struct outp_driver *this UNUSED, const struct rect *r UNUSED,
859            const struct color *bord UNUSED, const struct color *fill UNUSED)
860 {
861   assert (this->driver_open && this->page_open);
862 }
863
864 /* Polylines not supported. */
865 static void
866 ascii_polyline_begin (struct outp_driver *this UNUSED, const struct color *c UNUSED)
867 {
868   assert (this->driver_open && this->page_open);
869 }
870 static void
871 ascii_polyline_point (struct outp_driver *this UNUSED, int x UNUSED, int y UNUSED)
872 {
873   assert (this->driver_open && this->page_open);
874 }
875 static void
876 ascii_polyline_end (struct outp_driver *this UNUSED)
877 {
878   assert (this->driver_open && this->page_open);
879 }
880
881 static void
882 ascii_text_set_font_by_name (struct outp_driver * this, const char *s)
883 {
884   struct ascii_driver_ext *x = this->ext;
885   int len = strlen (s);
886
887   assert (this->driver_open && this->page_open);
888   x->cur_font = OUTP_F_R;
889   if (len == 0)
890     return;
891   if (s[len - 1] == 'I')
892     {
893       if (len > 1 && s[len - 2] == 'B')
894         x->cur_font = OUTP_F_BI;
895       else
896         x->cur_font = OUTP_F_I;
897     }
898   else if (s[len - 1] == 'B')
899     x->cur_font = OUTP_F_B;
900 }
901
902 static void
903 ascii_text_set_font_by_position (struct outp_driver *this, int pos)
904 {
905   struct ascii_driver_ext *x = this->ext;
906   assert (this->driver_open && this->page_open);
907   x->cur_font = pos >= 0 && pos < 4 ? pos : 0;
908 }
909
910 static void
911 ascii_text_set_font_by_family (struct outp_driver *this UNUSED, const char *s UNUSED)
912 {
913   assert (this->driver_open && this->page_open);
914 }
915
916 static const char *
917 ascii_text_get_font_name (struct outp_driver *this)
918 {
919   struct ascii_driver_ext *x = this->ext;
920
921   assert (this->driver_open && this->page_open);
922   switch (x->cur_font)
923     {
924     case OUTP_F_R:
925       return "R";
926     case OUTP_F_I:
927       return "I";
928     case OUTP_F_B:
929       return "B";
930     case OUTP_F_BI:
931       return "BI";
932     default:
933       assert (0);
934     }
935   abort ();
936 }
937
938 static const char *
939 ascii_text_get_font_family (struct outp_driver *this UNUSED)
940 {
941   assert (this->driver_open && this->page_open);
942   return "";
943 }
944
945 static int
946 ascii_text_set_size (struct outp_driver *this, int size)
947 {
948   assert (this->driver_open && this->page_open);
949   return size == this->vert;
950 }
951
952 static int
953 ascii_text_get_size (struct outp_driver *this, int *em_width)
954 {
955   assert (this->driver_open && this->page_open);
956   if (em_width)
957     *em_width = this->horiz;
958   return this->vert;
959 }
960
961 static void text_draw (struct outp_driver *this, struct outp_text *t);
962
963 /* Divides the text T->S into lines of width T->H.  Sets T->V to the
964    number of lines necessary.  Actually draws the text if DRAW is
965    nonzero.
966
967    You probably don't want to look at this code. */
968 static void
969 delineate (struct outp_driver *this, struct outp_text *t, int draw)
970 {
971   /* Width we're fitting everything into. */
972   int width = t->h / this->horiz;
973
974   /* Maximum `y' position we can write to. */
975   int max_y;
976
977   /* Current position in string, character following end of string. */
978   const char *s = ls_c_str (&t->s);
979   const char *end = ls_end (&t->s);
980
981   /* Temporary struct outp_text to pass to low-level function. */
982   struct outp_text temp;
983
984 #if GLOBAL_DEBUGGING && 0
985   if (!ext->debug)
986     {
987       ext->debug = 1;
988       printf (_("%s: horiz=%d, vert=%d\n"), this->name, this->horiz, this->vert);
989     }
990 #endif
991
992   if (!width)
993     {
994       t->h = t->v = 0;
995       return;
996     }
997
998   if (draw)
999     {
1000       temp.options = t->options;
1001       ls_shallow_copy (&temp.s, &t->s);
1002       temp.h = t->h / this->horiz;
1003       temp.x = t->x / this->horiz;
1004     }
1005   else
1006     t->y = 0;
1007   temp.y = t->y / this->vert;
1008
1009   if (t->options & OUTP_T_VERT)
1010     max_y = (t->v / this->vert) + temp.y - 1;
1011   else
1012     max_y = INT_MAX;
1013   
1014   while (end - s > width)
1015     {
1016       const char *beg = s;
1017       const char *space;
1018
1019       /* Find first space before &s[width]. */
1020       space = &s[width];
1021       for (;;)
1022         {
1023           if (space > s)
1024             {
1025               if (!isspace ((unsigned char) space[-1]))
1026                 {
1027                   space--;
1028                   continue;
1029                 }
1030               else
1031                 s = space;
1032             }
1033           else
1034             s = space = &s[width];
1035           break;
1036         }
1037
1038       /* Draw text. */
1039       if (draw)
1040         {
1041           ls_init (&temp.s, beg, space - beg);
1042           temp.w = space - beg;
1043           text_draw (this, &temp);
1044         }
1045       if (++temp.y > max_y)
1046         return;
1047
1048       /* Find first nonspace after space. */
1049       while (s < end && isspace ((unsigned char) *s))
1050         s++;
1051     }
1052   if (s < end)
1053     {
1054       if (draw)
1055         {
1056           ls_init (&temp.s, s, end - s);
1057           temp.w = end - s;
1058           text_draw (this, &temp);
1059         }
1060       temp.y++;
1061     }
1062
1063   t->v = (temp.y * this->vert) - t->y;
1064 }
1065
1066 static void
1067 ascii_text_metrics (struct outp_driver *this, struct outp_text *t)
1068 {
1069   assert (this->driver_open && this->page_open);
1070   if (!(t->options & OUTP_T_HORZ))
1071     {
1072       t->v = this->vert;
1073       t->h = ls_length (&t->s) * this->horiz;
1074     }
1075   else
1076     delineate (this, t, 0);
1077 }
1078
1079 static void
1080 ascii_text_draw (struct outp_driver *this, struct outp_text *t)
1081 {
1082   /* FIXME: orientations not supported. */
1083   assert (this->driver_open && this->page_open);
1084   if (!(t->options & OUTP_T_HORZ))
1085     {
1086       struct outp_text temp;
1087
1088       temp.options = t->options;
1089       temp.s = t->s;
1090       temp.h = temp.v = 0;
1091       temp.x = t->x / this->horiz;
1092       temp.y = t->y / this->vert;
1093       text_draw (this, &temp);
1094       ascii_text_metrics (this, t);
1095       
1096       return;
1097     }
1098   delineate (this, t, 1);
1099 }
1100
1101 static void
1102 text_draw (struct outp_driver *this, struct outp_text *t)
1103 {
1104   struct ascii_driver_ext *ext = this->ext;
1105   unsigned attr = ext->cur_font << 8;
1106
1107   int x = t->x;
1108   int y = t->y;
1109
1110   char *s = ls_c_str (&t->s);
1111
1112   /* Expand the line with the assumption that S takes up LEN character
1113      spaces (sometimes it takes up less). */
1114   int min_len;
1115
1116   assert (this->driver_open && this->page_open);
1117   switch (t->options & OUTP_T_JUST_MASK)
1118     {
1119     case OUTP_T_JUST_LEFT:
1120       break;
1121     case OUTP_T_JUST_CENTER:
1122       x -= (t->h - t->w) / 2;   /* fall through */
1123     case OUTP_T_JUST_RIGHT:
1124       x += (t->h - t->w);
1125       break;
1126     default:
1127       assert (0);
1128     }
1129
1130   if (!(t->y < ext->l && x < ext->w))
1131     return;
1132   min_len = min (x + ls_length (&t->s), ext->w);
1133   if (ext->lines[t->y].char_cnt < min_len)
1134     expand_line (ext, t->y, min_len);
1135
1136   {
1137     int len = ls_length (&t->s);
1138
1139     if (len + x > ext->w)
1140       len = ext->w - x;
1141     while (len--)
1142       ext->lines[y].chars[x++] = *s++ | attr;
1143   }
1144 }
1145 \f
1146 /* ascii_close_page () and support routines. */
1147
1148 #define LINE_BUF_SIZE 1024
1149 static unsigned char *line_buf;
1150 static unsigned char *line_p;
1151
1152 static inline int
1153 commit_line_buf (struct outp_driver *this)
1154 {
1155   struct ascii_driver_ext *x = this->ext;
1156   
1157   if ((int) fwrite (line_buf, 1, line_p - line_buf, x->file.file)
1158       < line_p - line_buf)
1159     {
1160       msg (ME, _("Writing `%s': %s"), x->file.filename, strerror (errno));
1161       return 0;
1162     }
1163
1164   line_p = line_buf;
1165   return 1;
1166 }
1167
1168 /* Writes everything from BP to EP exclusive into line_buf, or to
1169    THIS->output if line_buf overflows. */
1170 static inline void
1171 output_string (struct outp_driver *this, const char *bp, const char *ep)
1172 {
1173   if (LINE_BUF_SIZE - (line_p - line_buf) >= ep - bp)
1174     {
1175       memcpy (line_p, bp, ep - bp);
1176       line_p += ep - bp;
1177     }
1178   else
1179     while (bp < ep)
1180       {
1181         if (LINE_BUF_SIZE - (line_p - line_buf) <= 1 && !commit_line_buf (this))
1182           return;
1183         *line_p++ = *bp++;
1184       }
1185 }
1186
1187 /* Writes everything from BP to EP exclusive into line_buf, or to
1188    THIS->output if line_buf overflows.  Returns 1 if additional passes
1189    over the line are required.  FIXME: probably could do a lot of
1190    optimization here. */
1191 static inline int
1192 output_shorts (struct outp_driver *this,
1193                const unsigned short *bp, const unsigned short *ep)
1194 {
1195   struct ascii_driver_ext *ext = this->ext;
1196   size_t remaining = LINE_BUF_SIZE - (line_p - line_buf);
1197   int result = 0;
1198
1199   for (; bp < ep; bp++)
1200     {
1201       if (*bp & 0x800)
1202         {
1203           struct len_string *box = &ext->box[*bp & 0xff];
1204           size_t len = ls_length (box);
1205
1206           if (remaining >= len)
1207             {
1208               memcpy (line_p, ls_c_str (box), len);
1209               line_p += len;
1210               remaining -= len;
1211             }
1212           else
1213             {
1214               if (!commit_line_buf (this))
1215                 return 0;
1216               output_string (this, ls_c_str (box), ls_end (box));
1217               remaining = LINE_BUF_SIZE - (line_p - line_buf);
1218             }
1219         }
1220       else if (*bp & 0x0300)
1221         {
1222           struct len_string *on;
1223           char buf[5];
1224           int len;
1225
1226           switch (*bp & 0x0300)
1227             {
1228             case OUTP_F_I << 8:
1229               on = &ext->fonts[FSTY_ON | FSTY_ITALIC];
1230               break;
1231             case OUTP_F_B << 8:
1232               on = &ext->fonts[FSTY_ON | FSTY_BOLD];
1233               break;
1234             case OUTP_F_BI << 8:
1235               on = &ext->fonts[FSTY_ON | FSTY_BOLD_ITALIC];
1236               break;
1237             default:
1238               assert (0);
1239               abort ();
1240             }
1241           if (!on)
1242             {
1243               if (ext->overstrike_style == OVS_SINGLE)
1244                 switch (*bp & 0x0300)
1245                   {
1246                   case OUTP_F_I << 8:
1247                     buf[0] = '_';
1248                     buf[1] = '\b';
1249                     buf[2] = *bp;
1250                     len = 3;
1251                     break;
1252                   case OUTP_F_B << 8:
1253                     buf[0] = *bp;
1254                     buf[1] = '\b';
1255                     buf[2] = *bp;
1256                     len = 3;
1257                     break;
1258                   case OUTP_F_BI << 8:
1259                     buf[0] = '_';
1260                     buf[1] = '\b';
1261                     buf[2] = *bp;
1262                     buf[3] = '\b';
1263                     buf[4] = *bp;
1264                     len = 5;
1265                     break;
1266                   default:
1267                     assert (0);
1268                     abort ();
1269                   }
1270               else
1271                 {
1272                   buf[0] = *bp;
1273                   result = len = 1;
1274                 }
1275             }
1276           else
1277             {
1278               buf[0] = *bp;
1279               len = 1;
1280             }
1281           output_string (this, buf, &buf[len]);
1282         }
1283       else if (remaining)
1284         {
1285           *line_p++ = *bp;
1286           remaining--;
1287         }
1288       else
1289         {
1290           if (!commit_line_buf (this))
1291             return 0;
1292           remaining = LINE_BUF_SIZE - (line_p - line_buf);
1293           *line_p++ = *bp;
1294         }
1295     }
1296
1297   return result;
1298 }
1299
1300 /* Writes CH into line_buf N times, or to THIS->output if line_buf
1301    overflows. */
1302 static inline void
1303 output_char (struct outp_driver *this, int n, int ch)
1304 {
1305   if (LINE_BUF_SIZE - (line_p - line_buf) >= n)
1306     {
1307       memset (line_p, ch, n);
1308       line_p += n;
1309     }
1310   else
1311     while (n--)
1312       {
1313         if (LINE_BUF_SIZE - (line_p - line_buf) <= 1 && !commit_line_buf (this))
1314           return;
1315         *line_p++ = ch;
1316       }
1317 }
1318
1319 /* Advance the carriage from column 0 to the left margin. */
1320 static void
1321 advance_to_left_margin (struct outp_driver *this)
1322 {
1323   struct ascii_driver_ext *ext = this->ext;
1324   int margin;
1325
1326   margin = ext->left_margin;
1327   if (margin == 0)
1328     return;
1329   if (ext->tab_width && margin >= ext->tab_width)
1330     {
1331       output_char (this, margin / ext->tab_width, '\t');
1332       margin %= ext->tab_width;
1333     }
1334   if (margin)
1335     output_char (this, margin, ' ');
1336 }
1337
1338 /* Move the output file carriage N_CHARS left, to the left margin. */
1339 static void
1340 return_carriage (struct outp_driver *this, int n_chars)
1341 {
1342   struct ascii_driver_ext *ext = this->ext;
1343
1344   switch (ext->carriage_return_style)
1345     {
1346     case CRS_BS:
1347       output_char (this, n_chars, '\b');
1348       break;
1349     case CRS_CR:
1350       output_char (this, 1, '\r');
1351       advance_to_left_margin (this);
1352       break;
1353     default:
1354       assert (0);
1355       abort ();
1356     }
1357 }
1358
1359 /* Writes COUNT lines from the line buffer in THIS, starting at line
1360    number FIRST. */
1361 static void
1362 output_lines (struct outp_driver *this, int first, int count)
1363 {
1364   struct ascii_driver_ext *ext = this->ext;
1365   int line_num;
1366
1367   struct len_string *newline = &ext->ops[OPS_NEWLINE];
1368
1369   int n_chars;
1370   int n_passes;
1371
1372   if (NULL == ext->file.file)
1373     return;
1374
1375   /* Iterate over all the lines to be output. */
1376   for (line_num = first; line_num < first + count; line_num++)
1377     {
1378       struct line *line = &ext->lines[line_num];
1379       unsigned short *p = line->chars;
1380       unsigned short *end_p = p + line->char_cnt;
1381       unsigned short *bp, *ep;
1382       unsigned short attr = 0;
1383
1384       assert (end_p >= p);
1385
1386       /* Squeeze multiple blank lines into a single blank line if
1387          requested. */
1388       if (ext->squeeze_blank_lines
1389           && line_num > first
1390           && ext->lines[line_num].char_cnt == 0
1391           && ext->lines[line_num - 1].char_cnt == 0)
1392         continue;
1393
1394       /* Output every character in the line in the appropriate
1395          manner. */
1396       n_passes = 1;
1397       bp = ep = p;
1398       n_chars = 0;
1399       advance_to_left_margin (this);
1400       for (;;)                  
1401         {
1402           while (ep < end_p && attr == (*ep & 0x0300))
1403             ep++;
1404           if (output_shorts (this, bp, ep))
1405             n_passes = 2;
1406           n_chars += ep - bp;
1407           bp = ep;
1408
1409           if (bp >= end_p)
1410             break;
1411
1412           /* Turn off old font. */
1413           if (attr != (OUTP_F_R << 8))
1414             {
1415               struct len_string *off;
1416
1417               switch (attr)
1418                 {
1419                 case OUTP_F_I << 8:
1420                   off = &ext->fonts[FSTY_OFF | FSTY_ITALIC];
1421                   break;
1422                 case OUTP_F_B << 8:
1423                   off = &ext->fonts[FSTY_OFF | FSTY_BOLD];
1424                   break;
1425                 case OUTP_F_BI << 8:
1426                   off = &ext->fonts[FSTY_OFF | FSTY_BOLD_ITALIC];
1427                   break;
1428                 default:
1429                   assert (0);
1430                   abort ();
1431                 }
1432               if (off)
1433                 output_string (this, ls_c_str (off), ls_end (off));
1434             }
1435
1436           /* Turn on new font. */
1437           attr = (*bp & 0x0300);
1438           if (attr != (OUTP_F_R << 8))
1439             {
1440               struct len_string *on;
1441
1442               switch (attr)
1443                 {
1444                 case OUTP_F_I << 8:
1445                   on = &ext->fonts[FSTY_ON | FSTY_ITALIC];
1446                   break;
1447                 case OUTP_F_B << 8:
1448                   on = &ext->fonts[FSTY_ON | FSTY_BOLD];
1449                   break;
1450                 case OUTP_F_BI << 8:
1451                   on = &ext->fonts[FSTY_ON | FSTY_BOLD_ITALIC];
1452                   break;
1453                 default:
1454                   assert (0);
1455                   abort ();
1456                 }
1457               if (on)
1458                 output_string (this, ls_c_str (on), ls_end (on));
1459             }
1460
1461           ep = bp + 1;
1462         }
1463       if (n_passes > 1)
1464         {
1465           unsigned char ch;
1466
1467           return_carriage (this, n_chars);
1468           n_chars = 0;
1469           bp = ep = p;
1470           for (;;)
1471             {
1472               while (ep < end_p && (*ep & 0x0300) == (OUTP_F_R << 8))
1473                 ep++;
1474               if (ep >= end_p)
1475                 break;
1476               output_char (this, ep - bp, ' ');
1477
1478               switch (*ep & 0x0300)
1479                 {
1480                 case OUTP_F_I << 8:
1481                   ch = '_';
1482                   break;
1483                 case OUTP_F_B << 8:
1484                   ch = *ep;
1485                   break;
1486                 case OUTP_F_BI << 8:
1487                   ch = *ep;
1488                   n_passes = 3;
1489                   break;
1490                 default:
1491                   assert (0);
1492                   abort ();
1493                 }
1494               output_char (this, 1, ch);
1495               n_chars += ep - bp + 1;
1496               bp = ep + 1;
1497               ep = bp;
1498             }
1499         }
1500       if (n_passes > 2)
1501         {
1502           return_carriage (this, n_chars);
1503           bp = ep = p;
1504           for (;;)
1505             {
1506               while (ep < end_p && (*ep & 0x0300) != (OUTP_F_BI << 8))
1507                 ep++;
1508               if (ep >= end_p)
1509                 break;
1510               output_char (this, ep - bp, ' ');
1511               output_char (this, 1, '_');
1512               bp = ep + 1;
1513               ep = bp;
1514             }
1515         }
1516
1517       output_string (this, ls_c_str (newline), ls_end (newline));
1518     }
1519 }
1520
1521 static int
1522 ascii_close_page (struct outp_driver *this)
1523 {
1524   static unsigned char *s;
1525   static int s_len;
1526
1527   struct ascii_driver_ext *x = this->ext;
1528   int nl_len, ff_len, total_len;
1529   unsigned char *cp;
1530   int i;
1531
1532   assert (this->driver_open && this->page_open);
1533   
1534   if (!line_buf)
1535     line_buf = xmalloc (LINE_BUF_SIZE);
1536   line_p = line_buf;
1537
1538   nl_len = ls_length (&x->ops[OPS_NEWLINE]);
1539   if (x->top_margin)
1540     {
1541       total_len = x->top_margin * nl_len;
1542       if (s_len < total_len)
1543         {
1544           s_len = total_len;
1545           s = xrealloc (s, s_len);
1546         }
1547       for (cp = s, i = 0; i < x->top_margin; i++)
1548         {
1549           memcpy (cp, ls_c_str (&x->ops[OPS_NEWLINE]), nl_len);
1550           cp += nl_len;
1551         }
1552       output_string (this, s, &s[total_len]);
1553     }
1554   if (x->headers)
1555     {
1556       int len;
1557
1558       total_len = nl_len + x->w;
1559       if (s_len < total_len + 1)
1560         {
1561           s_len = total_len + 1;
1562           s = xrealloc (s, s_len);
1563         }
1564       
1565       memset (s, ' ', x->w);
1566
1567       {
1568         char temp[40];
1569
1570         snprintf (temp, 80, _("%s - Page %d"), curdate, x->page_number);
1571         memcpy (&s[x->w - strlen (temp)], temp, strlen (temp));
1572       }
1573
1574       if (outp_title && outp_subtitle)
1575         {
1576           len = min ((int) strlen (outp_title), x->w);
1577           memcpy (s, outp_title, len);
1578         }
1579       memcpy (&s[x->w], ls_c_str (&x->ops[OPS_NEWLINE]), nl_len);
1580       output_string (this, s, &s[total_len]);
1581
1582       memset (s, ' ', x->w);
1583       len = strlen (version) + 3 + strlen (host_system);
1584       if (len < x->w)
1585         sprintf (&s[x->w - len], "%s - %s" , version, host_system);
1586       if (outp_subtitle || outp_title)
1587         {
1588           char *string = outp_subtitle ? outp_subtitle : outp_title;
1589           len = min ((int) strlen (string), x->w);
1590           memcpy (s, string, len);
1591         }
1592       memcpy (&s[x->w], ls_c_str (&x->ops[OPS_NEWLINE]), nl_len);
1593       output_string (this, s, &s[total_len]);
1594       output_string (this, &s[x->w], &s[total_len]);
1595     }
1596   if (line_p != line_buf && !commit_line_buf (this))
1597     return 0;
1598
1599   output_lines (this, 0, x->l);
1600
1601   ff_len = ls_length (&x->ops[OPS_FORMFEED]);
1602   total_len = x->bottom_margin * nl_len + ff_len;
1603   if (s_len < total_len)
1604     s = xrealloc (s, total_len);
1605   for (cp = s, i = 0; i < x->bottom_margin; i++)
1606     {
1607       memcpy (cp, ls_c_str (&x->ops[OPS_NEWLINE]), nl_len);
1608       cp += nl_len;
1609     }
1610   memcpy (cp, ls_c_str (&x->ops[OPS_FORMFEED]), ff_len);
1611   if ( x->paginate ) 
1612           output_string (this, s, &s[total_len]);
1613   if (line_p != line_buf && !commit_line_buf (this))
1614     return 0;
1615
1616   this->page_open = 0;
1617   return 1;
1618 }
1619
1620 struct outp_class ascii_class =
1621 {
1622   "ascii",
1623   0,
1624   0,
1625
1626   ascii_open_global,
1627   ascii_close_global,
1628   ascii_font_sizes,
1629
1630   ascii_preopen_driver,
1631   ascii_option,
1632   ascii_postopen_driver,
1633   ascii_close_driver,
1634
1635   ascii_open_page,
1636   ascii_close_page,
1637
1638   NULL,
1639
1640   ascii_line_horz,
1641   ascii_line_vert,
1642   ascii_line_intersection,
1643
1644   ascii_box,
1645   ascii_polyline_begin,
1646   ascii_polyline_point,
1647   ascii_polyline_end,
1648
1649   ascii_text_set_font_by_name,
1650   ascii_text_set_font_by_position,
1651   ascii_text_set_font_by_family,
1652   ascii_text_get_font_name,
1653   ascii_text_get_font_family,
1654   ascii_text_set_size,
1655   ascii_text_get_size,
1656   ascii_text_metrics,
1657   ascii_text_draw,
1658 };