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