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