3548844693d09380dbfbe73a5ac696b3595b88d9
[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 x, 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->inline_contents.options = TAB_LEFT;
413   cell->inline_contents.text = CONST_CAST (char *, caption);
414   cell->inline_contents.table = NULL;
415   cell->contents = &cell->inline_contents;
416   cell->n_contents = 1;
417   cell->destructor = NULL;
418 }
419
420 static void
421 ascii_output_table_item (struct ascii_driver *a,
422                          const struct table_item *table_item)
423 {
424   const char *caption = table_item_get_caption (table_item);
425   struct render_params params;
426   struct render_pager *p;
427   int caption_height;
428   int i;
429
430   update_page_size (a, false);
431
432   if (caption != NULL)
433     {
434       /* XXX doesn't do well with very large captions */
435       struct table_cell cell;
436       ascii_init_caption_cell (caption, &cell);
437       caption_height = ascii_measure_cell_height (a, &cell, a->width);
438     }
439   else
440     caption_height = 0;
441
442   params.draw_line = ascii_draw_line;
443   params.measure_cell_width = ascii_measure_cell_width;
444   params.measure_cell_height = ascii_measure_cell_height;
445   params.adjust_break = NULL;
446   params.draw_cell = ascii_draw_cell;
447   params.aux = a;
448   params.size[H] = a->width;
449   params.size[V] = a->length - caption_height;
450   params.font_size[H] = 1;
451   params.font_size[V] = 1;
452   for (i = 0; i < RENDER_N_LINES; i++)
453     {
454       int width = i == RENDER_LINE_NONE ? 0 : 1;
455       params.line_widths[H][i] = width;
456       params.line_widths[V][i] = width;
457     }
458   for (i = 0; i < TABLE_N_AXES; i++)
459     params.min_break[i] = a->min_break[i];
460
461   if (a->file == NULL && !ascii_open_page (a))
462     return;
463
464   p = render_pager_create (render_page_create (&params, table_item_get_table (
465                                                  table_item)));
466   while (render_pager_has_next (p))
467     {
468       int used;
469
470       if (a->y > 0)
471         a->y++;
472       a->y += caption_height;
473       used = render_pager_draw_next (p, a->length - a->y);
474       if (used == 0)
475         {
476           assert (a->y > 0);
477           ascii_close_page (a);
478           if (!ascii_open_page (a))
479             break;
480         }
481       else
482         {
483           if (caption_height)
484             {
485               struct table_cell cell;
486               int bb[TABLE_N_AXES][2];
487
488               ascii_init_caption_cell (caption, &cell);
489               bb[H][0] = 0;
490               bb[H][1] = a->width;
491               bb[V][0] = 0;
492               bb[V][1] = caption_height;
493               a->y -= caption_height;
494               ascii_draw_cell (a, &cell, bb, bb);
495               a->y += caption_height;
496               caption_height = 0;
497             }
498           a->y += used;
499         }
500     }
501   render_pager_destroy (p);
502 }
503
504 static void
505 ascii_output_text (struct ascii_driver *a, const char *text)
506 {
507   struct table_item *table_item;
508
509   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
510   ascii_output_table_item (a, table_item);
511   table_item_unref (table_item);
512 }
513
514 static void
515 ascii_submit (struct output_driver *driver,
516               const struct output_item *output_item)
517 {
518   struct ascii_driver *a = ascii_driver_cast (driver);
519
520   output_driver_track_current_command (output_item, &a->command_name);
521
522   if (a->error)
523     return;
524
525   if (is_table_item (output_item))
526     ascii_output_table_item (a, to_table_item (output_item));
527 #ifdef HAVE_CAIRO
528   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
529     {
530       struct chart_item *chart_item = to_chart_item (output_item);
531       char *file_name;
532
533       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
534                                      a->chart_cnt++,
535                                      &a->fg, 
536                                      &a->bg);
537       if (file_name != NULL)
538         {
539           struct text_item *text_item;
540
541           text_item = text_item_create_format (
542             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
543
544           ascii_submit (driver, &text_item->output_item);
545           text_item_unref (text_item);
546           free (file_name);
547         }
548     }
549 #endif  /* HAVE_CAIRO */
550   else if (is_text_item (output_item))
551     {
552       const struct text_item *text_item = to_text_item (output_item);
553       enum text_item_type type = text_item_get_type (text_item);
554       const char *text = text_item_get_text (text_item);
555
556       switch (type)
557         {
558         case TEXT_ITEM_TITLE:
559           free (a->title);
560           a->title = xstrdup (text);
561           break;
562
563         case TEXT_ITEM_SUBTITLE:
564           free (a->subtitle);
565           a->subtitle = xstrdup (text);
566           break;
567
568         case TEXT_ITEM_COMMAND_OPEN:
569         case TEXT_ITEM_COMMAND_CLOSE:
570           break;
571
572         case TEXT_ITEM_BLANK_LINE:
573           if (a->y > 0)
574             a->y++;
575           break;
576
577         case TEXT_ITEM_EJECT_PAGE:
578           if (a->y > 0)
579             ascii_close_page (a);
580           break;
581
582         default:
583           ascii_output_text (a, text);
584           break;
585         }
586     }
587   else if (is_message_item (output_item))
588     {
589       const struct message_item *message_item = to_message_item (output_item);
590       const struct msg *msg = message_item_get_msg (message_item);
591       char *s = msg_to_string (msg, a->command_name);
592       ascii_output_text (a, s);
593       free (s);
594     }
595 }
596
597 const struct output_driver_factory txt_driver_factory =
598   { "txt", "-", ascii_create };
599 const struct output_driver_factory list_driver_factory =
600   { "list", "-", ascii_create };
601
602 static const struct output_driver_class ascii_driver_class =
603   {
604     "text",
605     ascii_destroy,
606     ascii_submit,
607     ascii_flush,
608   };
609 \f
610 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
611                             int n);
612 static void ascii_layout_cell (struct ascii_driver *,
613                                const struct table_cell *,
614                                int bb[TABLE_N_AXES][2],
615                                int clip[TABLE_N_AXES][2],
616                                int *width, int *height);
617
618 static void
619 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
620                  enum render_line_style styles[TABLE_N_AXES][2])
621 {
622   struct ascii_driver *a = a_;
623   char mbchar[6];
624   int x0, y0, x1, y1;
625   ucs4_t uc;
626   int mblen;
627   int x, y;
628
629   /* Clip to the page. */
630   x0 = MAX (bb[H][0] + a->x, 0);
631   y0 = MAX (bb[V][0] + a->y, 0);
632   x1 = MIN (bb[H][1] + a->x, a->width);
633   y1 = MIN (bb[V][1] + a->y, a->length);
634   if (x1 <= 0 || y1 <= 0 || x0 >= a->width || y0 >= a->length)
635     return;
636
637   /* Draw. */
638   uc = a->box[make_box_index (styles[V][0], styles[V][1],
639                               styles[H][0], styles[H][1])];
640   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
641   for (y = y0; y < y1; y++)
642     {
643       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
644       for (x = x0; x < x1; x++)
645         {
646           memcpy (p, mbchar, mblen);
647           p += mblen;
648         }
649     }
650 }
651
652 static void
653 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
654                           int *min_width, int *max_width)
655 {
656   struct ascii_driver *a = a_;
657   int bb[TABLE_N_AXES][2];
658   int clip[TABLE_N_AXES][2];
659   int h;
660
661   bb[H][0] = 0;
662   bb[H][1] = INT_MAX;
663   bb[V][0] = 0;
664   bb[V][1] = INT_MAX;
665   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
666   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
667
668   if (cell->n_contents != 1
669       || cell->contents[0].table
670       || strchr (cell->contents[0].text, ' '))
671     {
672       bb[H][1] = 1;
673       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
674     }
675   else
676     *min_width = *max_width;
677 }
678
679 static int
680 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
681 {
682   struct ascii_driver *a = a_;
683   int bb[TABLE_N_AXES][2];
684   int clip[TABLE_N_AXES][2];
685   int w, h;
686
687   bb[H][0] = 0;
688   bb[H][1] = width;
689   bb[V][0] = 0;
690   bb[V][1] = INT_MAX;
691   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
692   ascii_layout_cell (a, cell, bb, clip, &w, &h);
693   return h;
694 }
695
696 static void
697 ascii_draw_cell (void *a_, const struct table_cell *cell,
698                  int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
699 {
700   struct ascii_driver *a = a_;
701   int w, h;
702
703   ascii_layout_cell (a, cell, bb, clip, &w, &h);
704 }
705
706 static char *
707 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
708 {
709   assert (y < a->allocated_lines);
710   return u8_line_reserve (&a->lines[y], x0, x1, n);
711 }
712
713 static void
714 text_draw (struct ascii_driver *a, unsigned int options,
715            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
716            int y, const uint8_t *string, int n, size_t width)
717 {
718   int x0 = MAX (0, clip[H][0] + a->x);
719   int y0 = MAX (0, clip[V][0] + a->y);
720   int x1 = MIN (a->width, clip[H][1] + a->x);
721   int y1 = MIN (a->length, clip[V][1] + a->y);
722   int x;
723
724   y += a->y;
725   if (y < y0 || y >= y1)
726     return;
727
728   switch (options & TAB_ALIGNMENT)
729     {
730     case TAB_LEFT:
731       x = bb[H][0];
732       break;
733     case TAB_CENTER:
734       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
735       break;
736     case TAB_RIGHT:
737       x = bb[H][1] - width;
738       break;
739     default:
740       NOT_REACHED ();
741     }
742   x += a->x;
743   if (x >= x1)
744     return;
745
746   while (x < x0)
747     {
748       ucs4_t uc;
749       int mblen;
750       int w;
751
752       if (n == 0)
753         return;
754       mblen = u8_mbtouc (&uc, string, n);
755
756       string += mblen;
757       n -= mblen;
758
759       w = uc_width (uc, "UTF-8");
760       if (w > 0)
761         {
762           x += w;
763           width -= w;
764         }
765     }
766   if (n == 0)
767     return;
768
769   if (x + width > x1)
770     {
771       int ofs;
772
773       ofs = width = 0;
774       for (ofs = 0; ofs < n; )
775         {
776           ucs4_t uc;
777           int mblen;
778           int w;
779
780           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
781
782           w = uc_width (uc, "UTF-8");
783           if (w > 0)
784             {
785               if (width + w > x1 - x)
786                 break;
787               width += w;
788             }
789           ofs += mblen;
790         }
791       n = ofs;
792       if (n == 0)
793         return;
794     }
795
796   if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
797     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
798   else
799     {
800       size_t n_out;
801       size_t ofs;
802       char *out;
803       int mblen;
804
805       /* First figure out how many bytes need to be inserted. */
806       n_out = n;
807       for (ofs = 0; ofs < n; ofs += mblen)
808         {
809           ucs4_t uc;
810           int w;
811
812           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
813           w = uc_width (uc, "UTF-8");
814
815           if (w > 0)
816             n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
817         }
818
819       /* Then insert them. */
820       out = ascii_reserve (a, y, x, x + width, n_out);
821       for (ofs = 0; ofs < n; ofs += mblen)
822         {
823           ucs4_t uc;
824           int w;
825
826           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
827           w = uc_width (uc, "UTF-8");
828
829           if (w > 0)
830             {
831               if (a->emphasis == EMPH_UNDERLINE)
832                 *out++ = '_';
833               else
834                 out = mempcpy (out, string + ofs, mblen);
835               *out++ = '\b';
836             }
837           out = mempcpy (out, string + ofs, mblen);
838         }
839     }
840 }
841
842 static int
843 ascii_layout_cell_text (struct ascii_driver *a,
844                         const struct cell_contents *contents,
845                         int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
846                         int *widthp)
847 {
848   size_t length = strlen (contents->text);
849   char *breaks;
850   int bb_width;
851   size_t pos;
852   int y;
853
854   y = bb[V][0];
855   if (length == 0)
856     return y;
857
858   breaks = xmalloc (length + 1);
859   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, contents->text), length,
860                           "UTF-8", breaks);
861   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
862                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
863
864   pos = 0;
865   bb_width = bb[H][1] - bb[H][0];
866   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
867     {
868       const uint8_t *line = CHAR_CAST (const uint8_t *, contents->text + pos);
869       const char *b = breaks + pos;
870       size_t n = length - pos;
871
872       size_t last_break_ofs = 0;
873       int last_break_width = 0;
874       int width = 0;
875       size_t graph_ofs;
876       size_t ofs;
877
878       for (ofs = 0; ofs < n; )
879         {
880           ucs4_t uc;
881           int mblen;
882           int w;
883
884           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
885           if (b[ofs] == UC_BREAK_MANDATORY)
886             break;
887           else if (b[ofs] == UC_BREAK_POSSIBLE)
888             {
889               last_break_ofs = ofs;
890               last_break_width = width;
891             }
892
893           w = uc_width (uc, "UTF-8");
894           if (w > 0)
895             {
896               if (width + w > bb_width)
897                 {
898                   if (isspace (line[ofs]))
899                     break;
900                   else if (last_break_ofs != 0)
901                     {
902                       ofs = last_break_ofs;
903                       width = last_break_width;
904                       break;
905                     }
906                 }
907               width += w;
908             }
909           ofs += mblen;
910         }
911
912       /* Trim any trailing spaces off the end of the text to be drawn. */
913       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
914         if (!isspace (line[graph_ofs - 1]))
915           break;
916       width -= ofs - graph_ofs;
917
918       /* Draw text. */
919       text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
920
921       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
922          past any spaces past the end of the line (but not past a new-line). */
923       if (b[ofs] == UC_BREAK_MANDATORY)
924         ofs++;
925       else
926         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
927           ofs++;
928
929       if (width > *widthp)
930         *widthp = width;
931       pos += ofs;
932     }
933
934   free (breaks);
935
936   return y;
937 }
938
939 static int
940 ascii_layout_subtable (struct ascii_driver *a,
941                        const struct cell_contents *contents,
942                        int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2] UNUSED,
943                        int *widthp)
944 {
945   const struct table *table = contents->table;
946   struct render_params params;
947   struct render_pager *p;
948   int r[TABLE_N_AXES][2];
949   int width, height;
950   int i;
951
952   params.draw_line = ascii_draw_line;
953   params.measure_cell_width = ascii_measure_cell_width;
954   params.measure_cell_height = ascii_measure_cell_height;
955   params.adjust_break = NULL;
956   params.draw_cell = ascii_draw_cell,
957   params.aux = a;
958   params.size[H] = bb[TABLE_HORZ][1] - bb[TABLE_HORZ][0];
959   params.size[V] = bb[TABLE_VERT][1] - bb[TABLE_VERT][0];
960   params.font_size[H] = 1;
961   params.font_size[V] = 1;
962   for (i = 0; i < RENDER_N_LINES; i++)
963     {
964       int width = i == RENDER_LINE_NONE ? 0 : 1;
965       params.line_widths[H][i] = width;
966       params.line_widths[V][i] = width;
967     }
968
969   p = render_pager_create (render_page_create (&params, table));
970   width = render_pager_get_size (p, TABLE_HORZ);
971   height = render_pager_get_size (p, TABLE_VERT);
972
973   /* r = intersect(bb, clip) - bb. */
974   for (i = 0; i < TABLE_N_AXES; i++)
975     {
976       r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
977       r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
978     }
979
980   if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
981     {
982       unsigned int alignment = contents->options & TAB_ALIGNMENT;
983       int save_x = a->x;
984
985       a->x += bb[TABLE_HORZ][0];
986       if (alignment == TAB_RIGHT)
987         a->x += params.size[H] - width;
988       else if (alignment == TAB_CENTER)
989         a->x += (params.size[H] - width) / 2;
990       a->y += bb[TABLE_VERT][0];
991       render_pager_draw (p);
992       a->y -= bb[TABLE_VERT][0];
993       a->x = save_x;
994     }
995   render_pager_destroy (p);
996
997   if (width > *widthp)
998     *widthp = width;
999   return bb[V][0] + height;
1000 }
1001
1002 static void
1003 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
1004                    int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1005                    int *widthp, int *heightp)
1006 {
1007   int bb[TABLE_N_AXES][2];
1008   size_t i;
1009
1010   *widthp = 0;
1011   *heightp = 0;
1012
1013   memcpy (bb, bb_, sizeof bb);
1014   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1015     {
1016       const struct cell_contents *contents = &cell->contents[i];
1017
1018       /* Put a blank line between contents. */
1019       if (i > 0)
1020         {
1021           bb[V][0]++;
1022           if (bb[V][0] >= bb[V][1])
1023             break;
1024         }
1025
1026       if (contents->text)
1027         bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp);
1028       else
1029         bb[V][0] = ascii_layout_subtable (a, contents, bb, clip, widthp);
1030     }
1031   *heightp = bb[V][0] - bb_[V][0];
1032 }
1033
1034 void
1035 ascii_test_write (struct output_driver *driver,
1036                   const char *s, int x, int y, unsigned int options)
1037 {
1038   struct ascii_driver *a = ascii_driver_cast (driver);
1039   struct cell_contents contents;
1040   struct table_cell cell;
1041   int bb[TABLE_N_AXES][2];
1042   int width, height;
1043
1044   if (a->file == NULL && !ascii_open_page (a))
1045     return;
1046   a->y = 0;
1047
1048   contents.options = options | TAB_LEFT;
1049   contents.text = CONST_CAST (char *, s);
1050   contents.table = NULL;
1051
1052   memset (&cell, 0, sizeof cell);
1053   cell.contents = &contents;
1054   cell.n_contents = 1;
1055
1056   bb[TABLE_HORZ][0] = x;
1057   bb[TABLE_HORZ][1] = a->width;
1058   bb[TABLE_VERT][0] = y;
1059   bb[TABLE_VERT][1] = a->length;
1060
1061   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
1062
1063   a->y = 1;
1064 }
1065
1066 void
1067 ascii_test_set_length (struct output_driver *driver, int y, int length)
1068 {
1069   struct ascii_driver *a = ascii_driver_cast (driver);
1070
1071   if (a->file == NULL && !ascii_open_page (a))
1072     return;
1073
1074   if (y < 0 || y >= a->length)
1075     return;
1076   u8_line_set_length (&a->lines[y], length);
1077 }
1078 \f
1079 /* ascii_close_page () and support routines. */
1080
1081 #if HAVE_DECL_SIGWINCH
1082 static struct ascii_driver *the_driver;
1083
1084 static void
1085 winch_handler (int signum UNUSED)
1086 {
1087   update_page_size (the_driver, false);
1088 }
1089 #endif
1090
1091 static bool
1092 ascii_open_page (struct ascii_driver *a)
1093 {
1094   int i;
1095
1096   if (a->error)
1097     return false;
1098
1099   if (a->file == NULL)
1100     {
1101       a->file = fn_open (a->file_name, a->append ? "a" : "w");
1102       if (a->file != NULL)
1103         {
1104           if ( isatty (fileno (a->file)))
1105             {
1106 #if HAVE_DECL_SIGWINCH
1107               struct sigaction action;
1108               sigemptyset (&action.sa_mask);
1109               action.sa_flags = 0;
1110               action.sa_handler = winch_handler;
1111               the_driver = a;
1112               sigaction (SIGWINCH, &action, NULL);
1113 #endif
1114               a->auto_width = true;
1115               a->auto_length = true;
1116             }
1117         }
1118       else
1119         {
1120           msg_error (errno, _("ascii: opening output file `%s'"),
1121                  a->file_name);
1122           a->error = true;
1123           return false;
1124         }
1125     }
1126
1127   a->page_number++;
1128
1129   reallocate_lines (a);
1130
1131   for (i = 0; i < a->length; i++)
1132     u8_line_clear (&a->lines[i]);
1133
1134   return true;
1135 }
1136
1137 static void
1138 output_title_line (FILE *out, int width, const char *left, const char *right)
1139 {
1140   struct string s = DS_EMPTY_INITIALIZER;
1141   ds_put_byte_multiple (&s, ' ', width);
1142   if (left != NULL)
1143     {
1144       size_t length = MIN (strlen (left), width);
1145       memcpy (ds_end (&s) - width, left, length);
1146     }
1147   if (right != NULL)
1148     {
1149       size_t length = MIN (strlen (right), width);
1150       memcpy (ds_end (&s) - length, right, length);
1151     }
1152   ds_put_byte (&s, '\n');
1153   fputs (ds_cstr (&s), out);
1154   ds_destroy (&s);
1155 }
1156
1157 static void
1158 ascii_close_page (struct ascii_driver *a)
1159 {
1160   bool any_blank;
1161   int i, y;
1162
1163   a->y = 0;
1164   if (a->file == NULL)
1165     return;
1166
1167   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1168       && !a->paginate && a->page_number > 1)
1169     putc ('\n', a->file);
1170
1171   for (i = 0; i < a->top_margin; i++)
1172     putc ('\n', a->file);
1173   if (a->headers)
1174     {
1175       char *r1, *r2;
1176
1177       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1178       r2 = xasprintf ("%s - %s" , version, host_system);
1179
1180       output_title_line (a->file, a->width, a->title, r1);
1181       output_title_line (a->file, a->width, a->subtitle, r2);
1182       putc ('\n', a->file);
1183
1184       free (r1);
1185       free (r2);
1186     }
1187
1188   any_blank = false;
1189   for (y = 0; y < a->allocated_lines; y++)
1190     {
1191       struct u8_line *line = &a->lines[y];
1192
1193       if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1194         any_blank = true;
1195       else
1196         {
1197           if (any_blank)
1198             {
1199               putc ('\n', a->file);
1200               any_blank = false;
1201             }
1202
1203           while (ds_chomp_byte (&line->s, ' '))
1204             continue;
1205           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1206           putc ('\n', a->file);
1207         }
1208     }
1209   if (!a->squeeze_blank_lines)
1210     for (y = a->allocated_lines; y < a->length; y++)
1211       putc ('\n', a->file);
1212
1213   for (i = 0; i < a->bottom_margin; i++)
1214     putc ('\n', a->file);
1215   if (a->paginate)
1216     putc ('\f', a->file);
1217 }