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