5fe9dc0e6f973fa8c5cf89757226a83be2a98072
[pspp] / src / output / ascii.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013 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 <ctype.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <signal.h>
25 #include <unilbrk.h>
26 #include <unistd.h>
27 #include <unistr.h>
28 #include <uniwidth.h>
29
30 #include "data/file-name.h"
31 #include "data/settings.h"
32 #include "libpspp/assertion.h"
33 #include "libpspp/cast.h"
34 #include "libpspp/compiler.h"
35 #include "libpspp/message.h"
36 #include "libpspp/start-date.h"
37 #include "libpspp/string-map.h"
38 #include "libpspp/u8-line.h"
39 #include "libpspp/version.h"
40 #include "output/ascii.h"
41 #include "output/cairo.h"
42 #include "output/chart-item-provider.h"
43 #include "output/driver-provider.h"
44 #include "output/message-item.h"
45 #include "output/options.h"
46 #include "output/render.h"
47 #include "output/tab.h"
48 #include "output/table-item.h"
49 #include "output/text-item.h"
50
51 #include "gl/minmax.h"
52 #include "gl/xalloc.h"
53
54 #include "gettext.h"
55 #define _(msgid) gettext (msgid)
56
57 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
58 #define H TABLE_HORZ
59 #define V TABLE_VERT
60
61 #define N_BOX (RENDER_N_LINES * RENDER_N_LINES \
62                * RENDER_N_LINES * RENDER_N_LINES)
63
64 static const ucs4_t ascii_box_chars[N_BOX] =
65   {
66     ' ', '|', '#',
67     '-', '+', '#',
68     '=', '#', '#',
69     '|', '|', '#',
70     '+', '+', '#',
71     '#', '#', '#',
72     '#', '#', '#',
73     '#', '#', '#',
74     '#', '#', '#',
75     '-', '+', '#',
76     '-', '+', '#',
77     '#', '#', '#',
78     '+', '+', '#',
79     '+', '+', '#',
80     '#', '#', '#',
81     '#', '#', '#',
82     '#', '#', '#',
83     '#', '#', '#',
84     '=', '#', '#',
85     '#', '#', '#',
86     '=', '#', '#',
87     '#', '#', '#',
88     '#', '#', '#',
89     '#', '#', '#',
90     '#', '#', '#',
91     '#', '#', '#',
92     '#', '#', '#',
93   };
94
95 static const ucs4_t unicode_box_chars[N_BOX] =
96   {
97     0x0020, 0x2575, 0x2551,
98     0x2574, 0x256f, 0x255c,
99     0x2550, 0x255b, 0x255d,
100     0x2577, 0x2502, 0x2551,
101     0x256e, 0x2524, 0x2562,
102     0x2555, 0x2561, 0x2563,
103     0x2551, 0x2551, 0x2551,
104     0x2556, 0x2562, 0x2562,
105     0x2557, 0x2563, 0x2563,
106     0x2576, 0x2570, 0x2559,
107     0x2500, 0x2534, 0x2568,
108     0x2550, 0x2567, 0x2569,
109     0x256d, 0x251c, 0x255f,
110     0x252c, 0x253c, 0x256a,
111     0x2564, 0x256a, 0x256c,
112     0x2553, 0x255f, 0x255f,
113     0x2565, 0x256b, 0x256b,
114     0x2566, 0x256c, 0x256c,
115     0x2550, 0x2558, 0x255a,
116     0x2550, 0x2567, 0x2569,
117     0x2550, 0x2567, 0x2569,
118     0x2552, 0x255e, 0x2560,
119     0x2564, 0x256a, 0x256c,
120     0x2564, 0x256a, 0x256c,
121     0x2554, 0x2560, 0x2560,
122     0x2560, 0x256c, 0x256c,
123     0x2566, 0x256c, 0x256c,
124   };
125
126 static inline int
127 make_box_index (int left, int right, int top, int bottom)
128 {
129   return ((right * RENDER_N_LINES + bottom) * RENDER_N_LINES + left) * RENDER_N_LINES + top;
130 }
131
132 /* How to emphasize text. */
133 enum emphasis_style
134   {
135     EMPH_BOLD,                  /* Overstrike for bold. */
136     EMPH_UNDERLINE,             /* Overstrike for underlining. */
137     EMPH_NONE                   /* No emphasis. */
138   };
139
140 /* ASCII output driver. */
141 struct ascii_driver
142   {
143     struct output_driver driver;
144
145     /* User parameters. */
146     bool append;                /* Append if output file already exists? */
147     bool headers;               /* Print headers at top of page? */
148     bool paginate;              /* Insert formfeeds? */
149     bool squeeze_blank_lines;   /* Squeeze multiple blank lines into one? */
150     enum emphasis_style emphasis; /* How to emphasize text. */
151     char *chart_file_name;      /* Name of files used for charts. */
152
153 #ifdef HAVE_CAIRO
154     /* Colours for charts */
155     struct xr_color fg;
156     struct xr_color bg;
157 #endif
158
159     int width;                  /* Page width. */
160     int length;                 /* Page length minus margins and header. */
161     bool auto_width;            /* Use viewwidth as page width? */
162     bool auto_length;           /* Use viewlength as page width? */
163
164     int top_margin;             /* Top margin in lines. */
165     int bottom_margin;          /* Bottom margin in lines. */
166
167     const ucs4_t *box;          /* Line & box drawing characters. */
168
169     /* Internal state. */
170     char *command_name;
171     char *title;
172     char *subtitle;
173     char *file_name;            /* Output file name. */
174     FILE *file;                 /* Output file. */
175     bool error;                 /* Output error? */
176     int page_number;            /* Current page number. */
177     struct u8_line *lines;      /* Page content. */
178     int allocated_lines;        /* Number of lines allocated. */
179     int chart_cnt;              /* Number of charts so far. */
180     int y;
181   };
182
183 static const struct output_driver_class ascii_driver_class;
184
185 static void ascii_submit (struct output_driver *, const struct output_item *);
186
187 static int vertical_margins (const struct ascii_driver *);
188
189 static bool update_page_size (struct ascii_driver *, bool issue_error);
190 static int parse_page_size (struct driver_option *);
191
192 static void ascii_close_page (struct ascii_driver *);
193 static bool ascii_open_page (struct ascii_driver *);
194
195 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
196                              enum render_line_style styles[TABLE_N_AXES][2]);
197 static void ascii_measure_cell_width (void *, const struct table_cell *,
198                                       int *min, int *max);
199 static int ascii_measure_cell_height (void *, const struct table_cell *,
200                                       int width);
201 static void ascii_draw_cell (void *, const struct table_cell *,
202                              int bb[TABLE_N_AXES][2],
203                              int clip[TABLE_N_AXES][2]);
204
205 static void
206 reallocate_lines (struct ascii_driver *a)
207 {
208   if (a->length > a->allocated_lines)
209     {
210       int i;
211       a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
212       for (i = a->allocated_lines; i < a->length; i++)
213         u8_line_init (&a->lines[i]);
214       a->allocated_lines = a->length;
215     }
216 }
217
218
219 static struct ascii_driver *
220 ascii_driver_cast (struct output_driver *driver)
221 {
222   assert (driver->class == &ascii_driver_class);
223   return UP_CAST (driver, struct ascii_driver, driver);
224 }
225
226 static struct driver_option *
227 opt (struct output_driver *d, struct string_map *options, const char *key,
228      const char *default_value)
229 {
230   return driver_option_get (d, options, key, default_value);
231 }
232
233 static struct output_driver *
234 ascii_create (const char *file_name, enum settings_output_devices device_type,
235               struct string_map *o)
236 {
237   enum { BOX_ASCII, BOX_UNICODE } box;
238   struct output_driver *d;
239   struct ascii_driver *a;
240   int paper_length;
241
242   a = xzalloc (sizeof *a);
243   d = &a->driver;
244   output_driver_init (&a->driver, &ascii_driver_class, file_name, device_type);
245   a->append = parse_boolean (opt (d, o, "append", "false"));
246   a->headers = parse_boolean (opt (d, o, "headers", "false"));
247   a->paginate = parse_boolean (opt (d, o, "paginate", "false"));
248   a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true"));
249   a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
250                             "bold", EMPH_BOLD,
251                             "underline", EMPH_UNDERLINE,
252                             "none", EMPH_NONE,
253                             NULL_SENTINEL);
254
255   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", file_name));
256
257   a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX);
258   a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX);
259
260   a->width = parse_page_size (opt (d, o, "width", "79"));
261   paper_length = parse_page_size (opt (d, o, "length", "66"));
262   a->auto_width = a->width < 0;
263   a->auto_length = paper_length < 0;
264   a->length = paper_length - vertical_margins (a);
265 #ifdef HAVE_CAIRO
266   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
267   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
268 #endif
269   box = parse_enum (opt (d, o, "box", "ascii"),
270                     "ascii", BOX_ASCII,
271                     "unicode", BOX_UNICODE,
272                     NULL_SENTINEL);
273   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
274
275   a->command_name = NULL;
276   a->title = xstrdup ("");
277   a->subtitle = xstrdup ("");
278   a->file_name = xstrdup (file_name);
279   a->file = NULL;
280   a->error = false;
281   a->page_number = 0;
282   a->lines = NULL;
283   a->allocated_lines = 0;
284   a->chart_cnt = 1;
285
286   if (!update_page_size (a, true))
287     goto error;
288
289   return d;
290
291 error:
292   output_driver_destroy (d);
293   return NULL;
294 }
295
296 static int
297 parse_page_size (struct driver_option *option)
298 {
299   int dim = atol (option->default_value);
300
301   if (option->value != NULL)
302     {
303       if (!strcmp (option->value, "auto"))
304         dim = -1;
305       else
306         {
307           int value;
308           char *tail;
309
310           errno = 0;
311           value = strtol (option->value, &tail, 0);
312           if (dim >= 1 && errno != ERANGE && *tail == '\0')
313             dim = value;
314           else
315             msg (MW, _("%s: %s must be positive integer or `auto'"),
316                    option->driver_name, option->name);
317         }
318     }
319
320   driver_option_destroy (option);
321
322   return dim;
323 }
324
325 static int
326 vertical_margins (const struct ascii_driver *a)
327 {
328   return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
329 }
330
331 /* Re-calculates the page width and length based on settings,
332    margins, and, if "auto" is set, the size of the user's
333    terminal window or GUI output window. */
334 static bool
335 update_page_size (struct ascii_driver *a, bool issue_error)
336 {
337   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
338
339   if (a->auto_width)
340     a->width = settings_get_viewwidth ();
341   if (a->auto_length)
342     a->length = settings_get_viewlength () - vertical_margins (a);
343
344   if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
345     {
346       if (issue_error)
347         msg (ME,
348                _("ascii: page excluding margins and headers "
349                  "must be at least %d characters wide by %d lines long, but "
350                  "as configured is only %d characters by %d lines"),
351                MIN_WIDTH, MIN_LENGTH,
352                a->width, a->length);
353       if (a->width < MIN_WIDTH)
354         a->width = MIN_WIDTH;
355       if (a->length < MIN_LENGTH)
356         a->length = MIN_LENGTH;
357       return false;
358     }
359
360   reallocate_lines (a);
361
362   return true;
363 }
364
365 static void
366 ascii_destroy (struct output_driver *driver)
367 {
368   struct ascii_driver *a = ascii_driver_cast (driver);
369   int i;
370
371   if (a->y > 0)
372     ascii_close_page (a);
373
374   if (a->file != NULL)
375     fn_close (a->file_name, a->file);
376   free (a->command_name);
377   free (a->title);
378   free (a->subtitle);
379   free (a->file_name);
380   free (a->chart_file_name);
381   for (i = 0; i < a->allocated_lines; i++)
382     u8_line_destroy (&a->lines[i]);
383   free (a->lines);
384   free (a);
385 }
386
387 static void
388 ascii_flush (struct output_driver *driver)
389 {
390   struct ascii_driver *a = ascii_driver_cast (driver);
391   if (a->y > 0)
392     {
393       ascii_close_page (a);
394
395       if (fn_close (a->file_name, a->file) != 0)
396         msg_error (errno, _("ascii: closing output file `%s'"), a->file_name);
397       a->file = NULL;
398     }
399 }
400
401 static void
402 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
403 {
404   cell->contents = caption;
405   cell->options = TAB_LEFT;
406   cell->destructor = NULL;
407 }
408
409 static void
410 ascii_output_table_item (struct ascii_driver *a,
411                          const struct table_item *table_item)
412 {
413   const char *caption = table_item_get_caption (table_item);
414   struct render_params params;
415   struct render_page *page;
416   struct render_break x_break;
417   int caption_height;
418   int i;
419
420   update_page_size (a, false);
421
422   if (caption != NULL)
423     {
424       /* XXX doesn't do well with very large captions */
425       struct table_cell cell;
426       ascii_init_caption_cell (caption, &cell);
427       caption_height = ascii_measure_cell_height (a, &cell, a->width);
428     }
429   else
430     caption_height = 0;
431
432   params.draw_line = ascii_draw_line;
433   params.measure_cell_width = ascii_measure_cell_width;
434   params.measure_cell_height = ascii_measure_cell_height;
435   params.draw_cell = ascii_draw_cell,
436     params.aux = a;
437   params.size[H] = a->width;
438   params.size[V] = a->length - caption_height;
439   params.font_size[H] = 1;
440   params.font_size[V] = 1;
441   for (i = 0; i < RENDER_N_LINES; i++)
442     {
443       int width = i == RENDER_LINE_NONE ? 0 : 1;
444       params.line_widths[H][i] = width;
445       params.line_widths[V][i] = width;
446     }
447
448   if (a->file == NULL && !ascii_open_page (a))
449     return;
450
451   page = render_page_create (&params, table_item_get_table (table_item));
452   for (render_break_init (&x_break, page, H);
453        render_break_has_next (&x_break); )
454     {
455       struct render_page *x_slice;
456       struct render_break y_break;
457
458       x_slice = render_break_next (&x_break, a->width);
459       for (render_break_init (&y_break, x_slice, V);
460            render_break_has_next (&y_break); )
461         {
462           struct render_page *y_slice;
463           int space;
464
465           if (a->y > 0)
466             a->y++;
467
468           space = a->length - a->y - caption_height;
469           if (render_break_next_size (&y_break) > space)
470             {
471               assert (a->y > 0);
472               ascii_close_page (a);
473               if (!ascii_open_page (a))
474                 return;
475               continue;
476             }
477
478           y_slice = render_break_next (&y_break, space);
479           if (caption_height)
480             {
481               struct table_cell cell;
482               int bb[TABLE_N_AXES][2];
483
484               ascii_init_caption_cell (caption, &cell);
485               bb[H][0] = 0;
486               bb[H][1] = a->width;
487               bb[V][0] = 0;
488               bb[V][1] = caption_height;
489               ascii_draw_cell (a, &cell, bb, bb);
490               a->y += caption_height;
491               caption_height = 0;
492             }
493           render_page_draw (y_slice);
494           a->y += render_page_get_size (y_slice, V);
495           render_page_unref (y_slice);
496         }
497       render_break_destroy (&y_break);
498     }
499   render_break_destroy (&x_break);
500 }
501
502 static void
503 ascii_output_text (struct ascii_driver *a, const char *text)
504 {
505   struct table_item *table_item;
506
507   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
508   ascii_output_table_item (a, table_item);
509   table_item_unref (table_item);
510 }
511
512 static void
513 ascii_submit (struct output_driver *driver,
514               const struct output_item *output_item)
515 {
516   struct ascii_driver *a = ascii_driver_cast (driver);
517
518   output_driver_track_current_command (output_item, &a->command_name);
519
520   if (a->error)
521     return;
522
523   if (is_table_item (output_item))
524     ascii_output_table_item (a, to_table_item (output_item));
525 #ifdef HAVE_CAIRO
526   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
527     {
528       struct chart_item *chart_item = to_chart_item (output_item);
529       char *file_name;
530
531       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
532                                      a->chart_cnt++,
533                                      &a->fg, 
534                                      &a->bg);
535       if (file_name != NULL)
536         {
537           struct text_item *text_item;
538
539           text_item = text_item_create_format (
540             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
541
542           ascii_submit (driver, &text_item->output_item);
543           text_item_unref (text_item);
544           free (file_name);
545         }
546     }
547 #endif  /* HAVE_CAIRO */
548   else if (is_text_item (output_item))
549     {
550       const struct text_item *text_item = to_text_item (output_item);
551       enum text_item_type type = text_item_get_type (text_item);
552       const char *text = text_item_get_text (text_item);
553
554       switch (type)
555         {
556         case TEXT_ITEM_TITLE:
557           free (a->title);
558           a->title = xstrdup (text);
559           break;
560
561         case TEXT_ITEM_SUBTITLE:
562           free (a->subtitle);
563           a->subtitle = xstrdup (text);
564           break;
565
566         case TEXT_ITEM_COMMAND_OPEN:
567         case TEXT_ITEM_COMMAND_CLOSE:
568           break;
569
570         case TEXT_ITEM_BLANK_LINE:
571           if (a->y > 0)
572             a->y++;
573           break;
574
575         case TEXT_ITEM_EJECT_PAGE:
576           if (a->y > 0)
577             ascii_close_page (a);
578           break;
579
580         default:
581           ascii_output_text (a, text);
582           break;
583         }
584     }
585   else if (is_message_item (output_item))
586     {
587       const struct message_item *message_item = to_message_item (output_item);
588       const struct msg *msg = message_item_get_msg (message_item);
589       char *s = msg_to_string (msg, a->command_name);
590       ascii_output_text (a, s);
591       free (s);
592     }
593 }
594
595 const struct output_driver_factory txt_driver_factory =
596   { "txt", "-", ascii_create };
597 const struct output_driver_factory list_driver_factory =
598   { "list", "-", ascii_create };
599
600 static const struct output_driver_class ascii_driver_class =
601   {
602     "text",
603     ascii_destroy,
604     ascii_submit,
605     ascii_flush,
606   };
607 \f
608 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
609                             int n);
610 static void ascii_layout_cell (struct ascii_driver *,
611                                const struct table_cell *,
612                                int bb[TABLE_N_AXES][2],
613                                int clip[TABLE_N_AXES][2],
614                                int *width, int *height);
615
616 static void
617 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
618                  enum render_line_style styles[TABLE_N_AXES][2])
619 {
620   struct ascii_driver *a = a_;
621   char mbchar[6];
622   int x0, x1, y1;
623   ucs4_t uc;
624   int mblen;
625   int x, y;
626
627   /* Clip to the page. */
628   if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
629     return;
630   x0 = bb[H][0];
631   x1 = MIN (bb[H][1], a->width);
632   y1 = MIN (bb[V][1] + a->y, a->length);
633
634   /* Draw. */
635   uc = a->box[make_box_index (styles[V][0], styles[V][1],
636                               styles[H][0], styles[H][1])];
637   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
638   for (y = bb[V][0] + a->y; y < y1; y++)
639     {
640       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
641       for (x = x0; x < x1; x++)
642         {
643           memcpy (p, mbchar, mblen);
644           p += mblen;
645         }
646     }
647 }
648
649 static void
650 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
651                           int *min_width, int *max_width)
652 {
653   struct ascii_driver *a = a_;
654   int bb[TABLE_N_AXES][2];
655   int clip[TABLE_N_AXES][2];
656   int h;
657
658   bb[H][0] = 0;
659   bb[H][1] = INT_MAX;
660   bb[V][0] = 0;
661   bb[V][1] = INT_MAX;
662   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
663   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
664
665   if (strchr (cell->contents, ' '))
666     {
667       bb[H][1] = 1;
668       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
669     }
670   else
671     *min_width = *max_width;
672 }
673
674 static int
675 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
676 {
677   struct ascii_driver *a = a_;
678   int bb[TABLE_N_AXES][2];
679   int clip[TABLE_N_AXES][2];
680   int w, h;
681
682   bb[H][0] = 0;
683   bb[H][1] = width;
684   bb[V][0] = 0;
685   bb[V][1] = INT_MAX;
686   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
687   ascii_layout_cell (a, cell, bb, clip, &w, &h);
688   return h;
689 }
690
691 static void
692 ascii_draw_cell (void *a_, const struct table_cell *cell,
693                  int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
694 {
695   struct ascii_driver *a = a_;
696   int w, h;
697
698   ascii_layout_cell (a, cell, bb, clip, &w, &h);
699 }
700
701 static char *
702 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
703 {
704   assert (y < a->allocated_lines);
705   return u8_line_reserve (&a->lines[y], x0, x1, n);
706 }
707
708 static void
709 text_draw (struct ascii_driver *a, unsigned int options,
710            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
711            int y, const uint8_t *string, int n, size_t width)
712 {
713   int x0 = MAX (0, clip[H][0]);
714   int y0 = MAX (0, clip[V][0] + a->y);
715   int x1 = clip[H][1];
716   int y1 = MIN (a->length, clip[V][1] + a->y);
717   int x;
718
719   y += a->y;
720   if (y < y0 || y >= y1)
721     return;
722
723   switch (options & TAB_ALIGNMENT)
724     {
725     case TAB_LEFT:
726       x = bb[H][0];
727       break;
728     case TAB_CENTER:
729       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
730       break;
731     case TAB_RIGHT:
732       x = bb[H][1] - width;
733       break;
734     default:
735       NOT_REACHED ();
736     }
737   if (x >= x1)
738     return;
739
740   while (x < x0)
741     {
742       ucs4_t uc;
743       int mblen;
744       int w;
745
746       if (n == 0)
747         return;
748       mblen = u8_mbtouc (&uc, string, n);
749
750       string += mblen;
751       n -= mblen;
752
753       w = uc_width (uc, "UTF-8");
754       if (w > 0)
755         {
756           x += w;
757           width -= w;
758         }
759     }
760   if (n == 0)
761     return;
762
763   if (x + width > x1)
764     {
765       int ofs;
766
767       ofs = width = 0;
768       for (ofs = 0; ofs < n; )
769         {
770           ucs4_t uc;
771           int mblen;
772           int w;
773
774           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
775
776           w = uc_width (uc, "UTF-8");
777           if (w > 0)
778             {
779               if (width + w > x1 - x)
780                 break;
781               width += w;
782             }
783           ofs += mblen;
784         }
785       n = ofs;
786       if (n == 0)
787         return;
788     }
789
790   if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
791     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
792   else
793     {
794       size_t n_out;
795       size_t ofs;
796       char *out;
797       int mblen;
798
799       /* First figure out how many bytes need to be inserted. */
800       n_out = n;
801       for (ofs = 0; ofs < n; ofs += mblen)
802         {
803           ucs4_t uc;
804           int w;
805
806           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
807           w = uc_width (uc, "UTF-8");
808
809           if (w > 0)
810             n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
811         }
812
813       /* Then insert them. */
814       out = ascii_reserve (a, y, x, x + width, n_out);
815       for (ofs = 0; ofs < n; ofs += mblen)
816         {
817           ucs4_t uc;
818           int w;
819
820           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
821           w = uc_width (uc, "UTF-8");
822
823           if (w > 0)
824             {
825               if (a->emphasis == EMPH_UNDERLINE)
826                 *out++ = '_';
827               else
828                 out = mempcpy (out, string + ofs, mblen);
829               *out++ = '\b';
830             }
831           out = mempcpy (out, string + ofs, mblen);
832         }
833     }
834 }
835
836 static void
837 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
838                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
839                    int *widthp, int *heightp)
840 {
841   const char *text = cell->contents;
842   size_t length = strlen (text);
843   char *breaks;
844   int bb_width;
845   size_t pos;
846   int y;
847
848   *widthp = 0;
849   *heightp = 0;
850   if (length == 0)
851     return;
852
853   breaks = xmalloc (length + 1);
854   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
855                           "UTF-8", breaks);
856   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
857                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
858
859   pos = 0;
860   bb_width = bb[H][1] - bb[H][0];
861   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
862     {
863       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
864       const char *b = breaks + pos;
865       size_t n = length - pos;
866
867       size_t last_break_ofs = 0;
868       int last_break_width = 0;
869       int width = 0;
870       size_t graph_ofs;
871       size_t ofs;
872
873       for (ofs = 0; ofs < n; )
874         {
875           ucs4_t uc;
876           int mblen;
877           int w;
878
879           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
880           if (b[ofs] == UC_BREAK_MANDATORY)
881             break;
882           else if (b[ofs] == UC_BREAK_POSSIBLE)
883             {
884               last_break_ofs = ofs;
885               last_break_width = width;
886             }
887
888           w = uc_width (uc, "UTF-8");
889           if (w > 0)
890             {
891               if (width + w > bb_width)
892                 {
893                   if (isspace (line[ofs]))
894                     break;
895                   else if (last_break_ofs != 0)
896                     {
897                       ofs = last_break_ofs;
898                       width = last_break_width;
899                       break;
900                     }
901                 }
902               width += w;
903             }
904           ofs += mblen;
905         }
906
907       /* Trim any trailing spaces off the end of the text to be drawn. */
908       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
909         if (!isspace (line[graph_ofs - 1]))
910           break;
911       width -= ofs - graph_ofs;
912
913       /* Draw text. */
914       text_draw (a, cell->options, bb, clip, y, line, graph_ofs, width);
915
916       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
917          past any spaces past the end of the line (but not past a new-line). */
918       if (b[ofs] == UC_BREAK_MANDATORY)
919         ofs++;
920       else
921         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
922           ofs++;
923
924       if (width > *widthp)
925         *widthp = width;
926       pos += ofs;
927     }
928   *heightp = y - bb[V][0];
929
930   free (breaks);
931 }
932
933 void
934 ascii_test_write (struct output_driver *driver,
935                   const char *s, int x, int y, unsigned int options)
936 {
937   struct ascii_driver *a = ascii_driver_cast (driver);
938   struct table_cell cell;
939   int bb[TABLE_N_AXES][2];
940   int width, height;
941
942   if (a->file == NULL && !ascii_open_page (a))
943     return;
944   a->y = 0;
945
946   memset (&cell, 0, sizeof cell);
947   cell.contents = s;
948   cell.options = options | TAB_LEFT;
949
950   bb[TABLE_HORZ][0] = x;
951   bb[TABLE_HORZ][1] = a->width;
952   bb[TABLE_VERT][0] = y;
953   bb[TABLE_VERT][1] = a->length;
954
955   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
956
957   a->y = 1;
958 }
959
960 void
961 ascii_test_set_length (struct output_driver *driver, int y, int length)
962 {
963   struct ascii_driver *a = ascii_driver_cast (driver);
964
965   if (a->file == NULL && !ascii_open_page (a))
966     return;
967
968   if (y < 0 || y >= a->length)
969     return;
970   u8_line_set_length (&a->lines[y], length);
971 }
972 \f
973 /* ascii_close_page () and support routines. */
974
975 #if HAVE_DECL_SIGWINCH
976 static struct ascii_driver *the_driver;
977
978 static void
979 winch_handler (int signum UNUSED)
980 {
981   update_page_size (the_driver, false);
982 }
983 #endif
984
985 static bool
986 ascii_open_page (struct ascii_driver *a)
987 {
988   int i;
989
990   if (a->error)
991     return false;
992
993   if (a->file == NULL)
994     {
995       a->file = fn_open (a->file_name, a->append ? "a" : "w");
996       if (a->file != NULL)
997         {
998           if ( isatty (fileno (a->file)))
999             {
1000 #if HAVE_DECL_SIGWINCH
1001               struct sigaction action;
1002               sigemptyset (&action.sa_mask);
1003               action.sa_flags = 0;
1004               action.sa_handler = winch_handler;
1005               the_driver = a;
1006               sigaction (SIGWINCH, &action, NULL);
1007 #endif
1008               a->auto_width = true;
1009               a->auto_length = true;
1010             }
1011         }
1012       else
1013         {
1014           msg_error (errno, _("ascii: opening output file `%s'"),
1015                  a->file_name);
1016           a->error = true;
1017           return false;
1018         }
1019     }
1020
1021   a->page_number++;
1022
1023   reallocate_lines (a);
1024
1025   for (i = 0; i < a->length; i++)
1026     u8_line_clear (&a->lines[i]);
1027
1028   return true;
1029 }
1030
1031 static void
1032 output_title_line (FILE *out, int width, const char *left, const char *right)
1033 {
1034   struct string s = DS_EMPTY_INITIALIZER;
1035   ds_put_byte_multiple (&s, ' ', width);
1036   if (left != NULL)
1037     {
1038       size_t length = MIN (strlen (left), width);
1039       memcpy (ds_end (&s) - width, left, length);
1040     }
1041   if (right != NULL)
1042     {
1043       size_t length = MIN (strlen (right), width);
1044       memcpy (ds_end (&s) - length, right, length);
1045     }
1046   ds_put_byte (&s, '\n');
1047   fputs (ds_cstr (&s), out);
1048   ds_destroy (&s);
1049 }
1050
1051 static void
1052 ascii_close_page (struct ascii_driver *a)
1053 {
1054   bool any_blank;
1055   int i, y;
1056
1057   a->y = 0;
1058   if (a->file == NULL)
1059     return;
1060
1061   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1062       && !a->paginate && a->page_number > 1)
1063     putc ('\n', a->file);
1064
1065   for (i = 0; i < a->top_margin; i++)
1066     putc ('\n', a->file);
1067   if (a->headers)
1068     {
1069       char *r1, *r2;
1070
1071       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1072       r2 = xasprintf ("%s - %s" , version, host_system);
1073
1074       output_title_line (a->file, a->width, a->title, r1);
1075       output_title_line (a->file, a->width, a->subtitle, r2);
1076       putc ('\n', a->file);
1077
1078       free (r1);
1079       free (r2);
1080     }
1081
1082   any_blank = false;
1083   for (y = 0; y < a->allocated_lines; y++)
1084     {
1085       struct u8_line *line = &a->lines[y];
1086
1087       if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1088         any_blank = true;
1089       else
1090         {
1091           if (any_blank)
1092             {
1093               putc ('\n', a->file);
1094               any_blank = false;
1095             }
1096
1097           while (ds_chomp_byte (&line->s, ' '))
1098             continue;
1099           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1100           putc ('\n', a->file);
1101         }
1102     }
1103   if (!a->squeeze_blank_lines)
1104     for (y = a->allocated_lines; y < a->length; y++)
1105       putc ('\n', a->file);
1106
1107   for (i = 0; i < a->bottom_margin; i++)
1108     putc ('\n', a->file);
1109   if (a->paginate)
1110     putc ('\f', a->file);
1111 }