X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fascii.c;h=d4032483da7132c609370d4952d7901745525f9f;hb=5d626fa11b736925983b615d4e3fba000a0ce75b;hp=bdb02dbf3c8443a4c86eff9ca13ec2d50b837ff0;hpb=02118dddfe989ca4fda5db5b54d64b1a564b6674;p=pspp diff --git a/src/output/ascii.c b/src/output/ascii.c index bdb02dbf3c..d4032483da 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 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,865 +21,1017 @@ #include #include #include - -#include -#include -#include -#include -#include -#include -#include - -#include "chart.h" -#include "error.h" -#include "minmax.h" -#include "output.h" +#include +#include +#include +#include +#include + +#include "data/file-name.h" +#include "data/file-handle-def.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 "gl/xsize.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 - width=130 - 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 - - 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 { - LNS_TOP = 0, - LNS_LEFT = 2, - LNS_BOTTOM = 4, - LNS_RIGHT = 6, - - LNS_COUNT = 256 + ASCII_LINE_NONE, + ASCII_LINE_SINGLE, + ASCII_LINE_DOUBLE, + ASCII_N_LINES }; -/* Character attributes. */ -#define ATTR_EMPHASIS 0x100 /* Bold-face. */ -#define ATTR_BOX 0x200 /* Line drawing character. */ +#define N_BOX (ASCII_N_LINES * ASCII_N_LINES \ + * ASCII_N_LINES * ASCII_N_LINES) -/* A line of text. */ -struct line +static const ucs4_t ascii_box_chars[N_BOX] = { - unsigned short *chars; /* Characters and attributes. */ - int char_cnt; /* Length. */ - int char_cap; /* Allocated bytes. */ + ' ', '|', '#', + '-', '+', '#', + '=', '#', '#', + '|', '|', '#', + '+', '+', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '-', '+', '#', + '-', '+', '#', + '#', '#', '#', + '+', '+', '#', + '+', '+', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '=', '#', '#', + '#', '#', '#', + '=', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', + '#', '#', '#', }; -/* How to emphasize text. */ -enum emphasis_style +static const ucs4_t unicode_box_chars[N_BOX] = { - EMPH_BOLD, /* Overstrike for bold. */ - EMPH_UNDERLINE, /* Overstrike for underlining. */ - EMPH_NONE /* No emphasis. */ + 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, }; -/* ASCII output driver extension record. */ -struct ascii_driver_ext +static int +ascii_line_from_render_line (int render_line) +{ + switch (render_line) + { + case RENDER_LINE_NONE: + return ASCII_LINE_NONE; + + case RENDER_LINE_SINGLE: + case RENDER_LINE_DASHED: + case RENDER_LINE_THICK: + case RENDER_LINE_THIN: + return ASCII_LINE_SINGLE; + + case RENDER_LINE_DOUBLE: + return ASCII_LINE_DOUBLE; + + default: + return ASCII_LINE_NONE; + } + +} + +static int +make_box_index (int left_, int right_, int top_, int bottom_) +{ + bool rtl = render_direction_rtl (); + int left = ascii_line_from_render_line (rtl ? right_ : left_); + int right = ascii_line_from_render_line (rtl ? left_ : right_); + int top = ascii_line_from_render_line (top_); + int bottom = ascii_line_from_render_line (bottom_); + + int idx = right; + idx = idx * ASCII_N_LINES + bottom; + idx = idx * ASCII_N_LINES + left; + idx = idx * ASCII_N_LINES + top; + return idx; +} + +/* ASCII output driver. */ +struct ascii_driver { - struct pool *pool; + struct output_driver driver; /* User parameters. */ - 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. */ - - 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. */ + bool append; /* Append if output file already exists? */ + bool emphasis; /* Enable bold and underline in output? */ + 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. */ + bool auto_width; /* Use viewwidth as page width? */ + + int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */ + + const ucs4_t *box; /* Line & box drawing characters. */ /* Internal state. */ - char *file_name; /* Output file name. */ + struct file_handle *handle; FILE *file; /* Output file. */ - bool reported_error; /* Reported file open error? */ - int page_number; /* Current page number. */ - struct line *lines; /* Page content. */ - int line_cap; /* Number of lines allocated. */ + bool error; /* Output error? */ + struct u8_line *lines; /* Page content. */ + int allocated_lines; /* Number of lines allocated. */ int chart_cnt; /* Number of charts so far. */ }; -static void ascii_flush (struct outp_driver *); -static int get_default_box_char (size_t idx); -static bool handle_option (struct outp_driver *this, const char *key, - const struct string *val); - -static bool -ascii_open_driver (struct outp_driver *this, struct substring options) -{ - struct ascii_driver_ext *x; - int i; - - 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->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 (options, handle_option, this)) - goto error; +static const struct output_driver_class ascii_driver_class; - this->length = x->page_length - x->top_margin - x->bottom_margin - 1; - if (x->headers) - this->length -= 3; +static void ascii_submit (struct output_driver *, const struct output_item *); - if (this->width < 59 || this->length < 15) - { - error (0, 0, - _("ascii: page excluding margins and headers " - "must be at least 59 characters wide by 15 lines long, but as " - "configured is only %d characters by %d lines"), - this->width, this->length); - return false; - } +static bool update_page_size (struct ascii_driver *, bool issue_error); +static int parse_page_size (struct driver_option *); - 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 bool ascii_open_page (struct ascii_driver *); - return true; +static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2], + enum render_line_style styles[TABLE_N_AXES][2], + struct cell_color colors[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 color_idx, + int bb[TABLE_N_AXES][2], + int spill[TABLE_N_AXES][2], + int clip[TABLE_N_AXES][2]); - error: - pool_destroy (x->pool); - return false; +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); } -static int -get_default_box_char (size_t idx) +static struct driver_option * +opt (struct output_driver *d, struct string_map *options, const char *key, + const char *default_value) { - /* 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) - { - case 0x0000: - return ' '; - - case 0x0100: case 0x0101: case 0x0001: - return '-'; - - case 0x1000: case 0x1010: case 0x0010: - return '|'; - - case 0x0300: case 0x0303: case 0x0003: - case 0x0200: case 0x0202: case 0x0002: - return '='; - - default: - return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+'; - } + return driver_option_get (d, options, key, default_value); } -static bool -ascii_close_driver (struct outp_driver *this) +static struct output_driver * +ascii_create (struct file_handle *fh, enum settings_output_devices device_type, + struct string_map *o) { - struct ascii_driver_ext *x = this->ext; + enum { BOX_ASCII, BOX_UNICODE } box; + int min_break[TABLE_N_AXES]; + struct output_driver *d; + struct ascii_driver *a; + + a = xzalloc (sizeof *a); + d = &a->driver; + output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type); + a->append = parse_boolean (opt (d, o, "append", "false")); + a->emphasis = parse_boolean (opt (d, o, "emphasis", "false")); + + a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh))); + a->handle = fh; + + min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX); + + a->width = parse_page_size (opt (d, o, "width", "79")); + a->auto_width = a->width < 0; + a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 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->file = NULL; + a->error = false; + a->lines = NULL; + a->allocated_lines = 0; + a->chart_cnt = 1; + + if (!update_page_size (a, true)) + goto error; - ascii_flush (this); - pool_detach_file (x->pool, x->file); - pool_destroy (x->pool); + return d; - return true; +error: + output_driver_destroy (d); + return NULL; } -/* Generic option types. */ -enum - { - boolean_arg, - emphasis_arg, - nonneg_int_arg, - pos_int_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}, +static int +parse_page_size (struct driver_option *option) +{ + int dim = atol (option->default_value); - {"length", pos_int_arg, 0}, - {"width", pos_int_arg, 1}, + if (option->value != NULL) + { + if (!strcmp (option->value, "auto")) + dim = -1; + else + { + int value; + char *tail; - {"top-margin", nonneg_int_arg, 0}, - {"bottom-margin", nonneg_int_arg, 1}, - {"tab-width", nonneg_int_arg, 2}, + 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); + } + } - {"output-file", string_arg, 0}, - {"chart-files", string_arg, 1}, - {"chart-type", string_arg, 2}, - {"init", string_arg, 3}, + driver_option_destroy (option); - {NULL, 0, 0}, - }; + return dim; +} +/* Re-calculates the page width based on settings, margins, and, if "auto" is + set, the size of the user's terminal window or GUI output window. */ static bool -handle_option (struct outp_driver *this, const char *key, - const struct string *val) +update_page_size (struct ascii_driver *a, bool issue_error) { - struct ascii_driver_ext *x = this->ext; - int subcat; - const char *value; + enum { MIN_WIDTH = 6, MIN_LENGTH = 6 }; - value = ds_cstr (val); - if (!strncmp (key, "box[", 4)) + if (a->auto_width) { - 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; + a->width = settings_get_viewwidth (); + a->min_break[H] = a->width / 2; } - switch (outp_match_keyword (key, option_tab, &subcat)) + if (a->width < MIN_WIDTH) { - case -1: - error (0, 0, _("ascii: unknown parameter `%s'"), key); - break; - case pos_int_arg: - { - char *tail; - int arg; - - 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; - break; - default: - NOT_REACHED (); - } - } - break; - case string_arg: - switch (subcat) - { - 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; - } - break; - default: - NOT_REACHED (); + if (issue_error) + msg (ME, + _("ascii: page must be at least %d characters wide, but " + "as configured is only %d characters"), + MIN_WIDTH, + a->width); + if (a->width < MIN_WIDTH) + a->width = MIN_WIDTH; + return false; } return true; } static void -ascii_open_page (struct outp_driver *this) +ascii_destroy (struct output_driver *driver) { - struct ascii_driver_ext *x = this->ext; + struct ascii_driver *a = ascii_driver_cast (driver); int i; - if (x->file == NULL) - { - x->file = fn_open (x->file_name, x->append ? "a" : "w"); - if (x->file != 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; - } - } - - x->page_number++; - - if (this->length > x->line_cap) - { - x->lines = pool_nrealloc (x->pool, - x->lines, this->length, sizeof *x->lines); - for (i = x->line_cap; i < this->length; i++) - { - struct line *line = &x->lines[i]; - line->chars = NULL; - line->char_cap = 0; - } - x->line_cap = this->length; - } + if (a->file != NULL) + fn_close (a->handle, a->file); + fh_unref (a->handle); + free (a->chart_file_name); + for (i = 0; i < a->allocated_lines; i++) + u8_line_destroy (&a->lines[i]); + free (a->lines); + free (a); +} - for (i = 0; i < this->length; i++) - x->lines[i].char_cnt = 0; +static void +ascii_flush (struct output_driver *driver) +{ + struct ascii_driver *a = ascii_driver_cast (driver); + if (a->file) + fflush (a->file); } -/* 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) +static void +ascii_output_lines (struct ascii_driver *a, size_t n_lines) { - struct ascii_driver_ext *ext = this->ext; - struct line *line = &ext->lines[y]; - if (line->char_cnt < length) + for (size_t y = 0; y < n_lines; y++) { - int x; - if (line->char_cap < length) - { - line->char_cap = MIN (length * 2, this->width); - line->chars = pool_nrealloc (ext->pool, - line->chars, - line->char_cap, sizeof *line->chars); - } - for (x = line->char_cnt; x < length; x++) - line->chars[x] = ' '; - line->char_cnt = length; + struct u8_line *line = &a->lines[y]; + + while (ds_chomp_byte (&line->s, ' ')) + continue; + fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file); + putc ('\n', a->file); + + u8_line_clear (&a->lines[y]); } } 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_output_table_item (struct ascii_driver *a, + const struct table_item *table_item) { - struct ascii_driver_ext *ext = this->ext; - int y; - unsigned short value; + struct render_params params; + struct render_pager *p; + int i; - assert (this->page_open); -#if DEBUGGING - if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length) + update_page_size (a, false); + + 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] = INT_MAX; + params.font_size[H] = 1; + params.font_size[V] = 1; + for (i = 0; i < RENDER_N_LINES; i++) { -#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; + int width = i == RENDER_LINE_NONE ? 0 : 1; + params.line_widths[H][i] = width; + params.line_widths[V][i] = width; } -#endif + for (i = 0; i < TABLE_N_AXES; i++) + params.min_break[i] = a->min_break[i]; + params.supports_margins = false; - value = ((left << LNS_LEFT) | (right << LNS_RIGHT) - | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX); - for (y = y0; y < y1; y++) - { - int x; + if (a->file) + putc ('\n', a->file); + else if (!ascii_open_page (a)) + return; - expand_line (this, y, x1); - for (x = x0; x < x1; x++) - ext->lines[y].chars[x] = value; + p = render_pager_create (¶ms, table_item); + for (int i = 0; render_pager_has_next (p); i++) + { + if (i) + putc ('\n', a->file); + ascii_output_lines (a, render_pager_draw_next (p, INT_MAX)); } + render_pager_destroy (p); } static void -ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s) +ascii_output_text (struct ascii_driver *a, const char *text) { - extern struct som_table_class tab_table_class; + struct table_item *table_item; - assert (s->class == &tab_table_class); - assert (s->type == SOM_CHART); + table_item = table_item_create (table_from_string (TAB_LEFT, text), + NULL, NULL); + ascii_output_table_item (a, table_item); + table_item_unref (table_item); } 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) +ascii_submit (struct output_driver *driver, + const struct output_item *output_item) { - struct ascii_driver_ext *ext = this->ext; - unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0; + struct ascii_driver *a = ascii_driver_cast (driver); - int line_len; + if (a->error) + return; - switch (justification) + 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) { - case OUTP_LEFT: - break; - case OUTP_CENTER: - x += (width - length + 1) / 2; - break; - case OUTP_RIGHT: - x += width - length; - break; - default: - NOT_REACHED (); + 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) + { + struct text_item *text_item; + + text_item = text_item_create_format ( + TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name); + + 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)) + { + 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); - if (y >= this->length || x >= this->width) - return; + switch (type) + { + case TEXT_ITEM_TITLE: + case TEXT_ITEM_SUBTITLE: + case TEXT_ITEM_COMMAND_OPEN: + case TEXT_ITEM_COMMAND_CLOSE: + break; - if (x + length > this->width) - length = this->width - x; + case TEXT_ITEM_BLANK_LINE: + break; - line_len = x + length; + case TEXT_ITEM_EJECT_PAGE: + break; - expand_line (this, y, line_len); - while (length-- > 0) - ext->lines[y].chars[x++] = *string++ | attr; + default: + ascii_output_text (a, text); + break; + } + } + 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, message_item->command_name); + ascii_output_text (a, s); + free (s); + } } -/* 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) -{ - int max_width; - int height_left; +const struct output_driver_factory txt_driver_factory = + { "txt", "-", ascii_create }; +const struct output_driver_factory list_driver_factory = + { "list", "-", ascii_create }; - const char *cp = ss_data (text->string); +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); - max_width = 0; - height_left = text->v; +static void +ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2], + enum render_line_style styles[TABLE_N_AXES][2], + struct cell_color colors[TABLE_N_AXES][2] UNUSED) +{ + 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], 0); + y0 = MAX (bb[V][0], 0); + x1 = MIN (bb[H][1], a->width); + y1 = bb[V][1]; + if (x1 <= 0 || y1 <= 0 || x0 >= a->width) + return; - while (height_left > 0) + /* 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++) { - 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)) + char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0)); + for (x = x0; x < x1; x++) { - 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; + memcpy (p, mbchar, mblen); + p += mblen; } - - /* 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++; } - - if (width != NULL) - *width = max_width; - if (height != NULL) - *height = text->v - height_left; } static void -ascii_text_metrics (struct outp_driver *this, const struct outp_text *t, - int *width, int *height) +ascii_measure_cell_width (void *a_, const struct table_cell *cell, + int *min_width, int *max_width) { - delineate (this, t, false, width, height); + 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].n_footnotes + || strchr (cell->contents[0].text, ' ')) + { + bb[H][1] = 1; + ascii_layout_cell (a, cell, bb, clip, min_width, &h); + } + else + *min_width = *max_width; } -static void -ascii_text_draw (struct outp_driver *this, const struct outp_text *t) +static int +ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width) { - assert (this->page_open); - delineate (this, t, true, NULL, NULL); + 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; } - -/* ascii_close_page () and support routines. */ -/* Writes the LENGTH characters in S to OUT. */ static void -output_line (struct outp_driver *this, const struct line *line, - struct string *out) +ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED, + int bb[TABLE_N_AXES][2], + int spill[TABLE_N_AXES][2] UNUSED, + int clip[TABLE_N_AXES][2]) { - struct ascii_driver_ext *ext = this->ext; - const unsigned short *s = line->chars; - size_t length; + struct ascii_driver *a = a_; + int w, h; - 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); - } + ascii_layout_cell (a, cell, bb, clip, &w, &h); } -static void -append_lr_justified (struct string *out, int width, - const char *left, const char *right) +static char * +ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n) { - ds_put_char_multiple (out, ' ', width); - if (left != NULL) + if (y >= a->allocated_lines) { - size_t length = MIN (strlen (left), width); - memcpy (ds_end (out) - width, left, length); + size_t new_alloc = MAX (25, a->allocated_lines); + while (new_alloc <= y) + new_alloc = xtimes (new_alloc, 2); + a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines); + for (size_t i = a->allocated_lines; i < new_alloc; i++) + u8_line_init (&a->lines[i]); + a->allocated_lines = new_alloc; } - if (right != NULL) - { - size_t length = MIN (strlen (right), width); - memcpy (ds_end (out) - length, right, length); - } - ds_put_char (out, '\n'); + return u8_line_reserve (&a->lines[y], x0, x1, n); } static void -dump_output (struct outp_driver *this, struct string *out) +text_draw (struct ascii_driver *a, unsigned int options, + bool bold, bool underline, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int y, const uint8_t *string, int n, size_t width) { - struct ascii_driver_ext *x = this->ext; - fwrite (ds_data (out), ds_length (out), 1, x->file); - ds_clear (out); -} + int x0 = MAX (0, clip[H][0]); + int y0 = MAX (0, clip[V][0]); + int x1 = MIN (a->width, clip[H][1]); + int y1 = clip[V][1]; + int x; -static void -ascii_close_page (struct outp_driver *this) -{ - struct ascii_driver_ext *x = this->ext; - struct string out; - int line_num; + if (y < y0 || y >= y1) + return; + + switch (options & TAB_HALIGN) + { + case TAB_LEFT: + x = bb[H][0]; + break; + case TAB_CENTER: + x = (bb[H][0] + bb[H][1] - width + 1) / 2; + break; + case TAB_RIGHT: + x = bb[H][1] - width; + break; + default: + NOT_REACHED (); + } + if (x >= x1) + return; + + while (x < x0) + { + ucs4_t uc; + int mblen; + int w; + + if (n == 0) + return; + mblen = u8_mbtouc (&uc, string, n); - if (x->file == NULL) + string += mblen; + n -= mblen; + + w = uc_width (uc, "UTF-8"); + if (w > 0) + { + x += w; + width -= w; + } + } + if (n == 0) return; - ds_init_empty (&out); + 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); + + 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; + } - ds_put_char_multiple (&out, '\n', x->top_margin); - if (x->headers) + if (!a->emphasis || (!bold && !underline)) + memcpy (ascii_reserve (a, y, x, x + width, n), string, n); + else { - char *r1, *r2; + 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) + { + if (bold) + n_out += 1 + mblen; + if (underline) + n_out += 2; + } + } - r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number); - r2 = xasprintf ("%s - %s" , version, host_system); + /* 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 (bold) + { + out = mempcpy (out, string + ofs, mblen); + *out++ = '\b'; + } + if (underline) + { + *out++ = '_'; + *out++ = '\b'; + } + } + out = mempcpy (out, string + ofs, mblen); + } + } +} - append_lr_justified (&out, this->width, outp_title, r1); - append_lr_justified (&out, this->width, outp_subtitle, r2); - ds_put_char (&out, '\n'); +static int +ascii_layout_cell_text (struct ascii_driver *a, + const struct cell_contents *contents, + bool bold, bool underline, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int *widthp) +{ + size_t length; + const char *text; + char *breaks; + int bb_width; + size_t pos; + int y; - free (r1); - free (r2); + y = bb[V][0]; + length = strlen (contents->text); + if (contents->n_footnotes) + { + struct string s; + int i; + + ds_init_empty (&s); + ds_extend (&s, length + contents->n_footnotes * 4); + ds_put_cstr (&s, contents->text); + for (i = 0; i < contents->n_footnotes; i++) + ds_put_format (&s, "[%s]", contents->footnotes[i]->marker); + + length = ds_length (&s); + text = ds_steal_cstr (&s); + } + else + { + if (length == 0) + return y; + text = contents->text; } - dump_output (this, &out); - for (line_num = 0; line_num < this->length; line_num++) + breaks = xmalloc (length + 1); + u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length, + "UTF-8", breaks); + breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY + ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE); + + pos = 0; + bb_width = bb[H][1] - bb[H][0]; + for (y = bb[V][0]; y < bb[V][1] && pos < length; y++) { + const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos); + const char *b = breaks + pos; + size_t n = length - pos; - /* Squeeze multiple blank lines into a single blank line if - requested. */ - if (x->squeeze_blank_lines) + 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; ) { - if (line_num >= x->line_cap) + ucs4_t uc; + int mblen; + int w; + + mblen = u8_mbtouc (&uc, line + ofs, n - ofs); + if (b[ofs] == UC_BREAK_MANDATORY) break; - if (line_num > 0 - && x->lines[line_num].char_cnt == 0 - && x->lines[line_num - 1].char_cnt == 0) - continue; + 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; } - if (line_num < x->line_cap) - output_line (this, &x->lines[line_num], &out); - ds_put_char (&out, '\n'); - dump_output (this, &out); + /* 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. */ + text_draw (a, contents->options, bold, underline, + 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; } - ds_put_char_multiple (&out, '\n', x->bottom_margin); - if (x->paginate) - ds_put_char (&out, '\f'); + free (breaks); + if (text != contents->text) + free (CONST_CAST (char *, text)); - dump_output (this, &out); - ds_destroy (&out); + return y; } -/* 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) +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; - if (x->file != NULL) + int bb[TABLE_N_AXES][2]; + size_t i; + + *widthp = 0; + *heightp = 0; + + memcpy (bb, bb_, sizeof bb); + for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++) { - 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; + const struct cell_contents *contents = &cell->contents[i]; + + /* Put a blank line between contents. */ + if (i > 0) + { + bb[V][0]++; + if (bb[V][0] >= bb[V][1]) + break; + } + + bb[V][0] = ascii_layout_cell_text (a, contents, cell->style->bold, + cell->style->underline, + bb, clip, widthp); } + *heightp = bb[V][0] - bb_[V][0]; } -static void -ascii_chart_initialise (struct outp_driver *this, struct chart *ch) +void +ascii_test_write (struct output_driver *driver, + const char *s, int x, int y, bool bold, bool underline) { - struct ascii_driver_ext *x = this->ext; - struct outp_text t; - char *text; + struct ascii_driver *a = ascii_driver_cast (driver); + int bb[TABLE_N_AXES][2]; + int width, height; - if (x->chart_type == NULL) + if (a->file == NULL && !ascii_open_page (a)) return; - /* Initialize chart. */ - chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt); - if (ch->file_name == NULL) - return; + struct cell_contents contents = { + .options = TAB_LEFT, + .text = CONST_CAST (char *, s), + }; + struct cell_style cell_style = { + .bold = bold, + .underline = underline, + }; + struct table_cell cell = { + .contents = &contents, + .n_contents = 1, + .style = &cell_style, + }; - /* Mention chart in output. - First advance current position. */ - if (!this->page_open) - outp_open_page (this); - else - { - this->cp_y++; - if (this->cp_y >= this->length) - { - outp_close_page (this); - outp_open_page (this); - } - } + bb[TABLE_HORZ][0] = x; + bb[TABLE_HORZ][1] = a->width; + bb[TABLE_VERT][0] = y; + bb[TABLE_VERT][1] = INT_MAX; - /* 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); + ascii_layout_cell (a, &cell, bb, bb, &width, &height); } -static void -ascii_chart_finalise (struct outp_driver *this, struct chart *ch) +void +ascii_test_set_length (struct output_driver *driver, int y, int length) { - struct ascii_driver_ext *x = this->ext; - if (x->chart_type != NULL) - chart_finalise_separate (ch); + struct ascii_driver *a = ascii_driver_cast (driver); + + if (a->file == NULL && !ascii_open_page (a)) + return; + + if (y < 0) + return; + u8_line_set_length (&a->lines[y], length); } -const struct outp_class ascii_class = +void +ascii_test_flush (struct output_driver *driver) { - "ascii", - 0, + struct ascii_driver *a = ascii_driver_cast (driver); + + for (size_t i = a->allocated_lines; i-- > 0; ) + if (a->lines[i].width) + { + ascii_output_lines (a, i + 1); + break; + } +} + +/* ascii_close_page () and support routines. */ - ascii_open_driver, - ascii_close_driver, +#if HAVE_DECL_SIGWINCH +static struct ascii_driver *the_driver; - ascii_open_page, - ascii_close_page, - ascii_flush, +static void +winch_handler (int signum UNUSED) +{ + update_page_size (the_driver, false); +} +#endif - ascii_submit, +static bool +ascii_open_page (struct ascii_driver *a) +{ + if (a->error) + return false; - ascii_line, - ascii_text_metrics, - ascii_text_draw, + if (a->file == NULL) + { + a->file = fn_open (a->handle, 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; + } + } + else + { + msg_error (errno, _("ascii: opening output file `%s'"), + fh_get_file_name (a->handle)); + a->error = true; + return false; + } + } - ascii_chart_initialise, - ascii_chart_finalise -}; + return true; +}