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