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