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