ascii: Fix assertion in ascii_output_table_item().
[pspp] / src / output / ascii.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <ctype.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <signal.h>
25 #include <unilbrk.h>
26 #include <unistd.h>
27 #include <unistr.h>
28 #include <uniwidth.h>
29
30 #include "data/file-name.h"
31 #include "data/file-handle-def.h"
32 #include "data/settings.h"
33 #include "libpspp/assertion.h"
34 #include "libpspp/cast.h"
35 #include "libpspp/compiler.h"
36 #include "libpspp/message.h"
37 #include "libpspp/start-date.h"
38 #include "libpspp/string-map.h"
39 #include "libpspp/u8-line.h"
40 #include "libpspp/version.h"
41 #include "output/ascii.h"
42 #include "output/cairo.h"
43 #include "output/chart-item-provider.h"
44 #include "output/driver-provider.h"
45 #include "output/message-item.h"
46 #include "output/options.h"
47 #include "output/render.h"
48 #include "output/tab.h"
49 #include "output/table-item.h"
50 #include "output/text-item.h"
51
52 #include "gl/minmax.h"
53 #include "gl/xalloc.h"
54
55 #include "gettext.h"
56 #define _(msgid) gettext (msgid)
57
58 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
59 #define H TABLE_HORZ
60 #define V TABLE_VERT
61
62 #define N_BOX (RENDER_N_LINES * RENDER_N_LINES \
63                * RENDER_N_LINES * RENDER_N_LINES)
64
65 static const ucs4_t ascii_box_chars[N_BOX] =
66   {
67     ' ', '|', '#',
68     '-', '+', '#',
69     '=', '#', '#',
70     '|', '|', '#',
71     '+', '+', '#',
72     '#', '#', '#',
73     '#', '#', '#',
74     '#', '#', '#',
75     '#', '#', '#',
76     '-', '+', '#',
77     '-', '+', '#',
78     '#', '#', '#',
79     '+', '+', '#',
80     '+', '+', '#',
81     '#', '#', '#',
82     '#', '#', '#',
83     '#', '#', '#',
84     '#', '#', '#',
85     '=', '#', '#',
86     '#', '#', '#',
87     '=', '#', '#',
88     '#', '#', '#',
89     '#', '#', '#',
90     '#', '#', '#',
91     '#', '#', '#',
92     '#', '#', '#',
93     '#', '#', '#',
94   };
95
96 static const ucs4_t unicode_box_chars[N_BOX] =
97   {
98     0x0020, 0x2575, 0x2551,
99     0x2574, 0x256f, 0x255c,
100     0x2550, 0x255b, 0x255d,
101     0x2577, 0x2502, 0x2551,
102     0x256e, 0x2524, 0x2562,
103     0x2555, 0x2561, 0x2563,
104     0x2551, 0x2551, 0x2551,
105     0x2556, 0x2562, 0x2562,
106     0x2557, 0x2563, 0x2563,
107     0x2576, 0x2570, 0x2559,
108     0x2500, 0x2534, 0x2568,
109     0x2550, 0x2567, 0x2569,
110     0x256d, 0x251c, 0x255f,
111     0x252c, 0x253c, 0x256a,
112     0x2564, 0x256a, 0x256c,
113     0x2553, 0x255f, 0x255f,
114     0x2565, 0x256b, 0x256b,
115     0x2566, 0x256c, 0x256c,
116     0x2550, 0x2558, 0x255a,
117     0x2550, 0x2567, 0x2569,
118     0x2550, 0x2567, 0x2569,
119     0x2552, 0x255e, 0x2560,
120     0x2564, 0x256a, 0x256c,
121     0x2564, 0x256a, 0x256c,
122     0x2554, 0x2560, 0x2560,
123     0x2560, 0x256c, 0x256c,
124     0x2566, 0x256c, 0x256c,
125   };
126
127 static inline int
128 make_box_index (int left, int right, int top, int bottom)
129 {
130   int start_side = left;
131   int end_side = right;
132   if (render_direction_rtl ())
133     {
134       start_side = right;
135       end_side = left;
136     }
137
138   return ((end_side * RENDER_N_LINES + bottom) * RENDER_N_LINES + start_side) * RENDER_N_LINES + top;
139 }
140
141 /* How to emphasize text. */
142 enum emphasis_style
143   {
144     EMPH_BOLD,                  /* Overstrike for bold. */
145     EMPH_UNDERLINE,             /* Overstrike for underlining. */
146     EMPH_NONE                   /* No emphasis. */
147   };
148
149 /* ASCII output driver. */
150 struct ascii_driver
151   {
152     struct output_driver driver;
153
154     /* User parameters. */
155     bool append;                /* Append if output file already exists? */
156     bool headers;               /* Print headers at top of page? */
157     bool paginate;              /* Insert formfeeds? */
158     bool squeeze_blank_lines;   /* Squeeze multiple blank lines into one? */
159     enum emphasis_style emphasis; /* How to emphasize text. */
160     char *chart_file_name;      /* Name of files used for charts. */
161
162 #ifdef HAVE_CAIRO
163     /* Colours for charts */
164     struct xr_color fg;
165     struct xr_color bg;
166 #endif
167
168     int width;                  /* Page width. */
169     int length;                 /* Page length minus margins and header. */
170     bool auto_width;            /* Use viewwidth as page width? */
171     bool auto_length;           /* Use viewlength as page width? */
172
173     int top_margin;             /* Top margin in lines. */
174     int bottom_margin;          /* Bottom margin in lines. */
175
176     int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
177
178     const ucs4_t *box;          /* Line & box drawing characters. */
179
180     /* Internal state. */
181     char *command_name;
182     char *title;
183     char *subtitle;
184     struct file_handle *handle;
185     FILE *file;                 /* Output file. */
186     bool error;                 /* Output error? */
187     int page_number;            /* Current page number. */
188     struct u8_line *lines;      /* Page content. */
189     int allocated_lines;        /* Number of lines allocated. */
190     int chart_cnt;              /* Number of charts so far. */
191     int x, y;
192   };
193
194 static const struct output_driver_class ascii_driver_class;
195
196 static void ascii_submit (struct output_driver *, const struct output_item *);
197
198 static int vertical_margins (const struct ascii_driver *);
199
200 static bool update_page_size (struct ascii_driver *, bool issue_error);
201 static int parse_page_size (struct driver_option *);
202
203 static void ascii_close_page (struct ascii_driver *);
204 static bool ascii_open_page (struct ascii_driver *);
205
206 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
207                              enum render_line_style styles[TABLE_N_AXES][2]);
208 static void ascii_measure_cell_width (void *, const struct table_cell *,
209                                       int footnote_idx, int *min, int *max);
210 static int ascii_measure_cell_height (void *, const struct table_cell *,
211                                       int footnote_idx, int width);
212 static void ascii_draw_cell (void *, const struct table_cell *,
213                              int footnote_idx, int bb[TABLE_N_AXES][2],
214                              int clip[TABLE_N_AXES][2]);
215
216 static void
217 reallocate_lines (struct ascii_driver *a)
218 {
219   if (a->length > a->allocated_lines)
220     {
221       int i;
222       a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
223       for (i = a->allocated_lines; i < a->length; i++)
224         u8_line_init (&a->lines[i]);
225       a->allocated_lines = a->length;
226     }
227 }
228
229
230 static struct ascii_driver *
231 ascii_driver_cast (struct output_driver *driver)
232 {
233   assert (driver->class == &ascii_driver_class);
234   return UP_CAST (driver, struct ascii_driver, driver);
235 }
236
237 static struct driver_option *
238 opt (struct output_driver *d, struct string_map *options, const char *key,
239      const char *default_value)
240 {
241   return driver_option_get (d, options, key, default_value);
242 }
243
244 static struct output_driver *
245 ascii_create (struct  file_handle *fh, enum settings_output_devices device_type,
246               struct string_map *o)
247 {
248   enum { BOX_ASCII, BOX_UNICODE } box;
249   int min_break[TABLE_N_AXES];
250   struct output_driver *d;
251   struct ascii_driver *a;
252   int paper_length;
253
254   a = xzalloc (sizeof *a);
255   d = &a->driver;
256   output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
257   a->append = parse_boolean (opt (d, o, "append", "false"));
258   a->headers = parse_boolean (opt (d, o, "headers", "false"));
259   a->paginate = parse_boolean (opt (d, o, "paginate", "false"));
260   a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true"));
261   a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
262                             "bold", EMPH_BOLD,
263                             "underline", EMPH_UNDERLINE,
264                             "none", EMPH_NONE,
265                             NULL_SENTINEL);
266
267   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
268   a->handle = fh;
269
270   a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX);
271   a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX);
272
273   min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
274   min_break[V] = parse_int (opt (d, o, "min-vbreak", "-1"), -1, INT_MAX);
275
276   a->width = parse_page_size (opt (d, o, "width", "79"));
277   paper_length = parse_page_size (opt (d, o, "length", "66"));
278   a->auto_width = a->width < 0;
279   a->auto_length = paper_length < 0;
280   a->length = paper_length - vertical_margins (a);
281   a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
282   a->min_break[V] = min_break[V] >= 0 ? min_break[V] : a->length / 2;
283 #ifdef HAVE_CAIRO
284   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
285   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
286 #endif
287   box = parse_enum (opt (d, o, "box", "ascii"),
288                     "ascii", BOX_ASCII,
289                     "unicode", BOX_UNICODE,
290                     NULL_SENTINEL);
291   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
292
293   a->command_name = NULL;
294   a->title = xstrdup ("");
295   a->subtitle = xstrdup ("");
296   a->file = NULL;
297   a->error = false;
298   a->page_number = 0;
299   a->lines = NULL;
300   a->allocated_lines = 0;
301   a->chart_cnt = 1;
302
303   if (!update_page_size (a, true))
304     goto error;
305
306   return d;
307
308 error:
309   output_driver_destroy (d);
310   return NULL;
311 }
312
313 static int
314 parse_page_size (struct driver_option *option)
315 {
316   int dim = atol (option->default_value);
317
318   if (option->value != NULL)
319     {
320       if (!strcmp (option->value, "auto"))
321         dim = -1;
322       else
323         {
324           int value;
325           char *tail;
326
327           errno = 0;
328           value = strtol (option->value, &tail, 0);
329           if (dim >= 1 && errno != ERANGE && *tail == '\0')
330             dim = value;
331           else
332             msg (MW, _("%s: %s must be positive integer or `auto'"),
333                    option->driver_name, option->name);
334         }
335     }
336
337   driver_option_destroy (option);
338
339   return dim;
340 }
341
342 static int
343 vertical_margins (const struct ascii_driver *a)
344 {
345   return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
346 }
347
348 /* Re-calculates the page width and length based on settings,
349    margins, and, if "auto" is set, the size of the user's
350    terminal window or GUI output window. */
351 static bool
352 update_page_size (struct ascii_driver *a, bool issue_error)
353 {
354   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
355
356   if (a->auto_width)
357     {
358       a->width = settings_get_viewwidth ();
359       a->min_break[H] = a->width / 2;
360     }
361   if (a->auto_length)
362     {
363       a->length = settings_get_viewlength () - vertical_margins (a);
364       a->min_break[V] = a->length / 2;
365     }
366
367   if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
368     {
369       if (issue_error)
370         msg (ME,
371                _("ascii: page excluding margins and headers "
372                  "must be at least %d characters wide by %d lines long, but "
373                  "as configured is only %d characters by %d lines"),
374                MIN_WIDTH, MIN_LENGTH,
375                a->width, a->length);
376       if (a->width < MIN_WIDTH)
377         a->width = MIN_WIDTH;
378       if (a->length < MIN_LENGTH)
379         a->length = MIN_LENGTH;
380       return false;
381     }
382
383   reallocate_lines (a);
384
385   return true;
386 }
387
388 static void
389 ascii_destroy (struct output_driver *driver)
390 {
391   struct ascii_driver *a = ascii_driver_cast (driver);
392   int i;
393
394   if (a->y > 0)
395     ascii_close_page (a);
396
397   if (a->file != NULL)
398     fn_close (a->handle, a->file);
399   fh_unref (a->handle);
400   free (a->command_name);
401   free (a->title);
402   free (a->subtitle);
403   free (a->chart_file_name);
404   for (i = 0; i < a->allocated_lines; i++)
405     u8_line_destroy (&a->lines[i]);
406   free (a->lines);
407   free (a);
408 }
409
410 static void
411 ascii_flush (struct output_driver *driver)
412 {
413   struct ascii_driver *a = ascii_driver_cast (driver);
414   if (a->y > 0)
415     {
416       ascii_close_page (a);
417
418       if (fn_close (a->handle, a->file) != 0)
419         msg_error (errno, _("ascii: closing output file `%s'"), fh_get_file_name (a->handle));
420       a->file = NULL;
421     }
422 }
423
424 static void
425 ascii_output_table_item (struct ascii_driver *a,
426                          const struct table_item *table_item)
427 {
428   struct render_params params;
429   struct render_pager *p;
430   int i;
431
432   update_page_size (a, false);
433
434   params.draw_line = ascii_draw_line;
435   params.measure_cell_width = ascii_measure_cell_width;
436   params.measure_cell_height = ascii_measure_cell_height;
437   params.adjust_break = NULL;
438   params.draw_cell = ascii_draw_cell;
439   params.aux = a;
440   params.size[H] = a->width;
441   params.size[V] = a->length;
442   params.font_size[H] = 1;
443   params.font_size[V] = 1;
444   for (i = 0; i < RENDER_N_LINES; i++)
445     {
446       int width = i == RENDER_LINE_NONE ? 0 : 1;
447       params.line_widths[H][i] = width;
448       params.line_widths[V][i] = width;
449     }
450   for (i = 0; i < TABLE_N_AXES; i++)
451     params.min_break[i] = a->min_break[i];
452
453   if (a->file == NULL && !ascii_open_page (a))
454     return;
455
456   p = render_pager_create (&params, table_item);
457   while (render_pager_has_next (p))
458     {
459       int used;
460
461       if (a->y > 0)
462         a->y++;
463       used = render_pager_draw_next (p, a->length - a->y);
464       if (used == 0)
465         {
466           /* Make sure that we're not in a loop where the table we're rendering
467              can't be broken to fit on a page.  (The check on
468              render_pager_has_next() allows for objects that turn out to be
469              empty when we try to render them.)  */
470           assert (a->y > 0 || !render_pager_has_next (p));
471
472           ascii_close_page (a);
473           if (!ascii_open_page (a))
474             break;
475         }
476       else
477         a->y += used;
478     }
479   render_pager_destroy (p);
480 }
481
482 static void
483 ascii_output_text (struct ascii_driver *a, const char *text)
484 {
485   struct table_item *table_item;
486
487   table_item = table_item_create (table_from_string (TAB_LEFT, text),
488                                   NULL, NULL);
489   ascii_output_table_item (a, table_item);
490   table_item_unref (table_item);
491 }
492
493 static void
494 ascii_submit (struct output_driver *driver,
495               const struct output_item *output_item)
496 {
497   struct ascii_driver *a = ascii_driver_cast (driver);
498
499   output_driver_track_current_command (output_item, &a->command_name);
500
501   if (a->error)
502     return;
503
504   if (is_table_item (output_item))
505     ascii_output_table_item (a, to_table_item (output_item));
506 #ifdef HAVE_CAIRO
507   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
508     {
509       struct chart_item *chart_item = to_chart_item (output_item);
510       char *file_name;
511
512       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
513                                      a->chart_cnt++,
514                                      &a->fg,
515                                      &a->bg);
516       if (file_name != NULL)
517         {
518           struct text_item *text_item;
519
520           text_item = text_item_create_format (
521             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
522
523           ascii_submit (driver, &text_item->output_item);
524           text_item_unref (text_item);
525           free (file_name);
526         }
527     }
528 #endif  /* HAVE_CAIRO */
529   else if (is_text_item (output_item))
530     {
531       const struct text_item *text_item = to_text_item (output_item);
532       enum text_item_type type = text_item_get_type (text_item);
533       const char *text = text_item_get_text (text_item);
534
535       switch (type)
536         {
537         case TEXT_ITEM_TITLE:
538           free (a->title);
539           a->title = xstrdup (text);
540           break;
541
542         case TEXT_ITEM_SUBTITLE:
543           free (a->subtitle);
544           a->subtitle = xstrdup (text);
545           break;
546
547         case TEXT_ITEM_COMMAND_OPEN:
548         case TEXT_ITEM_COMMAND_CLOSE:
549           break;
550
551         case TEXT_ITEM_BLANK_LINE:
552           if (a->y > 0)
553             a->y++;
554           break;
555
556         case TEXT_ITEM_EJECT_PAGE:
557           if (a->y > 0)
558             ascii_close_page (a);
559           break;
560
561         default:
562           ascii_output_text (a, text);
563           break;
564         }
565     }
566   else if (is_message_item (output_item))
567     {
568       const struct message_item *message_item = to_message_item (output_item);
569       const struct msg *msg = message_item_get_msg (message_item);
570       char *s = msg_to_string (msg, a->command_name);
571       ascii_output_text (a, s);
572       free (s);
573     }
574 }
575
576 const struct output_driver_factory txt_driver_factory =
577   { "txt", "-", ascii_create };
578 const struct output_driver_factory list_driver_factory =
579   { "list", "-", ascii_create };
580
581 static const struct output_driver_class ascii_driver_class =
582   {
583     "text",
584     ascii_destroy,
585     ascii_submit,
586     ascii_flush,
587   };
588 \f
589 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
590                             int n);
591 static void ascii_layout_cell (struct ascii_driver *,
592                                const struct table_cell *,
593                                int footnote_idx,
594                                int bb[TABLE_N_AXES][2],
595                                int clip[TABLE_N_AXES][2],
596                                int *width, int *height);
597
598 static void
599 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
600                  enum render_line_style styles[TABLE_N_AXES][2])
601 {
602   struct ascii_driver *a = a_;
603   char mbchar[6];
604   int x0, y0, x1, y1;
605   ucs4_t uc;
606   int mblen;
607   int x, y;
608
609   /* Clip to the page. */
610   x0 = MAX (bb[H][0] + a->x, 0);
611   y0 = MAX (bb[V][0] + a->y, 0);
612   x1 = MIN (bb[H][1] + a->x, a->width);
613   y1 = MIN (bb[V][1] + a->y, a->length);
614   if (x1 <= 0 || y1 <= 0 || x0 >= a->width || y0 >= a->length)
615     return;
616
617   /* Draw. */
618   uc = a->box[make_box_index (styles[V][0], styles[V][1],
619                               styles[H][0], styles[H][1])];
620   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
621   for (y = y0; y < y1; y++)
622     {
623       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
624       for (x = x0; x < x1; x++)
625         {
626           memcpy (p, mbchar, mblen);
627           p += mblen;
628         }
629     }
630 }
631
632 static void
633 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
634                           int footnote_idx, int *min_width, int *max_width)
635 {
636   struct ascii_driver *a = a_;
637   int bb[TABLE_N_AXES][2];
638   int clip[TABLE_N_AXES][2];
639   int h;
640
641   bb[H][0] = 0;
642   bb[H][1] = INT_MAX;
643   bb[V][0] = 0;
644   bb[V][1] = INT_MAX;
645   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
646   ascii_layout_cell (a, cell, footnote_idx, bb, clip, max_width, &h);
647
648   if (cell->n_contents != 1
649       || cell->contents[0].table
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 int
949 ascii_layout_subtable (struct ascii_driver *a,
950                        const struct cell_contents *contents,
951                        int *footnote_idx UNUSED /* XXX */,
952                        int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2] UNUSED,
953                        int *widthp)
954 {
955   struct render_params params;
956   struct render_pager *p;
957   int r[TABLE_N_AXES][2];
958   int width, height;
959   int i;
960
961   params.draw_line = ascii_draw_line;
962   params.measure_cell_width = ascii_measure_cell_width;
963   params.measure_cell_height = ascii_measure_cell_height;
964   params.adjust_break = NULL;
965   params.draw_cell = ascii_draw_cell,
966   params.aux = a;
967   params.size[H] = bb[TABLE_HORZ][1] - bb[TABLE_HORZ][0];
968   params.size[V] = bb[TABLE_VERT][1] - bb[TABLE_VERT][0];
969   params.font_size[H] = 1;
970   params.font_size[V] = 1;
971   for (i = 0; i < RENDER_N_LINES; i++)
972     {
973       int width = i == RENDER_LINE_NONE ? 0 : 1;
974       params.line_widths[H][i] = width;
975       params.line_widths[V][i] = width;
976     }
977
978   p = render_pager_create (&params, contents->table);
979   width = render_pager_get_size (p, TABLE_HORZ);
980   height = render_pager_get_size (p, TABLE_VERT);
981
982   /* r = intersect(bb, clip) - bb. */
983   for (i = 0; i < TABLE_N_AXES; i++)
984     {
985       r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
986       r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
987     }
988
989   if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
990     {
991       unsigned int alignment = contents->options & TAB_ALIGNMENT;
992       int save_x = a->x;
993
994       a->x += bb[TABLE_HORZ][0];
995       if (alignment == TAB_RIGHT)
996         a->x += params.size[H] - width;
997       else if (alignment == TAB_CENTER)
998         a->x += (params.size[H] - width) / 2;
999       a->y += bb[TABLE_VERT][0];
1000       render_pager_draw (p);
1001       a->y -= bb[TABLE_VERT][0];
1002       a->x = save_x;
1003     }
1004   render_pager_destroy (p);
1005
1006   if (width > *widthp)
1007     *widthp = width;
1008   return bb[V][0] + height;
1009 }
1010
1011 static void
1012 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
1013                    int footnote_idx,
1014                    int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1015                    int *widthp, int *heightp)
1016 {
1017   int bb[TABLE_N_AXES][2];
1018   size_t i;
1019
1020   *widthp = 0;
1021   *heightp = 0;
1022
1023   memcpy (bb, bb_, sizeof bb);
1024   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1025     {
1026       const struct cell_contents *contents = &cell->contents[i];
1027
1028       /* Put a blank line between contents. */
1029       if (i > 0)
1030         {
1031           bb[V][0]++;
1032           if (bb[V][0] >= bb[V][1])
1033             break;
1034         }
1035
1036       if (contents->text)
1037         bb[V][0] = ascii_layout_cell_text (a, contents, &footnote_idx,
1038                                            bb, clip, widthp);
1039       else
1040         bb[V][0] = ascii_layout_subtable (a, contents, &footnote_idx,
1041                                           bb, clip, widthp);
1042     }
1043   *heightp = bb[V][0] - bb_[V][0];
1044 }
1045
1046 void
1047 ascii_test_write (struct output_driver *driver,
1048                   const char *s, int x, int y, unsigned int options)
1049 {
1050   struct ascii_driver *a = ascii_driver_cast (driver);
1051   struct cell_contents contents;
1052   struct table_cell cell;
1053   int bb[TABLE_N_AXES][2];
1054   int width, height;
1055
1056   if (a->file == NULL && !ascii_open_page (a))
1057     return;
1058   a->y = 0;
1059
1060   contents.options = options | TAB_LEFT;
1061   contents.text = CONST_CAST (char *, s);
1062   contents.table = NULL;
1063   contents.n_footnotes = 0;
1064
1065   memset (&cell, 0, sizeof cell);
1066   cell.contents = &contents;
1067   cell.n_contents = 1;
1068
1069   bb[TABLE_HORZ][0] = x;
1070   bb[TABLE_HORZ][1] = a->width;
1071   bb[TABLE_VERT][0] = y;
1072   bb[TABLE_VERT][1] = a->length;
1073
1074   ascii_layout_cell (a, &cell, 0, bb, bb, &width, &height);
1075
1076   a->y = 1;
1077 }
1078
1079 void
1080 ascii_test_set_length (struct output_driver *driver, int y, int length)
1081 {
1082   struct ascii_driver *a = ascii_driver_cast (driver);
1083
1084   if (a->file == NULL && !ascii_open_page (a))
1085     return;
1086
1087   if (y < 0 || y >= a->length)
1088     return;
1089   u8_line_set_length (&a->lines[y], length);
1090 }
1091 \f
1092 /* ascii_close_page () and support routines. */
1093
1094 #if HAVE_DECL_SIGWINCH
1095 static struct ascii_driver *the_driver;
1096
1097 static void
1098 winch_handler (int signum UNUSED)
1099 {
1100   update_page_size (the_driver, false);
1101 }
1102 #endif
1103
1104 static bool
1105 ascii_open_page (struct ascii_driver *a)
1106 {
1107   int i;
1108
1109   if (a->error)
1110     return false;
1111
1112   if (a->file == NULL)
1113     {
1114       a->file = fn_open (a->handle, a->append ? "a" : "w");
1115       if (a->file != NULL)
1116         {
1117           if ( isatty (fileno (a->file)))
1118             {
1119 #if HAVE_DECL_SIGWINCH
1120               struct sigaction action;
1121               sigemptyset (&action.sa_mask);
1122               action.sa_flags = 0;
1123               action.sa_handler = winch_handler;
1124               the_driver = a;
1125               sigaction (SIGWINCH, &action, NULL);
1126 #endif
1127               a->auto_width = true;
1128               a->auto_length = true;
1129             }
1130         }
1131       else
1132         {
1133           msg_error (errno, _("ascii: opening output file `%s'"),
1134                      fh_get_file_name (a->handle));
1135           a->error = true;
1136           return false;
1137         }
1138     }
1139
1140   a->page_number++;
1141
1142   reallocate_lines (a);
1143
1144   for (i = 0; i < a->length; i++)
1145     u8_line_clear (&a->lines[i]);
1146
1147   return true;
1148 }
1149
1150 static void
1151 output_title_line (FILE *out, int width, const char *left, const char *right)
1152 {
1153   struct string s = DS_EMPTY_INITIALIZER;
1154   ds_put_byte_multiple (&s, ' ', width);
1155   if (left != NULL)
1156     {
1157       size_t length = MIN (strlen (left), width);
1158       memcpy (ds_end (&s) - width, left, length);
1159     }
1160   if (right != NULL)
1161     {
1162       size_t length = MIN (strlen (right), width);
1163       memcpy (ds_end (&s) - length, right, length);
1164     }
1165   ds_put_byte (&s, '\n');
1166   fputs (ds_cstr (&s), out);
1167   ds_destroy (&s);
1168 }
1169
1170 static void
1171 ascii_close_page (struct ascii_driver *a)
1172 {
1173   bool any_blank;
1174   int i, y;
1175
1176   a->y = 0;
1177   if (a->file == NULL)
1178     return;
1179
1180   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1181       && !a->paginate && a->page_number > 1)
1182     putc ('\n', a->file);
1183
1184   for (i = 0; i < a->top_margin; i++)
1185     putc ('\n', a->file);
1186   if (a->headers)
1187     {
1188       char *r1, *r2;
1189
1190       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1191       r2 = xasprintf ("%s - %s" , version, host_system);
1192
1193       output_title_line (a->file, a->width, a->title, r1);
1194       output_title_line (a->file, a->width, a->subtitle, r2);
1195       putc ('\n', a->file);
1196
1197       free (r1);
1198       free (r2);
1199     }
1200
1201   any_blank = false;
1202   for (y = 0; y < a->allocated_lines; y++)
1203     {
1204       struct u8_line *line = &a->lines[y];
1205
1206       if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1207         any_blank = true;
1208       else
1209         {
1210           if (any_blank)
1211             {
1212               putc ('\n', a->file);
1213               any_blank = false;
1214             }
1215
1216           while (ds_chomp_byte (&line->s, ' '))
1217             continue;
1218           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1219           putc ('\n', a->file);
1220         }
1221     }
1222   if (!a->squeeze_blank_lines)
1223     for (y = a->allocated_lines; y < a->length; y++)
1224       putc ('\n', a->file);
1225
1226   for (i = 0; i < a->bottom_margin; i++)
1227     putc ('\n', a->file);
1228   if (a->paginate)
1229     putc ('\f', a->file);
1230 }