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