X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fascii.c;h=3548844693d09380dbfbe73a5ac696b3595b88d9;hb=d2ee46757c7602fb0d6827136fdb00af1140e2e1;hp=5a33a6611bfdd8d6f4c7fc4a602ff1a7c92e8717;hpb=18121006e5416f858633900820c4518dd30479c4;p=pspp diff --git a/src/output/ascii.c b/src/output/ascii.c index 5a33a6611b..3548844693 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 1997-9, 2000, 2007, 2009 Free Software Foundation, Inc. + Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,73 +21,114 @@ #include #include #include - -#include -#include -#include -#include -#include -#include -#include - -#include "chart.h" -#include "error.h" -#include "minmax.h" -#include "output.h" -#include "xalloc.h" +#include +#include +#include +#include +#include + +#include "data/file-name.h" +#include "data/settings.h" +#include "libpspp/assertion.h" +#include "libpspp/cast.h" +#include "libpspp/compiler.h" +#include "libpspp/message.h" +#include "libpspp/start-date.h" +#include "libpspp/string-map.h" +#include "libpspp/u8-line.h" +#include "libpspp/version.h" +#include "output/ascii.h" +#include "output/cairo.h" +#include "output/chart-item-provider.h" +#include "output/driver-provider.h" +#include "output/message-item.h" +#include "output/options.h" +#include "output/render.h" +#include "output/tab.h" +#include "output/table-item.h" +#include "output/text-item.h" + +#include "gl/minmax.h" +#include "gl/xalloc.h" #include "gettext.h" #define _(msgid) gettext (msgid) -/* ASCII driver options: (defaults listed first) - - output-file="pspp.list" - append=no|yes If output-file exists, append to it? - chart-files="pspp-#.png" Name used for charts. - chart-type=png Format of charts (use "none" to disable). - - paginate=on|off Formfeeds are desired? - tab-width=8 Width of a tab; 0 to not use tabs. - - headers=on|off Put headers at top of page? - emphasis=bold|underline|none Style to use for emphasis. - length=66|auto - width=79|auto - squeeze=off|on Squeeze multiple newlines into exactly one. +/* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */ +#define H TABLE_HORZ +#define V TABLE_VERT - top-margin=2 - bottom-margin=2 +#define N_BOX (RENDER_N_LINES * RENDER_N_LINES \ + * RENDER_N_LINES * RENDER_N_LINES) - box[x]="strng" Sets box character X (X in base 4: 0-3333). - init="string" Set initialization string. - */ - -/* Disable messages by failed range checks. */ -/*#define SUPPRESS_WARNINGS 1 */ - -/* Line styles bit shifts. */ -enum +static const ucs4_t ascii_box_chars[N_BOX] = { - LNS_TOP = 0, - LNS_LEFT = 2, - LNS_BOTTOM = 4, - LNS_RIGHT = 6, - - LNS_COUNT = 256 + ' ', '|', '#', + '-', '+', '#', + '=', '#', '#', + '|', '|', '#', + '+', '+', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '-', '+', '#', + '-', '+', '#', + '#', '#', '#', + '+', '+', '#', + '+', '+', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '=', '#', '#', + '#', '#', '#', + '=', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', }; -/* Character attributes. */ -#define ATTR_EMPHASIS 0x100 /* Bold-face. */ -#define ATTR_BOX 0x200 /* Line drawing character. */ - -/* A line of text. */ -struct line +static const ucs4_t unicode_box_chars[N_BOX] = { - unsigned short *chars; /* Characters and attributes. */ - int char_cnt; /* Length. */ - int char_cap; /* Allocated bytes. */ + 0x0020, 0x2575, 0x2551, + 0x2574, 0x256f, 0x255c, + 0x2550, 0x255b, 0x255d, + 0x2577, 0x2502, 0x2551, + 0x256e, 0x2524, 0x2562, + 0x2555, 0x2561, 0x2563, + 0x2551, 0x2551, 0x2551, + 0x2556, 0x2562, 0x2562, + 0x2557, 0x2563, 0x2563, + 0x2576, 0x2570, 0x2559, + 0x2500, 0x2534, 0x2568, + 0x2550, 0x2567, 0x2569, + 0x256d, 0x251c, 0x255f, + 0x252c, 0x253c, 0x256a, + 0x2564, 0x256a, 0x256c, + 0x2553, 0x255f, 0x255f, + 0x2565, 0x256b, 0x256b, + 0x2566, 0x256c, 0x256c, + 0x2550, 0x2558, 0x255a, + 0x2550, 0x2567, 0x2569, + 0x2550, 0x2567, 0x2569, + 0x2552, 0x255e, 0x2560, + 0x2564, 0x256a, 0x256c, + 0x2564, 0x256a, 0x256c, + 0x2554, 0x2560, 0x2560, + 0x2560, 0x256c, 0x256c, + 0x2566, 0x256c, 0x256c, }; +static inline int +make_box_index (int left, int right, int top, int bottom) +{ + return ((right * RENDER_N_LINES + bottom) * RENDER_N_LINES + left) * RENDER_N_LINES + top; +} + /* How to emphasize text. */ enum emphasis_style { @@ -96,851 +137,1081 @@ enum emphasis_style EMPH_NONE /* No emphasis. */ }; -/* ASCII output driver extension record. */ -struct ascii_driver_ext +/* ASCII output driver. */ +struct ascii_driver { - struct pool *pool; + struct output_driver driver; /* User parameters. */ - bool append; /* Append if output-file already exists? */ + bool append; /* Append if output file already exists? */ bool headers; /* Print headers at top of page? */ bool paginate; /* Insert formfeeds? */ bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */ enum emphasis_style emphasis; /* How to emphasize text. */ - int tab_width; /* Width of a tab; 0 not to use tabs. */ - const char *chart_type; /* Type of charts to output; NULL for none. */ - const char *chart_file_name; /* Name of files used for charts. */ + char *chart_file_name; /* Name of files used for charts. */ +#ifdef HAVE_CAIRO + /* Colours for charts */ + struct xr_color fg; + struct xr_color bg; +#endif + + int width; /* Page width. */ + int length; /* Page length minus margins and header. */ bool auto_width; /* Use viewwidth as page width? */ bool auto_length; /* Use viewlength as page width? */ - int page_length; /* Page length before subtracting margins. */ + int top_margin; /* Top margin in lines. */ int bottom_margin; /* Bottom margin in lines. */ - char *box[LNS_COUNT]; /* Line & box drawing characters. */ - char *init; /* Device initialization string. */ + int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */ + + const ucs4_t *box; /* Line & box drawing characters. */ /* Internal state. */ + char *command_name; + char *title; + char *subtitle; char *file_name; /* Output file name. */ FILE *file; /* Output file. */ - bool reported_error; /* Reported file open error? */ + bool error; /* Output error? */ int page_number; /* Current page number. */ - struct line *lines; /* Page content. */ - int line_cap; /* Number of lines allocated. */ + struct u8_line *lines; /* Page content. */ + int allocated_lines; /* Number of lines allocated. */ int chart_cnt; /* Number of charts so far. */ + int x, y; }; -static void ascii_flush (struct outp_driver *); -static int get_default_box_char (size_t idx); -static bool update_page_size (struct outp_driver *, bool issue_error); -static bool handle_option (void *this, const char *key, - const struct string *val); +static const struct output_driver_class ascii_driver_class; -static bool -ascii_open_driver (const char *name, int types, struct substring options) +static void ascii_submit (struct output_driver *, const struct output_item *); + +static int vertical_margins (const struct ascii_driver *); + +static bool update_page_size (struct ascii_driver *, bool issue_error); +static int parse_page_size (struct driver_option *); + +static void ascii_close_page (struct ascii_driver *); +static bool ascii_open_page (struct ascii_driver *); + +static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2], + enum render_line_style styles[TABLE_N_AXES][2]); +static void ascii_measure_cell_width (void *, const struct table_cell *, + int *min, int *max); +static int ascii_measure_cell_height (void *, const struct table_cell *, + int width); +static void ascii_draw_cell (void *, const struct table_cell *, + int bb[TABLE_N_AXES][2], + int clip[TABLE_N_AXES][2]); + +static void +reallocate_lines (struct ascii_driver *a) { - struct outp_driver *this; - struct ascii_driver_ext *x; - int i; + if (a->length > a->allocated_lines) + { + int i; + a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines); + for (i = a->allocated_lines; i < a->length; i++) + u8_line_init (&a->lines[i]); + a->allocated_lines = a->length; + } +} - this = outp_allocate_driver (&ascii_class, name, types); - this->width = 79; - this->font_height = 1; - this->prop_em_width = 1; - this->fixed_width = 1; - for (i = 0; i < OUTP_L_COUNT; i++) - this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE; - - this->ext = x = pool_create_container (struct ascii_driver_ext, pool); - x->append = false; - x->headers = true; - x->paginate = true; - x->squeeze_blank_lines = false; - x->emphasis = EMPH_BOLD; - x->tab_width = 8; - x->chart_file_name = pool_strdup (x->pool, "pspp-#.png"); - x->chart_type = pool_strdup (x->pool, "png"); - x->auto_width = false; - x->auto_length = false; - x->page_length = 66; - x->top_margin = 2; - x->bottom_margin = 2; - for (i = 0; i < LNS_COUNT; i++) - x->box[i] = NULL; - x->init = NULL; - x->file_name = pool_strdup (x->pool, "pspp.list"); - x->file = NULL; - x->reported_error = false; - x->page_number = 0; - x->lines = NULL; - x->line_cap = 0; - x->chart_cnt = 0; - - if (!outp_parse_options (this->name, options, handle_option, this)) - goto error; - if (!update_page_size (this, true)) - goto error; +static struct ascii_driver * +ascii_driver_cast (struct output_driver *driver) +{ + assert (driver->class == &ascii_driver_class); + return UP_CAST (driver, struct ascii_driver, driver); +} - for (i = 0; i < LNS_COUNT; i++) - if (x->box[i] == NULL) - { - char s[2]; - s[0] = get_default_box_char (i); - s[1] = '\0'; - x->box[i] = pool_strdup (x->pool, s); - } +static struct driver_option * +opt (struct output_driver *d, struct string_map *options, const char *key, + const char *default_value) +{ + return driver_option_get (d, options, key, default_value); +} - outp_register_driver (this); +static struct output_driver * +ascii_create (const char *file_name, enum settings_output_devices device_type, + struct string_map *o) +{ + enum { BOX_ASCII, BOX_UNICODE } box; + int min_break[TABLE_N_AXES]; + struct output_driver *d; + struct ascii_driver *a; + int paper_length; + + a = xzalloc (sizeof *a); + d = &a->driver; + output_driver_init (&a->driver, &ascii_driver_class, file_name, device_type); + a->append = parse_boolean (opt (d, o, "append", "false")); + a->headers = parse_boolean (opt (d, o, "headers", "false")); + a->paginate = parse_boolean (opt (d, o, "paginate", "false")); + a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true")); + a->emphasis = parse_enum (opt (d, o, "emphasis", "none"), + "bold", EMPH_BOLD, + "underline", EMPH_UNDERLINE, + "none", EMPH_NONE, + NULL_SENTINEL); + + a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", file_name)); + + a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX); + a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX); + + min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX); + min_break[V] = parse_int (opt (d, o, "min-vbreak", "-1"), -1, INT_MAX); + + a->width = parse_page_size (opt (d, o, "width", "79")); + paper_length = parse_page_size (opt (d, o, "length", "66")); + a->auto_width = a->width < 0; + a->auto_length = paper_length < 0; + a->length = paper_length - vertical_margins (a); + a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2; + a->min_break[V] = min_break[V] >= 0 ? min_break[V] : a->length / 2; +#ifdef HAVE_CAIRO + parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg); + parse_color (d, o, "foreground-color", "#000000000000", &a->fg); +#endif + box = parse_enum (opt (d, o, "box", "ascii"), + "ascii", BOX_ASCII, + "unicode", BOX_UNICODE, + NULL_SENTINEL); + a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars; + + a->command_name = NULL; + a->title = xstrdup (""); + a->subtitle = xstrdup (""); + a->file_name = xstrdup (file_name); + a->file = NULL; + a->error = false; + a->page_number = 0; + a->lines = NULL; + a->allocated_lines = 0; + a->chart_cnt = 1; + + if (!update_page_size (a, true)) + goto error; - return true; + return d; - error: - pool_destroy (x->pool); - outp_free_driver (this); - return false; +error: + output_driver_destroy (d); + return NULL; } static int -get_default_box_char (size_t idx) +parse_page_size (struct driver_option *option) { - /* Disassemble IDX into components. */ - unsigned top = (idx >> LNS_TOP) & 3; - unsigned left = (idx >> LNS_LEFT) & 3; - unsigned bottom = (idx >> LNS_BOTTOM) & 3; - unsigned right = (idx >> LNS_RIGHT) & 3; - - /* Reassemble components into nibbles in the order TLBR. - This makes it easy to read the case labels. */ - unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0); - switch (value) + int dim = atol (option->default_value); + + if (option->value != NULL) { - case 0x0000: - return ' '; + if (!strcmp (option->value, "auto")) + dim = -1; + else + { + int value; + char *tail; - case 0x0100: case 0x0101: case 0x0001: - return '-'; + errno = 0; + value = strtol (option->value, &tail, 0); + if (dim >= 1 && errno != ERANGE && *tail == '\0') + dim = value; + else + msg (MW, _("%s: %s must be positive integer or `auto'"), + option->driver_name, option->name); + } + } - case 0x1000: case 0x1010: case 0x0010: - return '|'; + driver_option_destroy (option); - case 0x0300: case 0x0303: case 0x0003: - case 0x0200: case 0x0202: case 0x0002: - return '='; + return dim; +} - default: - return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+'; - } +static int +vertical_margins (const struct ascii_driver *a) +{ + return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0); } /* Re-calculates the page width and length based on settings, margins, and, if "auto" is set, the size of the user's terminal window or GUI output window. */ static bool -update_page_size (struct outp_driver *this, bool issue_error) +update_page_size (struct ascii_driver *a, bool issue_error) { - struct ascii_driver_ext *x = this->ext; - int margins = x->top_margin + x->bottom_margin + 1 + (x->headers ? 3 : 0); + enum { MIN_WIDTH = 6, MIN_LENGTH = 6 }; - if (x->auto_width) - this->width = settings_get_viewwidth (); - if (x->auto_length) - x->page_length = settings_get_viewlength (); + if (a->auto_width) + a->width = settings_get_viewwidth (); + if (a->auto_length) + a->length = settings_get_viewlength () - vertical_margins (a); - this->length = x->page_length - margins; - - if (this->width < 59 || this->length < 15) + if (a->width < MIN_WIDTH || a->length < MIN_LENGTH) { if (issue_error) - error (0, 0, + msg (ME, _("ascii: page excluding margins and headers " - "must be at least 59 characters wide by 15 lines long, but " + "must be at least %d characters wide by %d lines long, but " "as configured is only %d characters by %d lines"), - this->width, this->length); - if (this->width < 59) - this->width = 59; - if (this->length < 15) - { - this->length = 15; - x->page_length = this->length + margins; - } + MIN_WIDTH, MIN_LENGTH, + a->width, a->length); + if (a->width < MIN_WIDTH) + a->width = MIN_WIDTH; + if (a->length < MIN_LENGTH) + a->length = MIN_LENGTH; return false; } + reallocate_lines (a); + return true; } -static bool -ascii_close_driver (struct outp_driver *this) +static void +ascii_destroy (struct output_driver *driver) { - struct ascii_driver_ext *x = this->ext; - - ascii_flush (this); - pool_detach_file (x->pool, x->file); - pool_destroy (x->pool); + struct ascii_driver *a = ascii_driver_cast (driver); + int i; - return true; + if (a->y > 0) + ascii_close_page (a); + + if (a->file != NULL) + fn_close (a->file_name, a->file); + free (a->command_name); + free (a->title); + free (a->subtitle); + free (a->file_name); + free (a->chart_file_name); + for (i = 0; i < a->allocated_lines; i++) + u8_line_destroy (&a->lines[i]); + free (a->lines); + free (a); } -/* Generic option types. */ -enum - { - boolean_arg, - emphasis_arg, - nonneg_int_arg, - page_size_arg, - string_arg - }; - -static const struct outp_option option_tab[] = - { - {"headers", boolean_arg, 0}, - {"paginate", boolean_arg, 1}, - {"squeeze", boolean_arg, 2}, - {"append", boolean_arg, 3}, - - {"emphasis", emphasis_arg, 0}, - - {"length", page_size_arg, 0}, - {"width", page_size_arg, 1}, - - {"top-margin", nonneg_int_arg, 0}, - {"bottom-margin", nonneg_int_arg, 1}, - {"tab-width", nonneg_int_arg, 2}, +static void +ascii_flush (struct output_driver *driver) +{ + struct ascii_driver *a = ascii_driver_cast (driver); + if (a->y > 0) + { + ascii_close_page (a); - {"output-file", string_arg, 0}, - {"chart-files", string_arg, 1}, - {"chart-type", string_arg, 2}, - {"init", string_arg, 3}, + if (fn_close (a->file_name, a->file) != 0) + msg_error (errno, _("ascii: closing output file `%s'"), a->file_name); + a->file = NULL; + } +} - {NULL, 0, 0}, - }; +static void +ascii_init_caption_cell (const char *caption, struct table_cell *cell) +{ + cell->inline_contents.options = TAB_LEFT; + cell->inline_contents.text = CONST_CAST (char *, caption); + cell->inline_contents.table = NULL; + cell->contents = &cell->inline_contents; + cell->n_contents = 1; + cell->destructor = NULL; +} -static bool -handle_option (void *this_, const char *key, - const struct string *val) +static void +ascii_output_table_item (struct ascii_driver *a, + const struct table_item *table_item) { - struct outp_driver *this = this_; - struct ascii_driver_ext *x = this->ext; - int subcat; - const char *value; + const char *caption = table_item_get_caption (table_item); + struct render_params params; + struct render_pager *p; + int caption_height; + int i; + + update_page_size (a, false); - value = ds_cstr (val); - if (!strncmp (key, "box[", 4)) + if (caption != NULL) + { + /* XXX doesn't do well with very large captions */ + struct table_cell cell; + ascii_init_caption_cell (caption, &cell); + caption_height = ascii_measure_cell_height (a, &cell, a->width); + } + else + caption_height = 0; + + params.draw_line = ascii_draw_line; + params.measure_cell_width = ascii_measure_cell_width; + params.measure_cell_height = ascii_measure_cell_height; + params.adjust_break = NULL; + params.draw_cell = ascii_draw_cell; + params.aux = a; + params.size[H] = a->width; + params.size[V] = a->length - caption_height; + params.font_size[H] = 1; + params.font_size[V] = 1; + for (i = 0; i < RENDER_N_LINES; i++) { - char *tail; - int indx = strtol (&key[4], &tail, 4); - if (*tail != ']' || indx < 0 || indx > LNS_COUNT) - { - error (0, 0, _("ascii: bad index value for `box' key: syntax " - "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX " - "expressed in base 4"), - LNS_COUNT); - return false; - } - if (x->box[indx] != NULL) - error (0, 0, _("ascii: multiple values for %s"), key); - x->box[indx] = pool_strdup (x->pool, value); - return true; + int width = i == RENDER_LINE_NONE ? 0 : 1; + params.line_widths[H][i] = width; + params.line_widths[V][i] = width; } + for (i = 0; i < TABLE_N_AXES; i++) + params.min_break[i] = a->min_break[i]; - switch (outp_match_keyword (key, option_tab, &subcat)) + if (a->file == NULL && !ascii_open_page (a)) + return; + + p = render_pager_create (render_page_create (¶ms, table_item_get_table ( + table_item))); + while (render_pager_has_next (p)) { - case -1: - error (0, 0, _("ascii: unknown parameter `%s'"), key); - break; - case page_size_arg: - { - char *tail; - int arg; - - if (ss_equals_case (ds_ss (val), ss_cstr ("auto"))) - { - if (!(this->device & OUTP_DEV_SCREEN)) - { - /* We only let `screen' devices have `auto' - length or width because output to such devices - is flushed before each new command. Resizing - a device in the middle of output seems like a - bad idea. */ - error (0, 0, _("ascii: only screen devices may have `auto' " - "length or width")); - } - else if (subcat == 0) - x->auto_length = true; - else - x->auto_width = true; - } - else - { - errno = 0; - arg = strtol (value, &tail, 0); - if (arg < 1 || errno == ERANGE || *tail) - { - error (0, 0, _("ascii: positive integer required as " - "`%s' value"), - key); - break; - } - switch (subcat) - { - case 0: - x->page_length = arg; - break; - case 1: - this->width = arg; - break; - default: - NOT_REACHED (); - } - } - } - break; - case emphasis_arg: - if (!strcmp (value, "bold")) - x->emphasis = EMPH_BOLD; - else if (!strcmp (value, "underline")) - x->emphasis = EMPH_UNDERLINE; - else if (!strcmp (value, "none")) - x->emphasis = EMPH_NONE; - else - error (0, 0, - _("ascii: `emphasis' value must be `bold', " - "`underline', or `none'")); - break; - case nonneg_int_arg: - { - char *tail; - int arg; - - errno = 0; - arg = strtol (value, &tail, 0); - if (arg < 0 || errno == ERANGE || *tail) - { - error (0, 0, - _("ascii: zero or positive integer required as `%s' value"), - key); - break; - } - switch (subcat) - { - case 0: - x->top_margin = arg; - break; - case 1: - x->bottom_margin = arg; - break; - case 2: - x->tab_width = arg; - break; - default: - NOT_REACHED (); - } - } - break; - case boolean_arg: - { - bool setting; - if (!strcmp (value, "on") || !strcmp (value, "true") - || !strcmp (value, "yes") || atoi (value)) - setting = true; - else if (!strcmp (value, "off") || !strcmp (value, "false") - || !strcmp (value, "no") || !strcmp (value, "0")) - setting = false; - else - { - error (0, 0, _("ascii: boolean value expected for `%s'"), key); - return false; - } - switch (subcat) - { - case 0: - x->headers = setting; - break; - case 1: - x->paginate = setting; - break; - case 2: - x->squeeze_blank_lines = setting; - break; - case 3: - x->append = setting; + int used; + + if (a->y > 0) + a->y++; + a->y += caption_height; + used = render_pager_draw_next (p, a->length - a->y); + if (used == 0) + { + assert (a->y > 0); + ascii_close_page (a); + if (!ascii_open_page (a)) break; - default: - NOT_REACHED (); - } - } - break; - case string_arg: - switch (subcat) + } + else { - case 0: - x->file_name = pool_strdup (x->pool, value); - break; - case 1: - if (ds_find_char (val, '#') != SIZE_MAX) - x->chart_file_name = pool_strdup (x->pool, value); - else - error (0, 0, _("`chart-files' value must contain `#'")); - break; - case 2: - if (value[0] != '\0') - x->chart_type = pool_strdup (x->pool, value); - else - x->chart_type = NULL; - break; - case 3: - x->init = pool_strdup (x->pool, value); - break; + if (caption_height) + { + struct table_cell cell; + int bb[TABLE_N_AXES][2]; + + ascii_init_caption_cell (caption, &cell); + bb[H][0] = 0; + bb[H][1] = a->width; + bb[V][0] = 0; + bb[V][1] = caption_height; + a->y -= caption_height; + ascii_draw_cell (a, &cell, bb, bb); + a->y += caption_height; + caption_height = 0; + } + a->y += used; } - break; - default: - NOT_REACHED (); } + render_pager_destroy (p); +} - return true; +static void +ascii_output_text (struct ascii_driver *a, const char *text) +{ + struct table_item *table_item; + + table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL); + ascii_output_table_item (a, table_item); + table_item_unref (table_item); } static void -ascii_open_page (struct outp_driver *this) +ascii_submit (struct output_driver *driver, + const struct output_item *output_item) { - struct ascii_driver_ext *x = this->ext; - int i; + struct ascii_driver *a = ascii_driver_cast (driver); - update_page_size (this, false); + output_driver_track_current_command (output_item, &a->command_name); + + if (a->error) + return; - if (x->file == NULL) + if (is_table_item (output_item)) + ascii_output_table_item (a, to_table_item (output_item)); +#ifdef HAVE_CAIRO + else if (is_chart_item (output_item) && a->chart_file_name != NULL) { - x->file = fn_open (x->file_name, x->append ? "a" : "w"); - if (x->file != NULL) + struct chart_item *chart_item = to_chart_item (output_item); + char *file_name; + + file_name = xr_draw_png_chart (chart_item, a->chart_file_name, + a->chart_cnt++, + &a->fg, + &a->bg); + if (file_name != NULL) { - pool_attach_file (x->pool, x->file); - if (x->init != NULL) - fputs (x->init, x->file); - } - else - { - /* Report the error to the user and complete - initialization. If we do not finish initialization, - then calls to other driver functions will segfault - later. It would be better to simply drop the driver - entirely, but we do not have a convenient mechanism - for this (yet). */ - if (!x->reported_error) - error (0, errno, _("ascii: opening output file \"%s\""), - x->file_name); - x->reported_error = true; - } - } + struct text_item *text_item; - x->page_number++; + text_item = text_item_create_format ( + TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name); - if (this->length > x->line_cap) + ascii_submit (driver, &text_item->output_item); + text_item_unref (text_item); + free (file_name); + } + } +#endif /* HAVE_CAIRO */ + else if (is_text_item (output_item)) { - x->lines = pool_nrealloc (x->pool, - x->lines, this->length, sizeof *x->lines); - for (i = x->line_cap; i < this->length; i++) + const struct text_item *text_item = to_text_item (output_item); + enum text_item_type type = text_item_get_type (text_item); + const char *text = text_item_get_text (text_item); + + switch (type) { - struct line *line = &x->lines[i]; - line->chars = NULL; - line->char_cap = 0; + case TEXT_ITEM_TITLE: + free (a->title); + a->title = xstrdup (text); + break; + + case TEXT_ITEM_SUBTITLE: + free (a->subtitle); + a->subtitle = xstrdup (text); + break; + + case TEXT_ITEM_COMMAND_OPEN: + case TEXT_ITEM_COMMAND_CLOSE: + break; + + case TEXT_ITEM_BLANK_LINE: + if (a->y > 0) + a->y++; + break; + + case TEXT_ITEM_EJECT_PAGE: + if (a->y > 0) + ascii_close_page (a); + break; + + default: + ascii_output_text (a, text); + break; } - x->line_cap = this->length; } - - for (i = 0; i < this->length; i++) - x->lines[i].char_cnt = 0; + else if (is_message_item (output_item)) + { + const struct message_item *message_item = to_message_item (output_item); + const struct msg *msg = message_item_get_msg (message_item); + char *s = msg_to_string (msg, a->command_name); + ascii_output_text (a, s); + free (s); + } } -/* Ensures that at least the first LENGTH characters of line Y in - THIS driver identified X have been cleared out. */ -static inline void -expand_line (struct outp_driver *this, int y, int length) +const struct output_driver_factory txt_driver_factory = + { "txt", "-", ascii_create }; +const struct output_driver_factory list_driver_factory = + { "list", "-", ascii_create }; + +static const struct output_driver_class ascii_driver_class = + { + "text", + ascii_destroy, + ascii_submit, + ascii_flush, + }; + +static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1, + int n); +static void ascii_layout_cell (struct ascii_driver *, + const struct table_cell *, + int bb[TABLE_N_AXES][2], + int clip[TABLE_N_AXES][2], + int *width, int *height); + +static void +ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2], + enum render_line_style styles[TABLE_N_AXES][2]) { - struct ascii_driver_ext *ext = this->ext; - struct line *line = &ext->lines[y]; - if (line->char_cnt < length) + struct ascii_driver *a = a_; + char mbchar[6]; + int x0, y0, x1, y1; + ucs4_t uc; + int mblen; + int x, y; + + /* Clip to the page. */ + x0 = MAX (bb[H][0] + a->x, 0); + y0 = MAX (bb[V][0] + a->y, 0); + x1 = MIN (bb[H][1] + a->x, a->width); + y1 = MIN (bb[V][1] + a->y, a->length); + if (x1 <= 0 || y1 <= 0 || x0 >= a->width || y0 >= a->length) + return; + + /* Draw. */ + uc = a->box[make_box_index (styles[V][0], styles[V][1], + styles[H][0], styles[H][1])]; + mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6); + for (y = y0; y < y1; y++) { - int x; - if (line->char_cap < length) + char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0)); + for (x = x0; x < x1; x++) { - line->char_cap = MIN (length * 2, this->width); - line->chars = pool_nrealloc (ext->pool, - line->chars, - line->char_cap, sizeof *line->chars); + memcpy (p, mbchar, mblen); + p += mblen; } - for (x = line->char_cnt; x < length; x++) - line->chars[x] = ' '; - line->char_cnt = length; } } static void -ascii_line (struct outp_driver *this, - int x0, int y0, int x1, int y1, - enum outp_line_style top, enum outp_line_style left, - enum outp_line_style bottom, enum outp_line_style right) +ascii_measure_cell_width (void *a_, const struct table_cell *cell, + int *min_width, int *max_width) { - struct ascii_driver_ext *ext = this->ext; - int y; - unsigned short value; - - assert (this->page_open); -#if DEBUGGING - if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length) + struct ascii_driver *a = a_; + int bb[TABLE_N_AXES][2]; + int clip[TABLE_N_AXES][2]; + int h; + + bb[H][0] = 0; + bb[H][1] = INT_MAX; + bb[V][0] = 0; + bb[V][1] = INT_MAX; + clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; + ascii_layout_cell (a, cell, bb, clip, max_width, &h); + + if (cell->n_contents != 1 + || cell->contents[0].table + || strchr (cell->contents[0].text, ' ')) { -#if !SUPPRESS_WARNINGS - printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"), - x0, y0, x1, y1, this->width, this->length); -#endif - return; + bb[H][1] = 1; + ascii_layout_cell (a, cell, bb, clip, min_width, &h); } -#endif - - value = ((left << LNS_LEFT) | (right << LNS_RIGHT) - | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX); - for (y = y0; y < y1; y++) - { - int x; + else + *min_width = *max_width; +} - expand_line (this, y, x1); - for (x = x0; x < x1; x++) - ext->lines[y].chars[x] = value; - } +static int +ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width) +{ + struct ascii_driver *a = a_; + int bb[TABLE_N_AXES][2]; + int clip[TABLE_N_AXES][2]; + int w, h; + + bb[H][0] = 0; + bb[H][1] = width; + bb[V][0] = 0; + bb[V][1] = INT_MAX; + clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; + ascii_layout_cell (a, cell, bb, clip, &w, &h); + return h; } static void -ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s) +ascii_draw_cell (void *a_, const struct table_cell *cell, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]) { - extern struct som_table_class tab_table_class; + struct ascii_driver *a = a_; + int w, h; - assert (s->class == &tab_table_class); - assert (s->type == SOM_CHART); + ascii_layout_cell (a, cell, bb, clip, &w, &h); } -static void -text_draw (struct outp_driver *this, - enum outp_font font, - int x, int y, - enum outp_justification justification, int width, - const char *string, size_t length) +static char * +ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n) { - struct ascii_driver_ext *ext = this->ext; - unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0; + assert (y < a->allocated_lines); + return u8_line_reserve (&a->lines[y], x0, x1, n); +} - int line_len; +static void +text_draw (struct ascii_driver *a, unsigned int options, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int y, const uint8_t *string, int n, size_t width) +{ + int x0 = MAX (0, clip[H][0] + a->x); + int y0 = MAX (0, clip[V][0] + a->y); + int x1 = MIN (a->width, clip[H][1] + a->x); + int y1 = MIN (a->length, clip[V][1] + a->y); + int x; + + y += a->y; + if (y < y0 || y >= y1) + return; - switch (justification) + switch (options & TAB_ALIGNMENT) { - case OUTP_LEFT: + case TAB_LEFT: + x = bb[H][0]; break; - case OUTP_CENTER: - x += (width - length + 1) / 2; + case TAB_CENTER: + x = (bb[H][0] + bb[H][1] - width + 1) / 2; break; - case OUTP_RIGHT: - x += width - length; + case TAB_RIGHT: + x = bb[H][1] - width; break; default: NOT_REACHED (); } + x += a->x; + if (x >= x1) + return; - if (y >= this->length || x >= this->width) + while (x < x0) + { + ucs4_t uc; + int mblen; + int w; + + if (n == 0) + return; + mblen = u8_mbtouc (&uc, string, n); + + string += mblen; + n -= mblen; + + w = uc_width (uc, "UTF-8"); + if (w > 0) + { + x += w; + width -= w; + } + } + if (n == 0) return; - if (x + length > this->width) - length = this->width - x; + if (x + width > x1) + { + int ofs; + + ofs = width = 0; + for (ofs = 0; ofs < n; ) + { + ucs4_t uc; + int mblen; + int w; + + mblen = u8_mbtouc (&uc, string + ofs, n - ofs); - line_len = x + length; + w = uc_width (uc, "UTF-8"); + if (w > 0) + { + if (width + w > x1 - x) + break; + width += w; + } + ofs += mblen; + } + n = ofs; + if (n == 0) + return; + } - expand_line (this, y, line_len); - while (length-- > 0) - ext->lines[y].chars[x++] = *string++ | attr; + if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE) + memcpy (ascii_reserve (a, y, x, x + width, n), string, n); + else + { + size_t n_out; + size_t ofs; + char *out; + int mblen; + + /* First figure out how many bytes need to be inserted. */ + n_out = n; + for (ofs = 0; ofs < n; ofs += mblen) + { + ucs4_t uc; + int w; + + mblen = u8_mbtouc (&uc, string + ofs, n - ofs); + w = uc_width (uc, "UTF-8"); + + if (w > 0) + n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen; + } + + /* Then insert them. */ + out = ascii_reserve (a, y, x, x + width, n_out); + for (ofs = 0; ofs < n; ofs += mblen) + { + ucs4_t uc; + int w; + + mblen = u8_mbtouc (&uc, string + ofs, n - ofs); + w = uc_width (uc, "UTF-8"); + + if (w > 0) + { + if (a->emphasis == EMPH_UNDERLINE) + *out++ = '_'; + else + out = mempcpy (out, string + ofs, mblen); + *out++ = '\b'; + } + out = mempcpy (out, string + ofs, mblen); + } + } } -/* Divides the text T->S into lines of width T->H. Sets *WIDTH - to the maximum width of a line and *HEIGHT to the number of - lines, if those arguments are non-null. Actually draws the - text if DRAW is true. */ -static void -delineate (struct outp_driver *this, const struct outp_text *text, bool draw, - int *width, int *height) +static int +ascii_layout_cell_text (struct ascii_driver *a, + const struct cell_contents *contents, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int *widthp) { - int max_width; - int height_left; + size_t length = strlen (contents->text); + char *breaks; + int bb_width; + size_t pos; + int y; - const char *cp = ss_data (text->string); + y = bb[V][0]; + if (length == 0) + return y; - max_width = 0; - height_left = text->v; + breaks = xmalloc (length + 1); + u8_possible_linebreaks (CHAR_CAST (const uint8_t *, contents->text), length, + "UTF-8", breaks); + breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY + ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE); - while (height_left > 0) + pos = 0; + bb_width = bb[H][1] - bb[H][0]; + for (y = bb[V][0]; y < bb[V][1] && pos < length; y++) { - size_t chars_left; - size_t line_len; - const char *end; - - /* Initially the line is up to text->h characters long. */ - chars_left = ss_end (text->string) - cp; - if (chars_left == 0) - break; - line_len = MIN (chars_left, text->h); - - /* A new-line terminates the line prematurely. */ - end = memchr (cp, '\n', line_len); - if (end != NULL) - line_len = end - cp; - - /* Don't cut off words if it can be avoided. */ - if (cp + line_len < ss_end (text->string)) + const uint8_t *line = CHAR_CAST (const uint8_t *, contents->text + pos); + const char *b = breaks + pos; + size_t n = length - pos; + + size_t last_break_ofs = 0; + int last_break_width = 0; + int width = 0; + size_t graph_ofs; + size_t ofs; + + for (ofs = 0; ofs < n; ) { - size_t space_len = line_len; - while (space_len > 0 && !isspace ((unsigned char) cp[space_len])) - space_len--; - if (space_len > 0) - line_len = space_len; + ucs4_t uc; + int mblen; + int w; + + mblen = u8_mbtouc (&uc, line + ofs, n - ofs); + if (b[ofs] == UC_BREAK_MANDATORY) + break; + else if (b[ofs] == UC_BREAK_POSSIBLE) + { + last_break_ofs = ofs; + last_break_width = width; + } + + w = uc_width (uc, "UTF-8"); + if (w > 0) + { + if (width + w > bb_width) + { + if (isspace (line[ofs])) + break; + else if (last_break_ofs != 0) + { + ofs = last_break_ofs; + width = last_break_width; + break; + } + } + width += w; + } + ofs += mblen; } + /* Trim any trailing spaces off the end of the text to be drawn. */ + for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--) + if (!isspace (line[graph_ofs - 1])) + break; + width -= ofs - graph_ofs; + /* Draw text. */ - if (draw) - text_draw (this, - text->font, - text->x, text->y + (text->v - height_left), - text->justification, text->h, - cp, line_len); - - /* Update. */ - height_left--; - if (line_len > max_width) - max_width = line_len; - - /* Next line. */ - cp += line_len; - if (cp < ss_end (text->string) && isspace ((unsigned char) *cp)) - cp++; + text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width); + + /* If a new-line ended the line, just skip the new-line. Otherwise, skip + past any spaces past the end of the line (but not past a new-line). */ + if (b[ofs] == UC_BREAK_MANDATORY) + ofs++; + else + while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY) + ofs++; + + if (width > *widthp) + *widthp = width; + pos += ofs; } - if (width != NULL) - *width = max_width; - if (height != NULL) - *height = text->v - height_left; -} + free (breaks); -static void -ascii_text_metrics (struct outp_driver *this, const struct outp_text *t, - int *width, int *height) -{ - delineate (this, t, false, width, height); + return y; } -static void -ascii_text_draw (struct outp_driver *this, const struct outp_text *t) +static int +ascii_layout_subtable (struct ascii_driver *a, + const struct cell_contents *contents, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2] UNUSED, + int *widthp) { - assert (this->page_open); - delineate (this, t, true, NULL, NULL); -} - -/* ascii_close_page () and support routines. */ + const struct table *table = contents->table; + struct render_params params; + struct render_pager *p; + int r[TABLE_N_AXES][2]; + int width, height; + int i; -/* Writes the LENGTH characters in S to OUT. */ -static void -output_line (struct outp_driver *this, const struct line *line, - struct string *out) -{ - struct ascii_driver_ext *ext = this->ext; - const unsigned short *s = line->chars; - size_t length; - - for (length = line->char_cnt; length-- > 0; s++) - if (*s & ATTR_BOX) - ds_put_cstr (out, ext->box[*s & 0xff]); - else - { - if (*s & ATTR_EMPHASIS) - { - if (ext->emphasis == EMPH_BOLD) - { - ds_put_char (out, *s); - ds_put_char (out, '\b'); - } - else if (ext->emphasis == EMPH_UNDERLINE) - ds_put_cstr (out, "_\b"); - } - ds_put_char (out, *s); - } -} + params.draw_line = ascii_draw_line; + params.measure_cell_width = ascii_measure_cell_width; + params.measure_cell_height = ascii_measure_cell_height; + params.adjust_break = NULL; + params.draw_cell = ascii_draw_cell, + params.aux = a; + params.size[H] = bb[TABLE_HORZ][1] - bb[TABLE_HORZ][0]; + params.size[V] = bb[TABLE_VERT][1] - bb[TABLE_VERT][0]; + params.font_size[H] = 1; + params.font_size[V] = 1; + for (i = 0; i < RENDER_N_LINES; i++) + { + int width = i == RENDER_LINE_NONE ? 0 : 1; + params.line_widths[H][i] = width; + params.line_widths[V][i] = width; + } -static void -append_lr_justified (struct string *out, int width, - const char *left, const char *right) -{ - ds_put_char_multiple (out, ' ', width); - if (left != NULL) + p = render_pager_create (render_page_create (¶ms, table)); + width = render_pager_get_size (p, TABLE_HORZ); + height = render_pager_get_size (p, TABLE_VERT); + + /* r = intersect(bb, clip) - bb. */ + for (i = 0; i < TABLE_N_AXES; i++) { - size_t length = MIN (strlen (left), width); - memcpy (ds_end (out) - width, left, length); + r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0]; + r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0]; } - if (right != NULL) + + if (r[H][0] < r[H][1] && r[V][0] < r[V][1]) { - size_t length = MIN (strlen (right), width); - memcpy (ds_end (out) - length, right, length); + unsigned int alignment = contents->options & TAB_ALIGNMENT; + int save_x = a->x; + + a->x += bb[TABLE_HORZ][0]; + if (alignment == TAB_RIGHT) + a->x += params.size[H] - width; + else if (alignment == TAB_CENTER) + a->x += (params.size[H] - width) / 2; + a->y += bb[TABLE_VERT][0]; + render_pager_draw (p); + a->y -= bb[TABLE_VERT][0]; + a->x = save_x; } - ds_put_char (out, '\n'); -} + render_pager_destroy (p); -static void -dump_output (struct outp_driver *this, struct string *out) -{ - struct ascii_driver_ext *x = this->ext; - fwrite (ds_data (out), ds_length (out), 1, x->file); - ds_clear (out); + if (width > *widthp) + *widthp = width; + return bb[V][0] + height; } static void -ascii_close_page (struct outp_driver *this) +ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, + int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int *widthp, int *heightp) { - struct ascii_driver_ext *x = this->ext; - struct string out; - int line_num; + int bb[TABLE_N_AXES][2]; + size_t i; - if (x->file == NULL) - return; - - ds_init_empty (&out); + *widthp = 0; + *heightp = 0; - ds_put_char_multiple (&out, '\n', x->top_margin); - if (x->headers) + memcpy (bb, bb_, sizeof bb); + for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++) { - char *r1, *r2; - - r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number); - r2 = xasprintf ("%s - %s" , version, host_system); - - append_lr_justified (&out, this->width, outp_title, r1); - append_lr_justified (&out, this->width, outp_subtitle, r2); - ds_put_char (&out, '\n'); - - free (r1); - free (r2); - } - dump_output (this, &out); + const struct cell_contents *contents = &cell->contents[i]; - for (line_num = 0; line_num < this->length; line_num++) - { - - /* Squeeze multiple blank lines into a single blank line if - requested. */ - if (x->squeeze_blank_lines) + /* Put a blank line between contents. */ + if (i > 0) { - if (line_num >= x->line_cap) + bb[V][0]++; + if (bb[V][0] >= bb[V][1]) break; - if (line_num > 0 - && x->lines[line_num].char_cnt == 0 - && x->lines[line_num - 1].char_cnt == 0) - continue; } - if (line_num < x->line_cap) - output_line (this, &x->lines[line_num], &out); - ds_put_char (&out, '\n'); - dump_output (this, &out); + if (contents->text) + bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp); + else + bb[V][0] = ascii_layout_subtable (a, contents, bb, clip, widthp); } + *heightp = bb[V][0] - bb_[V][0]; +} + +void +ascii_test_write (struct output_driver *driver, + const char *s, int x, int y, unsigned int options) +{ + struct ascii_driver *a = ascii_driver_cast (driver); + struct cell_contents contents; + struct table_cell cell; + int bb[TABLE_N_AXES][2]; + int width, height; + + if (a->file == NULL && !ascii_open_page (a)) + return; + a->y = 0; - ds_put_char_multiple (&out, '\n', x->bottom_margin); - if (x->paginate) - ds_put_char (&out, '\f'); + contents.options = options | TAB_LEFT; + contents.text = CONST_CAST (char *, s); + contents.table = NULL; - dump_output (this, &out); - ds_destroy (&out); + memset (&cell, 0, sizeof cell); + cell.contents = &contents; + cell.n_contents = 1; + + bb[TABLE_HORZ][0] = x; + bb[TABLE_HORZ][1] = a->width; + bb[TABLE_VERT][0] = y; + bb[TABLE_VERT][1] = a->length; + + ascii_layout_cell (a, &cell, bb, bb, &width, &height); + + a->y = 1; } -/* Flushes all output to the user and lets the user deal with it. - This is applied only to output drivers that are designated as - "screen" drivers that the user is interacting with in real - time. */ -static void -ascii_flush (struct outp_driver *this) +void +ascii_test_set_length (struct output_driver *driver, int y, int length) { - struct ascii_driver_ext *x = this->ext; - if (x->file != NULL) - { - if (fn_close (x->file_name, x->file) != 0) - error (0, errno, _("ascii: closing output file \"%s\""), - x->file_name); - pool_detach_file (x->pool, x->file); - x->file = NULL; - } + struct ascii_driver *a = ascii_driver_cast (driver); + + if (a->file == NULL && !ascii_open_page (a)) + return; + + if (y < 0 || y >= a->length) + return; + u8_line_set_length (&a->lines[y], length); } + +/* ascii_close_page () and support routines. */ + +#if HAVE_DECL_SIGWINCH +static struct ascii_driver *the_driver; static void -ascii_chart_initialise (struct outp_driver *this, struct chart *ch) +winch_handler (int signum UNUSED) { - struct ascii_driver_ext *x = this->ext; - struct outp_text t; - char *text; + update_page_size (the_driver, false); +} +#endif - if (x->chart_type == NULL) - return; +static bool +ascii_open_page (struct ascii_driver *a) +{ + int i; - /* Initialize chart. */ - chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt); - if (ch->file_name == NULL) - return; + if (a->error) + return false; - /* Mention chart in output. - First advance current position. */ - if (!this->page_open) - outp_open_page (this); - else + if (a->file == NULL) { - this->cp_y++; - if (this->cp_y >= this->length) + a->file = fn_open (a->file_name, a->append ? "a" : "w"); + if (a->file != NULL) + { + if ( isatty (fileno (a->file))) + { +#if HAVE_DECL_SIGWINCH + struct sigaction action; + sigemptyset (&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = winch_handler; + the_driver = a; + sigaction (SIGWINCH, &action, NULL); +#endif + a->auto_width = true; + a->auto_length = true; + } + } + else { - outp_close_page (this); - outp_open_page (this); + msg_error (errno, _("ascii: opening output file `%s'"), + a->file_name); + a->error = true; + return false; } } - /* Then write the text. */ - text = xasprintf ("See %s for a chart.", ch->file_name); - t.font = OUTP_FIXED; - t.justification = OUTP_LEFT; - t.string = ss_cstr (text); - t.h = this->width; - t.v = 1; - t.x = 0; - t.y = this->cp_y; - ascii_text_draw (this, &t); - this->cp_y++; - - free (text); + a->page_number++; + + reallocate_lines (a); + + for (i = 0; i < a->length; i++) + u8_line_clear (&a->lines[i]); + + return true; } static void -ascii_chart_finalise (struct outp_driver *this, struct chart *ch) +output_title_line (FILE *out, int width, const char *left, const char *right) { - struct ascii_driver_ext *x = this->ext; - if (x->chart_type != NULL) - chart_finalise_separate (ch); + struct string s = DS_EMPTY_INITIALIZER; + ds_put_byte_multiple (&s, ' ', width); + if (left != NULL) + { + size_t length = MIN (strlen (left), width); + memcpy (ds_end (&s) - width, left, length); + } + if (right != NULL) + { + size_t length = MIN (strlen (right), width); + memcpy (ds_end (&s) - length, right, length); + } + ds_put_byte (&s, '\n'); + fputs (ds_cstr (&s), out); + ds_destroy (&s); } -const struct outp_class ascii_class = +static void +ascii_close_page (struct ascii_driver *a) { - "ascii", - 0, + bool any_blank; + int i, y; - ascii_open_driver, - ascii_close_driver, + a->y = 0; + if (a->file == NULL) + return; + + if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines + && !a->paginate && a->page_number > 1) + putc ('\n', a->file); + + for (i = 0; i < a->top_margin; i++) + putc ('\n', a->file); + if (a->headers) + { + char *r1, *r2; - ascii_open_page, - ascii_close_page, - ascii_flush, + r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number); + r2 = xasprintf ("%s - %s" , version, host_system); + + output_title_line (a->file, a->width, a->title, r1); + output_title_line (a->file, a->width, a->subtitle, r2); + putc ('\n', a->file); + + free (r1); + free (r2); + } - ascii_submit, + any_blank = false; + for (y = 0; y < a->allocated_lines; y++) + { + struct u8_line *line = &a->lines[y]; - ascii_line, - ascii_text_metrics, - ascii_text_draw, + if (a->squeeze_blank_lines && y > 0 && line->width == 0) + any_blank = true; + else + { + if (any_blank) + { + putc ('\n', a->file); + any_blank = false; + } - ascii_chart_initialise, - ascii_chart_finalise -}; + while (ds_chomp_byte (&line->s, ' ')) + continue; + fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file); + putc ('\n', a->file); + } + } + if (!a->squeeze_blank_lines) + for (y = a->allocated_lines; y < a->length; y++) + putc ('\n', a->file); + + for (i = 0; i < a->bottom_margin; i++) + putc ('\n', a->file); + if (a->paginate) + putc ('\f', a->file); +}