X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fascii.c;h=8b48d9bdd508417c169ac209b41ffb9a720e4bb1;hb=9c607eb8737f25e44121450527ab50a315841580;hp=a0dc1b0109191087288d635fd16891eb8c8c40e9;hpb=8444d8d47de5e5f8d076b6f43f73c2c29494031e;p=pspp diff --git a/src/output/ascii.c b/src/output/ascii.c index a0dc1b0109..b94d4941c2 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -1,756 +1,1140 @@ -/* PSPP - computes sample statistics. - Copyright (C) 1997-9, 2000 Free Software Foundation, Inc. - Written by Ben Pfaff . +/* PSPP - a program for statistical analysis. + 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 the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ + along with this program. If not, see . */ #include #include #include #include +#include +#include #include +#include +#include +#include +#include + +#ifdef HAVE_TERMIOS_H +# include +# include +#endif -#include -#include -#include -#include -#include -#include -#include - -#include "chart.h" -#include "error.h" -#include "minmax.h" -#include "output.h" +#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-chart.h" +#include "output/chart-provider.h" +#include "output/driver-provider.h" +#include "output/options.h" +#include "output/pivot-output.h" +#include "output/pivot-table.h" +#include "output/render.h" +#include "output/output-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" - 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. - - top-margin=2 - bottom-margin=2 +/* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */ +#define H TABLE_HORZ +#define V TABLE_VERT - box[x]="strng" Sets box character X (X in base 4: 0-3333). - */ - -/* 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, + ASCII_LINE_NONE, + ASCII_LINE_DASHED, + ASCII_LINE_SINGLE, + ASCII_LINE_DOUBLE, + ASCII_N_LINES + }; - LNS_COUNT = 256 +struct box_chars + { + ucs4_t c[ASCII_N_LINES][ASCII_N_LINES][ASCII_N_LINES][ASCII_N_LINES]; }; -/* Character attributes. */ -#define ATTR_EMPHASIS 0x100 /* Bold-face. */ -#define ATTR_BOX 0x200 /* Line drawing character. */ +static const struct box_chars * +get_ascii_box (void) +{ + enum { + _ = ASCII_LINE_NONE, + d = ASCII_LINE_DASHED, + S = ASCII_LINE_SINGLE, + D = ASCII_LINE_DOUBLE, + }; -/* A line of text. */ -struct line - { - unsigned short *chars; /* Characters and attributes. */ - int char_cnt; /* Length. */ - int char_cap; /* Allocated bytes. */ + static const struct box_chars ascii_box = + { + /* r b l t: _ d S D */ + .c[_][_][_] = { ' ', '|', '|', '#', }, + .c[_][_][d] = { '-', '+', '+', '#', }, + .c[_][_][S] = { '-', '+', '+', '#', }, + .c[_][_][D] = { '=', '#', '#', '#', }, + .c[_][d][_] = { '|', '|', '|', '#', }, + .c[_][d][d] = { '+', '+', '+', '#', }, + .c[_][d][S] = { '+', '+', '+', '#', }, + .c[_][d][D] = { '#', '#', '#', '#', }, + .c[_][S][_] = { '|', '|', '|', '#', }, + .c[_][S][d] = { '+', '+', '+', '#', }, + .c[_][S][S] = { '+', '+', '+', '#', }, + .c[_][S][D] = { '#', '#', '#', '#', }, + .c[_][D][_] = { '#', '#', '#', '#', }, + .c[_][D][d] = { '#', '#', '#', '#', }, + .c[_][D][S] = { '#', '#', '#', '#', }, + .c[_][D][D] = { '#', '#', '#', '#', }, + .c[d][_][_] = { '-', '+', '+', '#', }, + .c[d][_][d] = { '-', '+', '+', '#', }, + .c[d][_][S] = { '-', '+', '+', '#', }, + .c[d][_][D] = { '#', '#', '#', '#', }, + .c[d][d][_] = { '+', '+', '+', '#', }, + .c[d][d][d] = { '+', '+', '+', '#', }, + .c[d][d][S] = { '+', '+', '+', '#', }, + .c[d][d][D] = { '#', '#', '#', '#', }, + .c[d][S][_] = { '+', '+', '+', '#', }, + .c[d][S][d] = { '+', '+', '+', '#', }, + .c[d][S][S] = { '+', '+', '+', '#', }, + .c[d][S][D] = { '#', '#', '#', '#', }, + .c[d][D][_] = { '#', '#', '#', '#', }, + .c[d][D][d] = { '#', '#', '#', '#', }, + .c[d][D][S] = { '#', '#', '#', '#', }, + .c[d][D][D] = { '#', '#', '#', '#', }, + .c[S][_][_] = { '-', '+', '+', '#', }, + .c[S][_][d] = { '-', '+', '+', '#', }, + .c[S][_][S] = { '-', '+', '+', '#', }, + .c[S][_][D] = { '#', '#', '#', '#', }, + .c[S][d][_] = { '+', '+', '+', '#', }, + .c[S][d][d] = { '+', '+', '+', '#', }, + .c[S][d][S] = { '+', '+', '+', '#', }, + .c[S][d][D] = { '#', '#', '#', '#', }, + .c[S][S][_] = { '+', '+', '+', '#', }, + .c[S][S][d] = { '+', '+', '+', '#', }, + .c[S][S][S] = { '+', '+', '+', '#', }, + .c[S][S][D] = { '#', '#', '#', '#', }, + .c[S][D][_] = { '#', '#', '#', '#', }, + .c[S][D][d] = { '#', '#', '#', '#', }, + .c[S][D][S] = { '#', '#', '#', '#', }, + .c[S][D][D] = { '#', '#', '#', '#', }, + .c[D][_][_] = { '=', '#', '#', '#', }, + .c[D][_][d] = { '#', '#', '#', '#', }, + .c[D][_][S] = { '#', '#', '#', '#', }, + .c[D][_][D] = { '=', '#', '#', '#', }, + .c[D][d][_] = { '#', '#', '#', '#', }, + .c[D][d][d] = { '#', '#', '#', '#', }, + .c[D][d][S] = { '#', '#', '#', '#', }, + .c[D][d][D] = { '#', '#', '#', '#', }, + .c[D][S][_] = { '#', '#', '#', '#', }, + .c[D][S][d] = { '#', '#', '#', '#', }, + .c[D][S][S] = { '#', '#', '#', '#', }, + .c[D][S][D] = { '#', '#', '#', '#', }, + .c[D][D][_] = { '#', '#', '#', '#', }, + .c[D][D][d] = { '#', '#', '#', '#', }, + .c[D][D][S] = { '#', '#', '#', '#', }, + .c[D][D][D] = { '#', '#', '#', '#', }, + }; + return &ascii_box; +} + +static const struct box_chars * +get_unicode_box (void) +{ + enum { + _ = ASCII_LINE_NONE, + d = ASCII_LINE_DASHED, + S = ASCII_LINE_SINGLE, + D = ASCII_LINE_DOUBLE, }; -/* How to emphasize text. */ -enum emphasis_style + static const struct box_chars unicode_box = + { + /* r b l t: _ d S D */ + .c[_][_][_] = { 0x0020, 0x2575, 0x2575, 0x2551, }, /* ╵╵║ */ + .c[_][_][d] = { 0x254c, 0x256f, 0x256f, 0x255c, }, /* ╴╯╯╜ */ + .c[_][_][S] = { 0x2574, 0x256f, 0x256f, 0x255c, }, /* ╴╯╯╜ */ + .c[_][_][D] = { 0x2550, 0x255b, 0x255b, 0x255d, }, /* ═╛╛╝ */ + .c[_][S][_] = { 0x2577, 0x2502, 0x2502, 0x2551, }, /* ╷││║ */ + .c[_][S][d] = { 0x256e, 0x2524, 0x2524, 0x2562, }, /* ╮┤┤╢ */ + .c[_][S][S] = { 0x256e, 0x2524, 0x2524, 0x2562, }, /* ╮┤┤╢ */ + .c[_][S][D] = { 0x2555, 0x2561, 0x2561, 0x2563, }, /* ╕╡╡╣ */ + .c[_][d][_] = { 0x2577, 0x250a, 0x2502, 0x2551, }, /* ╷┊│║ */ + .c[_][d][d] = { 0x256e, 0x2524, 0x2524, 0x2562, }, /* ╮┤┤╢ */ + .c[_][d][S] = { 0x256e, 0x2524, 0x2524, 0x2562, }, /* ╮┤┤╢ */ + .c[_][d][D] = { 0x2555, 0x2561, 0x2561, 0x2563, }, /* ╕╡╡╣ */ + .c[_][D][_] = { 0x2551, 0x2551, 0x2551, 0x2551, }, /* ║║║║ */ + .c[_][D][d] = { 0x2556, 0x2562, 0x2562, 0x2562, }, /* ╖╢╢╢ */ + .c[_][D][S] = { 0x2556, 0x2562, 0x2562, 0x2562, }, /* ╖╢╢╢ */ + .c[_][D][D] = { 0x2557, 0x2563, 0x2563, 0x2563, }, /* ╗╣╣╣ */ + .c[d][_][_] = { 0x254c, 0x2570, 0x2570, 0x2559, }, /* ╶╰╰╙ */ + .c[d][_][d] = { 0x254c, 0x2534, 0x2534, 0x2568, }, /* ╌┴┴╨ */ + .c[d][_][S] = { 0x2500, 0x2534, 0x2534, 0x2568, }, /* ─┴┴╨ */ + .c[d][_][D] = { 0x2550, 0x2567, 0x2567, 0x2569, }, /* ═╧╧╩ */ + .c[d][d][_] = { 0x256d, 0x251c, 0x251c, 0x255f, }, /* ╭├├╟ */ + .c[d][d][d] = { 0x252c, 0x002b, 0x253c, 0x256a, }, /* ┬+┼╪ */ + .c[d][d][S] = { 0x252c, 0x253c, 0x253c, 0x256a, }, /* ┬┼┼╪ */ + .c[d][d][D] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[d][S][_] = { 0x256d, 0x251c, 0x251c, 0x255f, }, /* ╭├├╟ */ + .c[d][S][d] = { 0x252c, 0x253c, 0x253c, 0x256a, }, /* ┬┼┼╪ */ + .c[d][S][S] = { 0x252c, 0x253c, 0x253c, 0x256a, }, /* ┬┼┼╪ */ + .c[d][S][D] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[d][D][_] = { 0x2553, 0x255f, 0x255f, 0x255f, }, /* ╓╟╟╟ */ + .c[d][D][d] = { 0x2565, 0x256b, 0x256b, 0x256b, }, /* ╥╫╫╫ */ + .c[d][D][S] = { 0x2565, 0x256b, 0x256b, 0x256b, }, /* ╥╫╫╫ */ + .c[d][D][D] = { 0x2566, 0x256c, 0x256c, 0x256c, }, /* ╦╬╬╬ */ + .c[S][_][_] = { 0x2576, 0x2570, 0x2570, 0x2559, }, /* ╶╰╰╙ */ + .c[S][_][d] = { 0x2500, 0x2534, 0x2534, 0x2568, }, /* ─┴┴╨ */ + .c[S][_][S] = { 0x2500, 0x2534, 0x2534, 0x2568, }, /* ─┴┴╨ */ + .c[S][_][D] = { 0x2550, 0x2567, 0x2567, 0x2569, }, /* ═╧╧╩ */ + .c[S][d][_] = { 0x256d, 0x251c, 0x251c, 0x255f, }, /* ╭├├╟ */ + .c[S][d][d] = { 0x252c, 0x253c, 0x253c, 0x256a, }, /* ┬┼┼╪ */ + .c[S][d][S] = { 0x252c, 0x253c, 0x253c, 0x256a, }, /* ┬┼┼╪ */ + .c[S][d][D] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[S][S][_] = { 0x256d, 0x251c, 0x251c, 0x255f, }, /* ╭├├╟ */ + .c[S][S][d] = { 0x252c, 0x253c, 0x253c, 0x256a, }, /* ┬┼┼╪ */ + .c[S][S][S] = { 0x252c, 0x253c, 0x253c, 0x256a, }, /* ┬┼┼╪ */ + .c[S][S][D] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[S][D][_] = { 0x2553, 0x255f, 0x255f, 0x255f, }, /* ╓╟╟╟ */ + .c[S][D][d] = { 0x2565, 0x256b, 0x256b, 0x256b, }, /* ╥╫╫╫ */ + .c[S][D][S] = { 0x2565, 0x256b, 0x256b, 0x256b, }, /* ╥╫╫╫ */ + .c[S][D][D] = { 0x2566, 0x256c, 0x256c, 0x256c, }, /* ╦╬╬╬ */ + .c[D][_][_] = { 0x2550, 0x2558, 0x2558, 0x255a, }, /* ═╘╘╚ */ + .c[D][_][d] = { 0x2550, 0x2567, 0x2567, 0x2569, }, /* ═╧╧╩ */ + .c[D][_][S] = { 0x2550, 0x2567, 0x2567, 0x2569, }, /* ═╧╧╩ */ + .c[D][_][D] = { 0x2550, 0x2567, 0x2567, 0x2569, }, /* ═╧╧╩ */ + .c[D][d][_] = { 0x2552, 0x255e, 0x255e, 0x2560, }, /* ╒╞╞╠ */ + .c[D][d][d] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[D][d][S] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[D][d][D] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[D][S][_] = { 0x2552, 0x255e, 0x255e, 0x2560, }, /* ╒╞╞╠ */ + .c[D][S][d] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[D][S][S] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[D][S][D] = { 0x2564, 0x256a, 0x256a, 0x256c, }, /* ╤╪╪╬ */ + .c[D][D][_] = { 0x2554, 0x2560, 0x2560, 0x2560, }, /* ╔╠╠╠ */ + .c[D][D][d] = { 0x2560, 0x256c, 0x256c, 0x256c, }, /* ╠╬╬╬ */ + .c[D][D][S] = { 0x2560, 0x256c, 0x256c, 0x256c, }, /* ╠╬╬╬ */ + .c[D][D][D] = { 0x2566, 0x256c, 0x256c, 0x256c, }, /* ╦╬╬╬ */ + }; + return &unicode_box; +} + +static int +ascii_line_from_render_line (int render_line) +{ + switch (render_line) + { + case TABLE_STROKE_NONE: + return ASCII_LINE_NONE; + + case TABLE_STROKE_DASHED: + return ASCII_LINE_DASHED; + + case TABLE_STROKE_SOLID: + case TABLE_STROKE_THICK: + case TABLE_STROKE_THIN: + return ASCII_LINE_SINGLE; + + case TABLE_STROKE_DOUBLE: + return ASCII_LINE_DOUBLE; + + default: + return ASCII_LINE_NONE; + } + +} + +static ucs4_t +box_get (const struct box_chars *box, + 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_); + + return box->c[right][bottom][left][top]; +} + +/* How the page width is determined. */ +enum ascii_width_mode { - EMPH_BOLD, /* Overstrike for bold. */ - EMPH_UNDERLINE, /* Overstrike for underlining. */ - EMPH_NONE /* No emphasis. */ + FIXED_WIDTH, /* Specified by configuration. */ + VIEW_WIDTH, /* From SET WIDTH. */ + TERMINAL_WIDTH /* From the terminal's width. */ }; -/* 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 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. */ + bool emphasis; /* Enable bold and underline in output? */ + 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. */ + /* Colours for charts */ + struct cell_color fg; + struct cell_color bg; - char *box[LNS_COUNT]; /* Line & box drawing characters. */ + /* How the page width is determined: */ + enum ascii_width_mode width_mode; + int width; /* Page width. */ + + int min_hbreak; /* Min cell size to break across pages. */ + + const struct box_chars *box; /* Line & box drawing characters. */ /* Internal state. */ - char *file_name; /* Output file name. */ + struct file_handle *handle; FILE *file; /* Output file. */ - 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 n_charts; /* Number of charts so far. */ + int n_objects; /* Number of objects so far. */ + const struct pivot_table *pt; + struct render_params params; }; -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 const struct output_driver_class ascii_driver_class; -static bool -ascii_open_driver (struct outp_driver *this, const char *options) +static void ascii_submit (struct output_driver *, const struct output_item *); + +static int get_terminal_width (void); + +static bool update_page_size (struct ascii_driver *, bool issue_error); +static int parse_page_size (struct driver_option *); + +static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2], + const struct table_border_style[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 valign_offset, + int spill[TABLE_N_AXES][2], + int clip[TABLE_N_AXES][2]); + +static struct ascii_driver * +ascii_driver_cast (struct output_driver *driver) { - struct ascii_driver_ext *x; - int i; + assert (driver->class == &ascii_driver_class); + return UP_CAST (driver, struct ascii_driver, driver); +} - 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->headers = true; - x->paginate = true; - x->squeeze_blank_lines = false; - x->emphasis = EMPH_BOLD; - x->tab_width = 8; - x->page_length = 66; - x->top_margin = 2; - x->bottom_margin = 2; - for (i = 0; i < LNS_COUNT; i++) - x->box[i] = NULL; - x->file_name = pool_strdup (x->pool, "pspp.list"); - x->file = NULL; - x->page_number = 0; - x->lines = NULL; - x->line_cap = 0; - - if (!outp_parse_options (options, handle_option, this)) - goto error; +static struct driver_option * +opt (struct string_map *options, const char *key, const char *default_value) +{ + return driver_option_get ("ascii", options, key, default_value); +} - x->file = pool_fopen (x->pool, x->file_name, "w"); - if (x->file == NULL) - { - error (0, errno, _("ascii: opening output file \"%s\""), x->file_name); - goto error; - } +/* Return true iff the terminal appears to be an xterm with + UTF-8 capabilities */ +static bool +term_is_utf8_xterm (void) +{ + const char *term = getenv ("TERM"); + const char *xterm_locale = getenv ("XTERM_LOCAL"); + return (term && xterm_locale + && !strcmp (term, "xterm") + && (strcasestr (xterm_locale, "utf8") + || strcasestr (xterm_locale, "utf-8"))); +} - this->length = x->page_length - x->top_margin - x->bottom_margin - 1; - if (x->headers) - this->length -= 3; - - if (this->width < 59 || this->length < 15) +static struct output_driver * +ascii_create (struct file_handle *fh, enum settings_output_devices device_type, + struct string_map *o) +{ + bool append = parse_boolean (opt (o, "append", "false")); + FILE *file = fn_open (fh, append ? "a" : "w"); + if (!file) { - 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; + msg_error (errno, _("ascii: opening output file `%s'"), + fh_get_file_name (fh)); + return NULL; } - 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); - } - - return true; + int width = parse_page_size (opt (o, "width", "-1")); + bool terminal = !strcmp (fh_get_file_name (fh), "-") && isatty (1); + + const char *default_box = (terminal && (!strcmp (locale_charset (), "UTF-8") + || term_is_utf8_xterm ()) + ? "unicode" : "ascii"); + enum { BOX_ASCII, BOX_UNICODE } box = parse_enum (opt (o, "box", default_box), + "ascii", BOX_ASCII, + "unicode", BOX_UNICODE, + NULL_SENTINEL); + + static const struct render_ops ascii_render_ops = { + .draw_line = ascii_draw_line, + .measure_cell_width = ascii_measure_cell_width, + .measure_cell_height = ascii_measure_cell_height, + .adjust_break = NULL, + .draw_cell = ascii_draw_cell, + }; + + static const int ascii_line_widths[TABLE_N_STROKES] = { + [TABLE_STROKE_NONE] = 0, + [TABLE_STROKE_SOLID] = 1, + [TABLE_STROKE_DASHED] = 1, + [TABLE_STROKE_THICK] = 1, + [TABLE_STROKE_THIN] = 1, + [TABLE_STROKE_DOUBLE] = 1, + }; + + struct ascii_driver *a = xmalloc (sizeof *a); + *a = (struct ascii_driver) { + .driver = { + .class = &ascii_driver_class, + .name = xstrdup (fh_get_file_name (fh)), + .device_type = device_type + }, + + .emphasis = parse_boolean (opt (o, "emphasis", "false")), + .chart_file_name = parse_chart_file_name (opt (o, "charts", + fh_get_file_name (fh))), + + .fg = parse_color (opt (o, "foreground-color", "#000000000000")), + .bg = parse_color (opt (o, "background-color", "#FFFFFFFFFFFF")), + + .width_mode = (width > 0 ? FIXED_WIDTH + : terminal ? TERMINAL_WIDTH + : VIEW_WIDTH), + .width = width, + + .min_hbreak = parse_int (opt (o, "min-hbreak", "-1"), -1, INT_MAX), + + .box = box == BOX_ASCII ? get_ascii_box () : get_unicode_box (), + + .handle = fh, + .file = file, + + .params = (struct render_params) { + .ops = &ascii_render_ops, + .aux = a, + .size = { [H] = a->width, [V] = INT_MAX }, + .font_size = { [H] = 1, [V] = 1 }, + .line_widths = ascii_line_widths, + .rtl = render_direction_rtl (), + .printing = true, + }, + }; - error: - pool_destroy (x->pool); - return false; + if (!update_page_size (a, true)) + goto error; + + return &a->driver; + +error: + output_driver_destroy (&a->driver); + 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; + + errno = 0; + value = strtol (option->value, &tail, 0); + if (value >= 1 && errno != ERANGE && *tail == '\0') + dim = value; + else + msg (MW, _("%s: %s must be positive integer or `auto'"), + option->driver_name, option->name); + } + } - case 0x0100: case 0x0101: case 0x0001: - return '-'; + driver_option_destroy (option); - case 0x1000: case 0x1010: case 0x0010: - return '|'; + return dim; +} - case 0x0300: case 0x0303: case 0x0003: - case 0x0200: case 0x0202: case 0x0002: - return '='; +/* 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 +update_page_size (struct ascii_driver *a, bool issue_error) +{ + enum { MIN_WIDTH = 6 }; + + int want_width = (a->width_mode == VIEW_WIDTH ? settings_get_viewwidth () + : a->width_mode == TERMINAL_WIDTH ? get_terminal_width () + : a->width); + bool ok = want_width >= MIN_WIDTH; + if (!ok && issue_error) + msg (ME, _("ascii: page must be at least %d characters wide, but " + "as configured is only %d characters"), + MIN_WIDTH, want_width); + + a->width = ok ? want_width : MIN_WIDTH; + a->params.size[H] = a->width; + a->params.min_break[H] = a->min_hbreak >= 0 ? a->min_hbreak : a->width / 2; + + return ok; +} - default: - return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+'; - } +static void +ascii_destroy (struct output_driver *driver) +{ + struct ascii_driver *a = ascii_driver_cast (driver); + int i; + + 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); } -static bool -ascii_close_driver (struct outp_driver *this) +static void +ascii_flush (struct output_driver *driver) { - struct ascii_driver_ext *x = this->ext; - - 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); - pool_destroy (x->pool); - - return true; + struct ascii_driver *a = ascii_driver_cast (driver); + if (a->file) + fflush (a->file); } -/* Generic option types. */ -enum - { - boolean_arg, - string_arg, - nonneg_int_arg, - pos_int_arg, - output_file_arg - }; +static void +ascii_output_lines (struct ascii_driver *a, size_t n_lines) +{ + for (size_t y = 0; y < n_lines; y++) + { + if (y < a->allocated_lines) + { + struct u8_line *line = &a->lines[y]; -static struct outp_option option_tab[] = - { - {"headers", boolean_arg, 0}, - {"paginate", boolean_arg, 1}, - {"squeeze", boolean_arg, 2}, + while (ds_chomp_byte (&line->s, ' ')) + continue; + fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file); + u8_line_clear (&a->lines[y]); + } + putc ('\n', a->file); + } +} - {"emphasis", string_arg, 3}, +static void +ascii_output_table_item (struct ascii_driver *a, + const struct output_item *item) +{ + update_page_size (a, false); + a->pt = item->table; - {"output-file", output_file_arg, 0}, + size_t *layer_indexes; + PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->table, true) + { + struct render_pager *p = render_pager_create (&a->params, item->table, + layer_indexes); + for (int i = 0; render_pager_has_next (p); i++) + { + if (a->n_objects++) + putc ('\n', a->file); - {"length", pos_int_arg, 0}, - {"width", pos_int_arg, 1}, + ascii_output_lines (a, render_pager_draw_next (p, INT_MAX)); + } + render_pager_destroy (p); + } - {"top-margin", nonneg_int_arg, 0}, - {"bottom-margin", nonneg_int_arg, 1}, - {"tab-width", nonneg_int_arg, 2}, + a->pt = NULL; +} - {NULL, 0, 0}, - }; +static void +ascii_output_table_item_unref (struct ascii_driver *a, + struct output_item *table_item) +{ + ascii_output_table_item (a, table_item); + output_item_unref (table_item); +} -static bool -handle_option (struct outp_driver *this, const char *key, - const struct string *val) +static void +ascii_submit (struct output_driver *driver, const struct output_item *item) { - struct ascii_driver_ext *x = this->ext; - int subcat; - const char *value; + struct ascii_driver *a = ascii_driver_cast (driver); + if (a->error) + return; - value = ds_c_str (val); - if (!strncmp (key, "box[", 4)) + switch (item->type) { - 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; - } + case OUTPUT_ITEM_TABLE: + ascii_output_table_item (a, item); + break; - switch (outp_match_keyword (key, option_tab, &subcat)) - { - case -1: - error (0, 0, _("ascii: unknown parameter `%s'"), key); + case OUTPUT_ITEM_IMAGE: + if (a->chart_file_name != NULL) + { + char *file_name = xr_write_png_image ( + item->image, a->chart_file_name, ++a->n_charts); + if (file_name != NULL) + { + struct output_item *text_item = text_item_create_nocopy ( + TEXT_ITEM_LOG, + xasprintf (_("See %s for an image."), file_name), + NULL); + + ascii_submit (driver, text_item); + output_item_unref (text_item); + free (file_name); + } + } break; - case output_file_arg: - x->file_name = pool_strdup (x->pool, value); + + case OUTPUT_ITEM_CHART: + if (a->chart_file_name != NULL) + { + char *file_name = xr_draw_png_chart ( + item->chart, a->chart_file_name, ++a->n_charts, &a->fg, &a->bg); + if (file_name != NULL) + { + struct output_item *text_item = text_item_create_nocopy ( + TEXT_ITEM_LOG, + xasprintf (_("See %s for a chart."), file_name), + NULL); + + ascii_submit (driver, text_item); + output_item_unref (text_item); + free (file_name); + } + } 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: - abort (); - } - } + + case OUTPUT_ITEM_TEXT: + if (item->text.subtype != TEXT_ITEM_PAGE_TITLE) + ascii_output_table_item_unref ( + a, text_item_to_table_item (output_item_ref (item))); break; - case string_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'")); + + case OUTPUT_ITEM_MESSAGE: + ascii_output_table_item_unref ( + a, text_item_to_table_item ( + message_item_to_text_item ( + output_item_ref (item)))); 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: - abort (); - } - } + + case OUTPUT_ITEM_GROUP: 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; - default: - abort (); - } - } + + case OUTPUT_ITEM_PAGE_BREAK: break; - default: - abort (); } - - return true; } +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 = + { + .name = "text", + .destroy = ascii_destroy, + .submit = ascii_submit, + .flush = 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_open_page (struct outp_driver *this) +ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2], + const struct table_border_style styles[TABLE_N_AXES][2]) { - struct ascii_driver_ext *x = this->ext; - int i; - - x->page_number++; + 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; - if (this->length > x->line_cap) + /* Draw. */ + enum table_stroke v0 = styles[V][0].stroke; + enum table_stroke v1 = styles[V][1].stroke; + enum table_stroke h0 = styles[H][0].stroke; + enum table_stroke h1 = styles[H][1].stroke; + uc = box_get (a->box, v0, v1, h0, h1); + mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6); + for (y = y0; y < y1; y++) { - x->lines = pool_nrealloc (x->pool, - x->lines, this->length, sizeof *x->lines); - for (i = x->line_cap; i < this->length; i++) + char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0)); + for (x = x0; x < x1; x++) { - struct line *line = &x->lines[i]; - line->chars = NULL; - line->char_cap = 0; + memcpy (p, mbchar, mblen); + p += mblen; } - x->line_cap = this->length; } +} - for (i = 0; i < this->length; i++) - x->lines[i].char_cnt = 0; +static void +ascii_measure_cell_width (void *a_, const struct table_cell *cell, + int *min_width, int *max_width) +{ + 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); + + bb[H][1] = 1; + ascii_layout_cell (a, cell, bb, clip, min_width, &h); } -/* 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 int +ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width) { - struct ascii_driver_ext *ext = this->ext; - struct line *line = &ext->lines[y]; - if (line->char_cnt < length) - { - 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 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_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_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED, + int bb[TABLE_N_AXES][2], int valign_offset, + int spill[TABLE_N_AXES][2] UNUSED, + int clip[TABLE_N_AXES][2]) { - struct ascii_driver_ext *ext = this->ext; - int y; - unsigned short value; + struct ascii_driver *a = a_; + int w, h; - assert (this->page_open); -#if DEBUGGING - if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length) - { -#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; - } -#endif + bb[V][0] += valign_offset; + ascii_layout_cell (a, cell, bb, clip, &w, &h); +} - value = ((left << LNS_LEFT) | (right << LNS_RIGHT) - | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX); - for (y = y0; y < y1; y++) +static char * +ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n) +{ + if (y >= a->allocated_lines) { - int x; - - expand_line (this, y, x1); - for (x = x0; x < x1; x++) - ext->lines[y].chars[x] = value; + 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; } + return u8_line_reserve (&a->lines[y], x0, x1, n); } 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) +text_draw (struct ascii_driver *a, enum table_halign halign, bool numeric, + 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 *ext = this->ext; - unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0; + 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; - int line_len; + if (y < y0 || y >= y1) + return; - switch (justification) + switch (table_halign_interpret (halign, numeric)) { - case OUTP_LEFT: + case TABLE_HALIGN_LEFT: + x = bb[H][0]; break; - case OUTP_CENTER: - x += (width - length + 1) / 2; + case TABLE_HALIGN_CENTER: + x = (bb[H][0] + bb[H][1] - width + 1) / 2; break; - case OUTP_RIGHT: - x += width - length; + case TABLE_HALIGN_RIGHT: + case TABLE_HALIGN_DECIMAL: + x = bb[H][1] - width; break; default: - abort (); + NOT_REACHED (); } + if (x >= x1) + return; + + while (x < x0) + { + ucs4_t uc; + int mblen; + int w; - if (y >= this->length || x >= this->width) + 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; - line_len = x + length; + 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; + } - expand_line (this, y, line_len); - while (length-- > 0) - ext->lines[y].chars[x++] = *string++ | attr; + if (!a->emphasis || (!bold && !underline)) + 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) + { + if (bold) + n_out += 1 + mblen; + if (underline) + n_out += 2; + } + } + + /* 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); + } + } } -/* Divides the text T->S into lines of width T->H. Sets T->V to the - number of lines necessary. 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) +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) { - int max_width; - int height_left; + *widthp = 0; + *heightp = 0; - const char *cp = ls_c_str (&text->string); + struct string body = DS_EMPTY_INITIALIZER; + bool numeric = pivot_value_format (cell->value, a->pt, &body); - max_width = 0; - height_left = text->v; - - while (height_left > 0) + /* Calculate length; if it's zero, then there's nothing to do. */ + if (ds_is_empty (&body)) { - size_t chars_left; - size_t line_len; - const char *end; + ds_destroy (&body); + return; + } - /* Initially the line is up to text->h characters long. */ - chars_left = ls_end (&text->string) - cp; - if (chars_left == 0) - break; - line_len = MIN (chars_left, text->h); + size_t length = ds_length (&body); + const uint8_t *text = CHAR_CAST (uint8_t *, ds_cstr (&body)); + + char *breaks = xmalloc (length + 1); + u8_possible_linebreaks (text, length, "UTF-8", breaks); + breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY + ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE); - /* A new-line terminates the line prematurely. */ - end = memchr (cp, '\n', line_len); - if (end != NULL) - line_len = end - cp; + size_t pos = 0; + int bb_width = bb[H][1] - bb[H][0]; + for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++) + { + const uint8_t *line = 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; - /* Don't cut off words if it can be avoided. */ - if (cp + line_len < ls_end (&text->string)) + 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 < ls_end (&text->string) && isspace ((unsigned char) *cp)) - cp++; + text_draw (a, cell->cell_style->halign, numeric, + cell->font_style->bold, + cell->font_style->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; + ++*heightp; + pos += ofs; } - if (width != NULL) - *width = max_width; - if (height != NULL) - *height = text->v - height_left; + free (breaks); + ds_destroy (&body); } -static void -ascii_text_metrics (struct outp_driver *this, const struct outp_text *t, - int *width, int *height) +void +ascii_test_write (struct output_driver *driver, + const char *s, int x, int y, bool bold, bool underline) { - delineate (this, t, false, width, height); -} + struct ascii_driver *a = ascii_driver_cast (driver); + int bb[TABLE_N_AXES][2]; + int width, height; -static void -ascii_text_draw (struct outp_driver *this, const struct outp_text *t) -{ - assert (this->page_open); - delineate (this, t, true, NULL, NULL); -} + if (!a->file) + return; - -/* ascii_close_page () and support routines. */ + struct cell_style cell_style = { .halign = TABLE_HALIGN_LEFT }; + struct font_style font_style = { + .bold = bold, + .underline = underline, + }; + const struct pivot_value value = { + .text = { + .type = PIVOT_VALUE_TEXT, + .local = CONST_CAST (char *, s), + .c = CONST_CAST (char *, s), + .id = CONST_CAST (char *, s), + .user_provided = true, + }, + }; + struct table_cell cell = { + .value = &value, + .font_style = &font_style, + .cell_style = &cell_style, + }; -/* 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_puts (out, ext->box[*s & 0xff]); - else - { - if (*s & ATTR_EMPHASIS) - { - if (ext->emphasis == EMPH_BOLD) - { - ds_putc (out, *s); - ds_putc (out, '\b'); - } - else if (ext->emphasis == EMPH_UNDERLINE) - ds_puts (out, "_\b"); - } - ds_putc (out, *s); - } -} + bb[TABLE_HORZ][0] = x; + bb[TABLE_HORZ][1] = a->width; + bb[TABLE_VERT][0] = y; + bb[TABLE_VERT][1] = INT_MAX; -static void -append_lr_justified (struct string *out, int width, - const char *left, const char *right) -{ - ds_putc_multiple (out, ' ', width); - if (left != NULL) - { - size_t length = MIN (strlen (left), width); - memcpy (ds_end (out) - width, left, length); - } - if (right != NULL) - { - size_t length = MIN (strlen (right), width); - memcpy (ds_end (out) - length, right, length); - } - ds_putc (out, '\n'); + struct pivot_table pt = { + .show_values = SETTINGS_VALUE_SHOW_DEFAULT, + .show_variables = SETTINGS_VALUE_SHOW_DEFAULT, + }; + a->pt = &pt; + ascii_layout_cell (a, &cell, bb, bb, &width, &height); + a->pt = NULL; } -static void -dump_output (struct outp_driver *this, struct string *out) +void +ascii_test_set_length (struct output_driver *driver, int y, int length) { - struct ascii_driver_ext *x = this->ext; - fwrite (ds_data (out), ds_length (out), 1, x->file); - ds_clear (out); -} + struct ascii_driver *a = ascii_driver_cast (driver); -static void -ascii_close_page (struct outp_driver *this) -{ - struct ascii_driver_ext *x = this->ext; - struct string out; - int line_num; - - ds_init (&out, 128); - - ds_putc_multiple (&out, '\n', x->top_margin); - if (x->headers) - { - 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_putc (&out, '\n'); - - free (r1); - free (r2); - } - dump_output (this, &out); - - 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) - { - if (line_num >= x->line_cap) - 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_putc (&out, '\n'); - dump_output (this, &out); - } - - ds_putc_multiple (&out, '\n', x->bottom_margin); - if (x->paginate) - ds_putc (&out, '\f'); + if (!a->file) + return; - dump_output (this, &out); - ds_destroy (&out); + if (y < 0) + return; + u8_line_set_length (&a->lines[y], length); } -static void -ascii_chart_initialise (struct outp_driver *d UNUSED, struct chart *ch) +void +ascii_test_flush (struct output_driver *driver) { - error (0, 0, _("ascii: charts are unsupported by this driver")); - ch->lp = 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; + } } + +static sig_atomic_t terminal_changed = true; +static int terminal_width; -static void -ascii_chart_finalise (struct outp_driver *d UNUSED, struct chart *ch UNUSED) +#if HAVE_DECL_SIGWINCH +static void +winch_handler (int signum UNUSED) { - + terminal_changed = true; } +#endif -struct outp_class ascii_class = +int +get_terminal_width (void) { - "ascii", - 0, +#if HAVE_DECL_SIGWINCH + static bool setup_signal; + if (!setup_signal) + { + setup_signal = true; - ascii_open_driver, - ascii_close_driver, + struct sigaction action = { .sa_handler = winch_handler }; + sigemptyset (&action.sa_mask); + sigaction (SIGWINCH, &action, NULL); + } +#endif - ascii_open_page, - ascii_close_page, + if (terminal_changed) + { + terminal_changed = false; - NULL, +#ifdef HAVE_TERMIOS_H + struct winsize ws; + if (!ioctl (0, TIOCGWINSZ, &ws)) + terminal_width = ws.ws_col; + else +#endif + { + if (getenv ("COLUMNS")) + terminal_width = atoi (getenv ("COLUMNS")); + } - ascii_line, - ascii_text_metrics, - ascii_text_draw, + if (terminal_width <= 0 || terminal_width > 1024) + terminal_width = 79; + } - ascii_chart_initialise, - ascii_chart_finalise -}; + return terminal_width; +}