LCOV - code coverage report
Current view: top level - src/fe_utils - print.c (source / functions) Hit Total Coverage
Test: PostgreSQL 15devel Lines: 1446 1556 92.9 %
Date: 2021-09-17 16:07:28 Functions: 46 47 97.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * Query-result printing support for frontend code
       4             :  *
       5             :  * This file used to be part of psql, but now it's separated out to allow
       6             :  * other frontend programs to use it.  Because the printing code needs
       7             :  * access to the cancel_pressed flag as well as SIGPIPE trapping and
       8             :  * pager open/close functions, all that stuff came with it.
       9             :  *
      10             :  *
      11             :  * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
      12             :  * Portions Copyright (c) 1994, Regents of the University of California
      13             :  *
      14             :  * src/fe_utils/print.c
      15             :  *
      16             :  *-------------------------------------------------------------------------
      17             :  */
      18             : #include "postgres_fe.h"
      19             : 
      20             : #include <limits.h>
      21             : #include <math.h>
      22             : #include <unistd.h>
      23             : 
      24             : #ifndef WIN32
      25             : #include <sys/ioctl.h>            /* for ioctl() */
      26             : #endif
      27             : 
      28             : #ifdef HAVE_TERMIOS_H
      29             : #include <termios.h>
      30             : #endif
      31             : 
      32             : #include "catalog/pg_type_d.h"
      33             : #include "fe_utils/mbprint.h"
      34             : #include "fe_utils/print.h"
      35             : 
      36             : /*
      37             :  * If the calling program doesn't have any mechanism for setting
      38             :  * cancel_pressed, it will have no effect.
      39             :  *
      40             :  * Note: print.c's general strategy for when to check cancel_pressed is to do
      41             :  * so at completion of each row of output.
      42             :  */
      43             : volatile sig_atomic_t cancel_pressed = false;
      44             : 
      45             : static bool always_ignore_sigpipe = false;
      46             : 
      47             : /* info for locale-aware numeric formatting; set up by setDecimalLocale() */
      48             : static char *decimal_point;
      49             : static int  groupdigits;
      50             : static char *thousands_sep;
      51             : 
      52             : static char default_footer[100];
      53             : static printTableFooter default_footer_cell = {default_footer, NULL};
      54             : 
      55             : /* Line style control structures */
      56             : const printTextFormat pg_asciiformat =
      57             : {
      58             :     "ascii",
      59             :     {
      60             :         {"-", "+", "+", "+"},
      61             :         {"-", "+", "+", "+"},
      62             :         {"-", "+", "+", "+"},
      63             :         {"", "|", "|", "|"}
      64             :     },
      65             :     "|",
      66             :     "|",
      67             :     "|",
      68             :     " ",
      69             :     "+",
      70             :     " ",
      71             :     "+",
      72             :     ".",
      73             :     ".",
      74             :     true
      75             : };
      76             : 
      77             : const printTextFormat pg_asciiformat_old =
      78             : {
      79             :     "old-ascii",
      80             :     {
      81             :         {"-", "+", "+", "+"},
      82             :         {"-", "+", "+", "+"},
      83             :         {"-", "+", "+", "+"},
      84             :         {"", "|", "|", "|"}
      85             :     },
      86             :     ":",
      87             :     ";",
      88             :     " ",
      89             :     "+",
      90             :     " ",
      91             :     " ",
      92             :     " ",
      93             :     " ",
      94             :     " ",
      95             :     false
      96             : };
      97             : 
      98             : /* Default unicode linestyle format */
      99             : printTextFormat pg_utf8format;
     100             : 
     101             : typedef struct unicodeStyleRowFormat
     102             : {
     103             :     const char *horizontal;
     104             :     const char *vertical_and_right[2];
     105             :     const char *vertical_and_left[2];
     106             : } unicodeStyleRowFormat;
     107             : 
     108             : typedef struct unicodeStyleColumnFormat
     109             : {
     110             :     const char *vertical;
     111             :     const char *vertical_and_horizontal[2];
     112             :     const char *up_and_horizontal[2];
     113             :     const char *down_and_horizontal[2];
     114             : } unicodeStyleColumnFormat;
     115             : 
     116             : typedef struct unicodeStyleBorderFormat
     117             : {
     118             :     const char *up_and_right;
     119             :     const char *vertical;
     120             :     const char *down_and_right;
     121             :     const char *horizontal;
     122             :     const char *down_and_left;
     123             :     const char *left_and_right;
     124             : } unicodeStyleBorderFormat;
     125             : 
     126             : typedef struct unicodeStyleFormat
     127             : {
     128             :     unicodeStyleRowFormat row_style[2];
     129             :     unicodeStyleColumnFormat column_style[2];
     130             :     unicodeStyleBorderFormat border_style[2];
     131             :     const char *header_nl_left;
     132             :     const char *header_nl_right;
     133             :     const char *nl_left;
     134             :     const char *nl_right;
     135             :     const char *wrap_left;
     136             :     const char *wrap_right;
     137             :     bool        wrap_right_border;
     138             : } unicodeStyleFormat;
     139             : 
     140             : static const unicodeStyleFormat unicode_style = {
     141             :     {
     142             :         {
     143             :             /* ─ */
     144             :             "\342\224\200",
     145             :             /* ├╟ */
     146             :             {"\342\224\234", "\342\225\237"},
     147             :             /* ┤╢ */
     148             :             {"\342\224\244", "\342\225\242"},
     149             :         },
     150             :         {
     151             :             /* ═ */
     152             :             "\342\225\220",
     153             :             /* ╞╠ */
     154             :             {"\342\225\236", "\342\225\240"},
     155             :             /* ╡╣ */
     156             :             {"\342\225\241", "\342\225\243"},
     157             :         },
     158             :     },
     159             :     {
     160             :         {
     161             :             /* │ */
     162             :             "\342\224\202",
     163             :             /* ┼╪ */
     164             :             {"\342\224\274", "\342\225\252"},
     165             :             /* ┴╧ */
     166             :             {"\342\224\264", "\342\225\247"},
     167             :             /* ┬╤ */
     168             :             {"\342\224\254", "\342\225\244"},
     169             :         },
     170             :         {
     171             :             /* ║ */
     172             :             "\342\225\221",
     173             :             /* ╫╬ */
     174             :             {"\342\225\253", "\342\225\254"},
     175             :             /* ╨╩ */
     176             :             {"\342\225\250", "\342\225\251"},
     177             :             /* ╥╦ */
     178             :             {"\342\225\245", "\342\225\246"},
     179             :         },
     180             :     },
     181             :     {
     182             :         /* └│┌─┐┘ */
     183             :         {"\342\224\224", "\342\224\202", "\342\224\214", "\342\224\200", "\342\224\220", "\342\224\230"},
     184             :         /* ╚║╔═╗╝ */
     185             :         {"\342\225\232", "\342\225\221", "\342\225\224", "\342\225\220", "\342\225\227", "\342\225\235"},
     186             :     },
     187             :     " ",
     188             :     "\342\206\265",               /* ↵ */
     189             :     " ",
     190             :     "\342\206\265",               /* ↵ */
     191             :     "\342\200\246",               /* … */
     192             :     "\342\200\246",               /* … */
     193             :     true
     194             : };
     195             : 
     196             : 
     197             : /* Local functions */
     198             : static int  strlen_max_width(unsigned char *str, int *target_width, int encoding);
     199             : static void IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
     200             :                           FILE **fout, bool *is_pager);
     201             : 
     202             : static void print_aligned_vertical(const printTableContent *cont,
     203             :                                    FILE *fout, bool is_pager);
     204             : 
     205             : 
     206             : /* Count number of digits in integral part of number */
     207             : static int
     208         128 : integer_digits(const char *my_str)
     209             : {
     210             :     /* ignoring any sign ... */
     211         128 :     if (my_str[0] == '-' || my_str[0] == '+')
     212          24 :         my_str++;
     213             :     /* ... count initial integral digits */
     214         128 :     return strspn(my_str, "0123456789");
     215             : }
     216             : 
     217             : /* Compute additional length required for locale-aware numeric output */
     218             : static int
     219          64 : additional_numeric_locale_len(const char *my_str)
     220             : {
     221          64 :     int         int_len = integer_digits(my_str),
     222          64 :                 len = 0;
     223             : 
     224             :     /* Account for added thousands_sep instances */
     225          64 :     if (int_len > groupdigits)
     226           0 :         len += ((int_len - 1) / groupdigits) * strlen(thousands_sep);
     227             : 
     228             :     /* Account for possible additional length of decimal_point */
     229          64 :     if (strchr(my_str, '.') != NULL)
     230           0 :         len += strlen(decimal_point) - 1;
     231             : 
     232          64 :     return len;
     233             : }
     234             : 
     235             : /*
     236             :  * Format a numeric value per current LC_NUMERIC locale setting
     237             :  *
     238             :  * Returns the appropriately formatted string in a new allocated block,
     239             :  * caller must free.
     240             :  *
     241             :  * setDecimalLocale() must have been called earlier.
     242             :  */
     243             : static char *
     244          64 : format_numeric_locale(const char *my_str)
     245             : {
     246             :     char       *new_str;
     247             :     int         new_len,
     248             :                 int_len,
     249             :                 leading_digits,
     250             :                 i,
     251             :                 new_str_pos;
     252             : 
     253             :     /*
     254             :      * If the string doesn't look like a number, return it unchanged.  This
     255             :      * check is essential to avoid mangling already-localized "money" values.
     256             :      */
     257          64 :     if (strspn(my_str, "0123456789+-.eE") != strlen(my_str))
     258           0 :         return pg_strdup(my_str);
     259             : 
     260          64 :     new_len = strlen(my_str) + additional_numeric_locale_len(my_str);
     261          64 :     new_str = pg_malloc(new_len + 1);
     262          64 :     new_str_pos = 0;
     263          64 :     int_len = integer_digits(my_str);
     264             : 
     265             :     /* number of digits in first thousands group */
     266          64 :     leading_digits = int_len % groupdigits;
     267          64 :     if (leading_digits == 0)
     268          12 :         leading_digits = groupdigits;
     269             : 
     270             :     /* process sign */
     271          64 :     if (my_str[0] == '-' || my_str[0] == '+')
     272             :     {
     273          12 :         new_str[new_str_pos++] = my_str[0];
     274          12 :         my_str++;
     275             :     }
     276             : 
     277             :     /* process integer part of number */
     278         152 :     for (i = 0; i < int_len; i++)
     279             :     {
     280             :         /* Time to insert separator? */
     281          88 :         if (i > 0 && --leading_digits == 0)
     282             :         {
     283           0 :             strcpy(&new_str[new_str_pos], thousands_sep);
     284           0 :             new_str_pos += strlen(thousands_sep);
     285           0 :             leading_digits = groupdigits;
     286             :         }
     287          88 :         new_str[new_str_pos++] = my_str[i];
     288             :     }
     289             : 
     290             :     /* handle decimal point if any */
     291          64 :     if (my_str[i] == '.')
     292             :     {
     293           0 :         strcpy(&new_str[new_str_pos], decimal_point);
     294           0 :         new_str_pos += strlen(decimal_point);
     295           0 :         i++;
     296             :     }
     297             : 
     298             :     /* copy the rest (fractional digits and/or exponent, and \0 terminator) */
     299          64 :     strcpy(&new_str[new_str_pos], &my_str[i]);
     300             : 
     301             :     /* assert we didn't underestimate new_len (an overestimate is OK) */
     302             :     Assert(strlen(new_str) <= new_len);
     303             : 
     304          64 :     return new_str;
     305             : }
     306             : 
     307             : 
     308             : static void
     309      234948 : print_separator(struct separator sep, FILE *fout)
     310             : {
     311      234948 :     if (sep.separator_zero)
     312           0 :         fputc('\000', fout);
     313      234948 :     else if (sep.separator)
     314      234948 :         fputs(sep.separator, fout);
     315      234948 : }
     316             : 
     317             : 
     318             : /*
     319             :  * Return the list of explicitly-requested footers or, when applicable, the
     320             :  * default "(xx rows)" footer.  Always omit the default footer when given
     321             :  * non-default footers, "\pset footer off", or a specific instruction to that
     322             :  * effect from a calling backslash command.  Vertical formats number each row,
     323             :  * making the default footer redundant; they do not call this function.
     324             :  *
     325             :  * The return value may point to static storage; do not keep it across calls.
     326             :  */
     327             : static printTableFooter *
     328       72026 : footers_with_default(const printTableContent *cont)
     329             : {
     330       72026 :     if (cont->footers == NULL && cont->opt->default_footer)
     331             :     {
     332             :         unsigned long total_records;
     333             : 
     334       70112 :         total_records = cont->opt->prior_records + cont->nrows;
     335       70112 :         snprintf(default_footer, sizeof(default_footer),
     336       70112 :                  ngettext("(%lu row)", "(%lu rows)", total_records),
     337             :                  total_records);
     338             : 
     339       70112 :         return &default_footer_cell;
     340             :     }
     341             :     else
     342        1914 :         return cont->footers;
     343             : }
     344             : 
     345             : 
     346             : /*************************/
     347             : /* Unaligned text        */
     348             : /*************************/
     349             : 
     350             : 
     351             : static void
     352        3224 : print_unaligned_text(const printTableContent *cont, FILE *fout)
     353             : {
     354        3224 :     bool        opt_tuples_only = cont->opt->tuples_only;
     355             :     unsigned int i;
     356             :     const char *const *ptr;
     357        3224 :     bool        need_recordsep = false;
     358             : 
     359        3224 :     if (cancel_pressed)
     360           0 :         return;
     361             : 
     362        3224 :     if (cont->opt->start_table)
     363             :     {
     364             :         /* print title */
     365        3224 :         if (!opt_tuples_only && cont->title)
     366             :         {
     367           4 :             fputs(cont->title, fout);
     368           4 :             print_separator(cont->opt->recordSep, fout);
     369             :         }
     370             : 
     371             :         /* print headers */
     372        3224 :         if (!opt_tuples_only)
     373             :         {
     374         240 :             for (ptr = cont->headers; *ptr; ptr++)
     375             :             {
     376         170 :                 if (ptr != cont->headers)
     377         100 :                     print_separator(cont->opt->fieldSep, fout);
     378         170 :                 fputs(*ptr, fout);
     379             :             }
     380          70 :             need_recordsep = true;
     381             :         }
     382             :     }
     383             :     else
     384             :         /* assume continuing printout */
     385           0 :         need_recordsep = true;
     386             : 
     387             :     /* print cells */
     388      238856 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
     389             :     {
     390      235632 :         if (need_recordsep)
     391             :         {
     392       13608 :             print_separator(cont->opt->recordSep, fout);
     393       13608 :             need_recordsep = false;
     394       13608 :             if (cancel_pressed)
     395           0 :                 break;
     396             :         }
     397      235632 :         fputs(*ptr, fout);
     398             : 
     399      235632 :         if ((i + 1) % cont->ncolumns)
     400      219098 :             print_separator(cont->opt->fieldSep, fout);
     401             :         else
     402       16534 :             need_recordsep = true;
     403             :     }
     404             : 
     405             :     /* print footers */
     406        3224 :     if (cont->opt->stop_table)
     407             :     {
     408        3224 :         printTableFooter *footers = footers_with_default(cont);
     409             : 
     410        3224 :         if (!opt_tuples_only && footers != NULL && !cancel_pressed)
     411             :         {
     412             :             printTableFooter *f;
     413             : 
     414         140 :             for (f = footers; f; f = f->next)
     415             :             {
     416          70 :                 if (need_recordsep)
     417             :                 {
     418          70 :                     print_separator(cont->opt->recordSep, fout);
     419          70 :                     need_recordsep = false;
     420             :                 }
     421          70 :                 fputs(f->data, fout);
     422          70 :                 need_recordsep = true;
     423             :             }
     424             :         }
     425             : 
     426             :         /*
     427             :          * The last record is terminated by a newline, independent of the set
     428             :          * record separator.  But when the record separator is a zero byte, we
     429             :          * use that (compatible with find -print0 and xargs).
     430             :          */
     431        3224 :         if (need_recordsep)
     432             :         {
     433        2996 :             if (cont->opt->recordSep.separator_zero)
     434           0 :                 print_separator(cont->opt->recordSep, fout);
     435             :             else
     436        2996 :                 fputc('\n', fout);
     437             :         }
     438             :     }
     439             : }
     440             : 
     441             : 
     442             : static void
     443          68 : print_unaligned_vertical(const printTableContent *cont, FILE *fout)
     444             : {
     445          68 :     bool        opt_tuples_only = cont->opt->tuples_only;
     446             :     unsigned int i;
     447             :     const char *const *ptr;
     448          68 :     bool        need_recordsep = false;
     449             : 
     450          68 :     if (cancel_pressed)
     451           0 :         return;
     452             : 
     453          68 :     if (cont->opt->start_table)
     454             :     {
     455             :         /* print title */
     456          68 :         if (!opt_tuples_only && cont->title)
     457             :         {
     458           4 :             fputs(cont->title, fout);
     459           4 :             need_recordsep = true;
     460             :         }
     461             :     }
     462             :     else
     463             :         /* assume continuing printout */
     464           0 :         need_recordsep = true;
     465             : 
     466             :     /* print records */
     467         952 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
     468             :     {
     469         884 :         if (need_recordsep)
     470             :         {
     471             :             /* record separator is 2 occurrences of recordsep in this mode */
     472         356 :             print_separator(cont->opt->recordSep, fout);
     473         356 :             print_separator(cont->opt->recordSep, fout);
     474         356 :             need_recordsep = false;
     475         356 :             if (cancel_pressed)
     476           0 :                 break;
     477             :         }
     478             : 
     479         884 :         fputs(cont->headers[i % cont->ncolumns], fout);
     480         884 :         print_separator(cont->opt->fieldSep, fout);
     481         884 :         fputs(*ptr, fout);
     482             : 
     483         884 :         if ((i + 1) % cont->ncolumns)
     484         464 :             print_separator(cont->opt->recordSep, fout);
     485             :         else
     486         420 :             need_recordsep = true;
     487             :     }
     488             : 
     489          68 :     if (cont->opt->stop_table)
     490             :     {
     491             :         /* print footers */
     492          68 :         if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
     493             :         {
     494             :             printTableFooter *f;
     495             : 
     496           4 :             print_separator(cont->opt->recordSep, fout);
     497           8 :             for (f = cont->footers; f; f = f->next)
     498             :             {
     499           4 :                 print_separator(cont->opt->recordSep, fout);
     500           4 :                 fputs(f->data, fout);
     501             :             }
     502             :         }
     503             : 
     504             :         /* see above in print_unaligned_text() */
     505          68 :         if (need_recordsep)
     506             :         {
     507          68 :             if (cont->opt->recordSep.separator_zero)
     508           0 :                 print_separator(cont->opt->recordSep, fout);
     509             :             else
     510          68 :                 fputc('\n', fout);
     511             :         }
     512             :     }
     513             : }
     514             : 
     515             : 
     516             : /********************/
     517             : /* Aligned text     */
     518             : /********************/
     519             : 
     520             : 
     521             : /* draw "line" */
     522             : static void
     523       68758 : _print_horizontal_line(const unsigned int ncolumns, const unsigned int *widths,
     524             :                        unsigned short border, printTextRule pos,
     525             :                        const printTextFormat *format,
     526             :                        FILE *fout)
     527             : {
     528       68758 :     const printTextLineFormat *lformat = &format->lrule[pos];
     529             :     unsigned int i,
     530             :                 j;
     531             : 
     532       68758 :     if (border == 1)
     533       68630 :         fputs(lformat->hrule, fout);
     534         128 :     else if (border == 2)
     535          96 :         fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
     536             : 
     537      195048 :     for (i = 0; i < ncolumns; i++)
     538             :     {
     539     2114606 :         for (j = 0; j < widths[i]; j++)
     540     1988316 :             fputs(lformat->hrule, fout);
     541             : 
     542      126290 :         if (i < ncolumns - 1)
     543             :         {
     544       57600 :             if (border == 0)
     545          32 :                 fputc(' ', fout);
     546             :             else
     547       57568 :                 fprintf(fout, "%s%s%s", lformat->hrule,
     548             :                         lformat->midvrule, lformat->hrule);
     549             :         }
     550             :     }
     551             : 
     552       68758 :     if (border == 2)
     553          96 :         fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
     554       68662 :     else if (border == 1)
     555       68630 :         fputs(lformat->hrule, fout);
     556             : 
     557       68758 :     fputc('\n', fout);
     558       68758 : }
     559             : 
     560             : 
     561             : /*
     562             :  *  Print pretty boxes around cells.
     563             :  */
     564             : static void
     565       68746 : print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
     566             : {
     567       68746 :     bool        opt_tuples_only = cont->opt->tuples_only;
     568       68746 :     int         encoding = cont->opt->encoding;
     569       68746 :     unsigned short opt_border = cont->opt->border;
     570       68746 :     const printTextFormat *format = get_line_style(cont->opt);
     571       68746 :     const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
     572             : 
     573       68746 :     unsigned int col_count = 0,
     574       68746 :                 cell_count = 0;
     575             : 
     576             :     unsigned int i,
     577             :                 j;
     578             : 
     579             :     unsigned int *width_header,
     580             :                *max_width,
     581             :                *width_wrap,
     582             :                *width_average;
     583             :     unsigned int *max_nl_lines, /* value split by newlines */
     584             :                *curr_nl_line,
     585             :                *max_bytes;
     586             :     unsigned char **format_buf;
     587             :     unsigned int width_total;
     588             :     unsigned int total_header_width;
     589       68746 :     unsigned int extra_row_output_lines = 0;
     590       68746 :     unsigned int extra_output_lines = 0;
     591             : 
     592             :     const char *const *ptr;
     593             : 
     594             :     struct lineptr **col_lineptrs;  /* pointers to line pointer per column */
     595             : 
     596             :     bool       *header_done;    /* Have all header lines been output? */
     597             :     int        *bytes_output;   /* Bytes output for column value */
     598             :     printTextLineWrap *wrap;    /* Wrap status for each column */
     599       68746 :     int         output_columns = 0; /* Width of interactive console */
     600       68746 :     bool        is_local_pager = false;
     601             : 
     602       68746 :     if (cancel_pressed)
     603           0 :         return;
     604             : 
     605       68746 :     if (opt_border > 2)
     606           0 :         opt_border = 2;
     607             : 
     608       68746 :     if (cont->ncolumns > 0)
     609             :     {
     610       68678 :         col_count = cont->ncolumns;
     611       68678 :         width_header = pg_malloc0(col_count * sizeof(*width_header));
     612       68678 :         width_average = pg_malloc0(col_count * sizeof(*width_average));
     613       68678 :         max_width = pg_malloc0(col_count * sizeof(*max_width));
     614       68678 :         width_wrap = pg_malloc0(col_count * sizeof(*width_wrap));
     615       68678 :         max_nl_lines = pg_malloc0(col_count * sizeof(*max_nl_lines));
     616       68678 :         curr_nl_line = pg_malloc0(col_count * sizeof(*curr_nl_line));
     617       68678 :         col_lineptrs = pg_malloc0(col_count * sizeof(*col_lineptrs));
     618       68678 :         max_bytes = pg_malloc0(col_count * sizeof(*max_bytes));
     619       68678 :         format_buf = pg_malloc0(col_count * sizeof(*format_buf));
     620       68678 :         header_done = pg_malloc0(col_count * sizeof(*header_done));
     621       68678 :         bytes_output = pg_malloc0(col_count * sizeof(*bytes_output));
     622       68678 :         wrap = pg_malloc0(col_count * sizeof(*wrap));
     623             :     }
     624             :     else
     625             :     {
     626          68 :         width_header = NULL;
     627          68 :         width_average = NULL;
     628          68 :         max_width = NULL;
     629          68 :         width_wrap = NULL;
     630          68 :         max_nl_lines = NULL;
     631          68 :         curr_nl_line = NULL;
     632          68 :         col_lineptrs = NULL;
     633          68 :         max_bytes = NULL;
     634          68 :         format_buf = NULL;
     635          68 :         header_done = NULL;
     636          68 :         bytes_output = NULL;
     637          68 :         wrap = NULL;
     638             :     }
     639             : 
     640             :     /* scan all column headers, find maximum width and max max_nl_lines */
     641      195016 :     for (i = 0; i < col_count; i++)
     642             :     {
     643             :         int         width,
     644             :                     nl_lines,
     645             :                     bytes_required;
     646             : 
     647      126270 :         pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
     648             :                    encoding, &width, &nl_lines, &bytes_required);
     649      126270 :         if (width > max_width[i])
     650      126246 :             max_width[i] = width;
     651      126270 :         if (nl_lines > max_nl_lines[i])
     652      126270 :             max_nl_lines[i] = nl_lines;
     653      126270 :         if (bytes_required > max_bytes[i])
     654      126270 :             max_bytes[i] = bytes_required;
     655      126270 :         if (nl_lines > extra_row_output_lines)
     656       68678 :             extra_row_output_lines = nl_lines;
     657             : 
     658      126270 :         width_header[i] = width;
     659             :     }
     660             :     /* Add height of tallest header column */
     661       68746 :     extra_output_lines += extra_row_output_lines;
     662       68746 :     extra_row_output_lines = 0;
     663             : 
     664             :     /* scan all cells, find maximum width, compute cell_count */
     665      710572 :     for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++)
     666             :     {
     667             :         int         width,
     668             :                     nl_lines,
     669             :                     bytes_required;
     670             : 
     671      641826 :         pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
     672             :                    &width, &nl_lines, &bytes_required);
     673             : 
     674      641826 :         if (width > max_width[i % col_count])
     675       73172 :             max_width[i % col_count] = width;
     676      641826 :         if (nl_lines > max_nl_lines[i % col_count])
     677        1010 :             max_nl_lines[i % col_count] = nl_lines;
     678      641826 :         if (bytes_required > max_bytes[i % col_count])
     679       73258 :             max_bytes[i % col_count] = bytes_required;
     680             : 
     681      641826 :         width_average[i % col_count] += width;
     682             :     }
     683             : 
     684             :     /* If we have rows, compute average */
     685       68746 :     if (col_count != 0 && cell_count != 0)
     686             :     {
     687       65482 :         int         rows = cell_count / col_count;
     688             : 
     689      183830 :         for (i = 0; i < col_count; i++)
     690      118348 :             width_average[i] /= rows;
     691             :     }
     692             : 
     693             :     /* adjust the total display width based on border style */
     694       68746 :     if (opt_border == 0)
     695          32 :         width_total = col_count;
     696       68714 :     else if (opt_border == 1)
     697       68682 :         width_total = col_count * 3 - ((col_count > 0) ? 1 : 0);
     698             :     else
     699          32 :         width_total = col_count * 3 + 1;
     700       68746 :     total_header_width = width_total;
     701             : 
     702      195016 :     for (i = 0; i < col_count; i++)
     703             :     {
     704      126270 :         width_total += max_width[i];
     705      126270 :         total_header_width += width_header[i];
     706             :     }
     707             : 
     708             :     /*
     709             :      * At this point: max_width[] contains the max width of each column,
     710             :      * max_nl_lines[] contains the max number of lines in each column,
     711             :      * max_bytes[] contains the maximum storage space for formatting strings,
     712             :      * width_total contains the giant width sum.  Now we allocate some memory
     713             :      * for line pointers.
     714             :      */
     715      195016 :     for (i = 0; i < col_count; i++)
     716             :     {
     717             :         /* Add entry for ptr == NULL array termination */
     718      126270 :         col_lineptrs[i] = pg_malloc0((max_nl_lines[i] + 1) *
     719             :                                      sizeof(**col_lineptrs));
     720             : 
     721      126270 :         format_buf[i] = pg_malloc(max_bytes[i] + 1);
     722             : 
     723      126270 :         col_lineptrs[i]->ptr = format_buf[i];
     724             :     }
     725             : 
     726             :     /* Default word wrap to the full width, i.e. no word wrap */
     727      195016 :     for (i = 0; i < col_count; i++)
     728      126270 :         width_wrap[i] = max_width[i];
     729             : 
     730             :     /*
     731             :      * Choose target output width: \pset columns, or $COLUMNS, or ioctl
     732             :      */
     733       68746 :     if (cont->opt->columns > 0)
     734         364 :         output_columns = cont->opt->columns;
     735       68382 :     else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
     736             :     {
     737          52 :         if (cont->opt->env_columns > 0)
     738           0 :             output_columns = cont->opt->env_columns;
     739             : #ifdef TIOCGWINSZ
     740             :         else
     741             :         {
     742             :             struct winsize screen_size;
     743             : 
     744          52 :             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
     745           0 :                 output_columns = screen_size.ws_col;
     746             :         }
     747             : #endif
     748             :     }
     749             : 
     750       68746 :     if (cont->opt->format == PRINT_WRAPPED)
     751             :     {
     752             :         /*
     753             :          * Optional optimized word wrap. Shrink columns with a high max/avg
     754             :          * ratio.  Slightly bias against wider columns. (Increases chance a
     755             :          * narrow column will fit in its cell.)  If available columns is
     756             :          * positive...  and greater than the width of the unshrinkable column
     757             :          * headers
     758             :          */
     759          92 :         if (output_columns > 0 && output_columns >= total_header_width)
     760             :         {
     761             :             /* While there is still excess width... */
     762         176 :             while (width_total > output_columns)
     763             :             {
     764         128 :                 double      max_ratio = 0;
     765         128 :                 int         worst_col = -1;
     766             : 
     767             :                 /*
     768             :                  * Find column that has the highest ratio of its maximum width
     769             :                  * compared to its average width.  This tells us which column
     770             :                  * will produce the fewest wrapped values if shortened.
     771             :                  * width_wrap starts as equal to max_width.
     772             :                  */
     773         384 :                 for (i = 0; i < col_count; i++)
     774             :                 {
     775         256 :                     if (width_average[i] && width_wrap[i] > width_header[i])
     776             :                     {
     777             :                         /* Penalize wide columns by 1% of their width */
     778             :                         double      ratio;
     779             : 
     780         512 :                         ratio = (double) width_wrap[i] / width_average[i] +
     781         256 :                             max_width[i] * 0.01;
     782         256 :                         if (ratio > max_ratio)
     783             :                         {
     784         168 :                             max_ratio = ratio;
     785         168 :                             worst_col = i;
     786             :                         }
     787             :                     }
     788             :                 }
     789             : 
     790             :                 /* Exit loop if we can't squeeze any more. */
     791         128 :                 if (worst_col == -1)
     792           0 :                     break;
     793             : 
     794             :                 /* Decrease width of target column by one. */
     795         128 :                 width_wrap[worst_col]--;
     796         128 :                 width_total--;
     797             :             }
     798             :         }
     799             :     }
     800             : 
     801             :     /*
     802             :      * If in expanded auto mode, we have now calculated the expected width, so
     803             :      * we can now escape to vertical mode if necessary.  If the output has
     804             :      * only one column, the expanded format would be wider than the regular
     805             :      * format, so don't use it in that case.
     806             :      */
     807       68746 :     if (cont->opt->expanded == 2 && output_columns > 0 && cont->ncolumns > 1 &&
     808           0 :         (output_columns < total_header_width || output_columns < width_total))
     809             :     {
     810           0 :         print_aligned_vertical(cont, fout, is_pager);
     811           0 :         goto cleanup;
     812             :     }
     813             : 
     814             :     /* If we wrapped beyond the display width, use the pager */
     815       68746 :     if (!is_pager && fout == stdout && output_columns > 0 &&
     816         352 :         (output_columns < total_header_width || output_columns < width_total))
     817             :     {
     818         212 :         fout = PageOutput(INT_MAX, cont->opt);   /* force pager */
     819         212 :         is_pager = is_local_pager = true;
     820             :     }
     821             : 
     822             :     /* Check if newlines or our wrapping now need the pager */
     823       68746 :     if (!is_pager && fout == stdout)
     824             :     {
     825             :         /* scan all cells, find maximum width, compute cell_count */
     826      706472 :         for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
     827             :         {
     828             :             int         width,
     829             :                         nl_lines,
     830             :                         bytes_required;
     831             : 
     832      638002 :             pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
     833             :                        &width, &nl_lines, &bytes_required);
     834             : 
     835             :             /*
     836             :              * A row can have both wrapping and newlines that cause it to
     837             :              * display across multiple lines.  We check for both cases below.
     838             :              */
     839      638002 :             if (width > 0 && width_wrap[i])
     840             :             {
     841             :                 unsigned int extra_lines;
     842             : 
     843             :                 /* don't count the first line of nl_lines - it's not "extra" */
     844      591206 :                 extra_lines = ((width - 1) / width_wrap[i]) + nl_lines - 1;
     845      591206 :                 if (extra_lines > extra_row_output_lines)
     846        1048 :                     extra_row_output_lines = extra_lines;
     847             :             }
     848             : 
     849             :             /* i is the current column number: increment with wrap */
     850      638002 :             if (++i >= col_count)
     851             :             {
     852      275106 :                 i = 0;
     853             :                 /* At last column of each row, add tallest column height */
     854      275106 :                 extra_output_lines += extra_row_output_lines;
     855      275106 :                 extra_row_output_lines = 0;
     856             :             }
     857             :         }
     858       68470 :         IsPagerNeeded(cont, extra_output_lines, false, &fout, &is_pager);
     859       68470 :         is_local_pager = is_pager;
     860             :     }
     861             : 
     862             :     /* time to output */
     863       68746 :     if (cont->opt->start_table)
     864             :     {
     865             :         /* print title */
     866       68710 :         if (cont->title && !opt_tuples_only)
     867             :         {
     868             :             int         width,
     869             :                         height;
     870             : 
     871        2462 :             pg_wcssize((const unsigned char *) cont->title, strlen(cont->title),
     872             :                        encoding, &width, &height, NULL);
     873        2462 :             if (width >= width_total)
     874             :                 /* Aligned */
     875         118 :                 fprintf(fout, "%s\n", cont->title);
     876             :             else
     877             :                 /* Centered */
     878        2344 :                 fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "",
     879             :                         cont->title);
     880             :         }
     881             : 
     882             :         /* print headers */
     883       68710 :         if (!opt_tuples_only)
     884             :         {
     885             :             int         more_col_wrapping;
     886             :             int         curr_nl_line;
     887             : 
     888       68694 :             if (opt_border == 2)
     889          32 :                 _print_horizontal_line(col_count, width_wrap, opt_border,
     890             :                                        PRINT_RULE_TOP, format, fout);
     891             : 
     892      194856 :             for (i = 0; i < col_count; i++)
     893      378486 :                 pg_wcsformat((const unsigned char *) cont->headers[i],
     894      126162 :                              strlen(cont->headers[i]), encoding,
     895      126162 :                              col_lineptrs[i], max_nl_lines[i]);
     896             : 
     897       68694 :             more_col_wrapping = col_count;
     898       68694 :             curr_nl_line = 0;
     899       68694 :             memset(header_done, false, col_count * sizeof(bool));
     900      137416 :             while (more_col_wrapping)
     901             :             {
     902       68722 :                 if (opt_border == 2)
     903          64 :                     fputs(dformat->leftvrule, fout);
     904             : 
     905      195076 :                 for (i = 0; i < cont->ncolumns; i++)
     906             :                 {
     907      126354 :                     struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;
     908             :                     unsigned int nbspace;
     909             : 
     910      126354 :                     if (opt_border != 0 ||
     911         128 :                         (!format->wrap_right_border && i > 0))
     912      126258 :                         fputs(curr_nl_line ? format->header_nl_left : " ",
     913             :                               fout);
     914             : 
     915      126354 :                     if (!header_done[i])
     916             :                     {
     917      126306 :                         nbspace = width_wrap[i] - this_line->width;
     918             : 
     919             :                         /* centered */
     920      126306 :                         fprintf(fout, "%-*s%s%-*s",
     921      126306 :                                 nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
     922             : 
     923      126306 :                         if (!(this_line + 1)->ptr)
     924             :                         {
     925      126162 :                             more_col_wrapping--;
     926      126162 :                             header_done[i] = 1;
     927             :                         }
     928             :                     }
     929             :                     else
     930          48 :                         fprintf(fout, "%*s", width_wrap[i], "");
     931             : 
     932      126354 :                     if (opt_border != 0 || format->wrap_right_border)
     933      126290 :                         fputs(!header_done[i] ? format->header_nl_right : " ",
     934             :                               fout);
     935             : 
     936      126354 :                     if (opt_border != 0 && col_count > 0 && i < col_count - 1)
     937       57568 :                         fputs(dformat->midvrule, fout);
     938             :                 }
     939       68722 :                 curr_nl_line++;
     940             : 
     941       68722 :                 if (opt_border == 2)
     942          64 :                     fputs(dformat->rightvrule, fout);
     943       68722 :                 fputc('\n', fout);
     944             :             }
     945             : 
     946       68694 :             _print_horizontal_line(col_count, width_wrap, opt_border,
     947             :                                    PRINT_RULE_MIDDLE, format, fout);
     948             :         }
     949             :     }
     950             : 
     951             :     /* print cells, one loop per row */
     952      344792 :     for (i = 0, ptr = cont->cells; *ptr; i += col_count, ptr += col_count)
     953             :     {
     954             :         bool        more_lines;
     955             : 
     956      276046 :         if (cancel_pressed)
     957           0 :             break;
     958             : 
     959             :         /*
     960             :          * Format each cell.
     961             :          */
     962      917872 :         for (j = 0; j < col_count; j++)
     963             :         {
     964     1283652 :             pg_wcsformat((const unsigned char *) ptr[j], strlen(ptr[j]), encoding,
     965      641826 :                          col_lineptrs[j], max_nl_lines[j]);
     966      641826 :             curr_nl_line[j] = 0;
     967             :         }
     968             : 
     969      276046 :         memset(bytes_output, 0, col_count * sizeof(int));
     970             : 
     971             :         /*
     972             :          * Each time through this loop, one display line is output. It can
     973             :          * either be a full value or a partial value if embedded newlines
     974             :          * exist or if 'format=wrapping' mode is enabled.
     975             :          */
     976             :         do
     977             :         {
     978      286402 :             more_lines = false;
     979             : 
     980             :             /* left border */
     981      286402 :             if (opt_border == 2)
     982         368 :                 fputs(dformat->leftvrule, fout);
     983             : 
     984             :             /* for each column */
     985      940950 :             for (j = 0; j < col_count; j++)
     986             :             {
     987             :                 /* We have a valid array element, so index it */
     988      654548 :                 struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]];
     989             :                 int         bytes_to_output;
     990      654548 :                 int         chars_to_output = width_wrap[j];
     991     1308360 :                 bool        finalspaces = (opt_border == 2 ||
     992      653812 :                                            (col_count > 0 && j < col_count - 1));
     993             : 
     994             :                 /* Print left-hand wrap or newline mark */
     995      654548 :                 if (opt_border != 0)
     996             :                 {
     997      653908 :                     if (wrap[j] == PRINT_LINE_WRAP_WRAP)
     998          80 :                         fputs(format->wrap_left, fout);
     999      653828 :                     else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE)
    1000       10408 :                         fputs(format->nl_left, fout);
    1001             :                     else
    1002      643420 :                         fputc(' ', fout);
    1003             :                 }
    1004             : 
    1005      654548 :                 if (!this_line->ptr)
    1006             :                 {
    1007             :                     /* Past newline lines so just pad for other columns */
    1008        1978 :                     if (finalspaces)
    1009        1656 :                         fprintf(fout, "%*s", chars_to_output, "");
    1010             :                 }
    1011             :                 else
    1012             :                 {
    1013             :                     /* Get strlen() of the characters up to width_wrap */
    1014             :                     bytes_to_output =
    1015      652570 :                         strlen_max_width(this_line->ptr + bytes_output[j],
    1016             :                                          &chars_to_output, encoding);
    1017             : 
    1018             :                     /*
    1019             :                      * If we exceeded width_wrap, it means the display width
    1020             :                      * of a single character was wider than our target width.
    1021             :                      * In that case, we have to pretend we are only printing
    1022             :                      * the target display width and make the best of it.
    1023             :                      */
    1024      652570 :                     if (chars_to_output > width_wrap[j])
    1025           0 :                         chars_to_output = width_wrap[j];
    1026             : 
    1027      652570 :                     if (cont->aligns[j] == 'r') /* Right aligned cell */
    1028             :                     {
    1029             :                         /* spaces first */
    1030      271052 :                         fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
    1031      271052 :                         fwrite((char *) (this_line->ptr + bytes_output[j]),
    1032             :                                1, bytes_to_output, fout);
    1033             :                     }
    1034             :                     else        /* Left aligned cell */
    1035             :                     {
    1036             :                         /* spaces second */
    1037      381518 :                         fwrite((char *) (this_line->ptr + bytes_output[j]),
    1038             :                                1, bytes_to_output, fout);
    1039             :                     }
    1040             : 
    1041      652570 :                     bytes_output[j] += bytes_to_output;
    1042             : 
    1043             :                     /* Do we have more text to wrap? */
    1044      652570 :                     if (*(this_line->ptr + bytes_output[j]) != '\0')
    1045          80 :                         more_lines = true;
    1046             :                     else
    1047             :                     {
    1048             :                         /* Advance to next newline line */
    1049      652490 :                         curr_nl_line[j]++;
    1050      652490 :                         if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
    1051       10664 :                             more_lines = true;
    1052      652490 :                         bytes_output[j] = 0;
    1053             :                     }
    1054             :                 }
    1055             : 
    1056             :                 /* Determine next line's wrap status for this column */
    1057      654548 :                 wrap[j] = PRINT_LINE_WRAP_NONE;
    1058      654548 :                 if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
    1059             :                 {
    1060       10744 :                     if (bytes_output[j] != 0)
    1061          80 :                         wrap[j] = PRINT_LINE_WRAP_WRAP;
    1062       10664 :                     else if (curr_nl_line[j] != 0)
    1063       10664 :                         wrap[j] = PRINT_LINE_WRAP_NEWLINE;
    1064             :                 }
    1065             : 
    1066             :                 /*
    1067             :                  * If left-aligned, pad out remaining space if needed (not
    1068             :                  * last column, and/or wrap marks required).
    1069             :                  */
    1070      654548 :                 if (cont->aligns[j] != 'r') /* Left aligned cell */
    1071             :                 {
    1072      383290 :                     if (finalspaces ||
    1073      196708 :                         wrap[j] == PRINT_LINE_WRAP_WRAP ||
    1074      196700 :                         wrap[j] == PRINT_LINE_WRAP_NEWLINE)
    1075      196440 :                         fprintf(fout, "%*s",
    1076      196440 :                                 width_wrap[j] - chars_to_output, "");
    1077             :                 }
    1078             : 
    1079             :                 /* Print right-hand wrap or newline mark */
    1080      654548 :                 if (wrap[j] == PRINT_LINE_WRAP_WRAP)
    1081          80 :                     fputs(format->wrap_right, fout);
    1082      654468 :                 else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE)
    1083       10664 :                     fputs(format->nl_right, fout);
    1084      643804 :                 else if (opt_border == 2 || (col_count > 0 && j < col_count - 1))
    1085      367628 :                     fputc(' ', fout);
    1086             : 
    1087             :                 /* Print column divider, if not the last column */
    1088      654548 :                 if (opt_border != 0 && (col_count > 0 && j < col_count - 1))
    1089             :                 {
    1090      367826 :                     if (wrap[j + 1] == PRINT_LINE_WRAP_WRAP)
    1091          24 :                         fputs(format->midvrule_wrap, fout);
    1092      367802 :                     else if (wrap[j + 1] == PRINT_LINE_WRAP_NEWLINE)
    1093         648 :                         fputs(format->midvrule_nl, fout);
    1094      367154 :                     else if (col_lineptrs[j + 1][curr_nl_line[j + 1]].ptr == NULL)
    1095        1566 :                         fputs(format->midvrule_blank, fout);
    1096             :                     else
    1097      365588 :                         fputs(dformat->midvrule, fout);
    1098             :                 }
    1099             :             }
    1100             : 
    1101             :             /* end-of-row border */
    1102      286402 :             if (opt_border == 2)
    1103         368 :                 fputs(dformat->rightvrule, fout);
    1104      286402 :             fputc('\n', fout);
    1105             : 
    1106      286402 :         } while (more_lines);
    1107             :     }
    1108             : 
    1109       68746 :     if (cont->opt->stop_table)
    1110             :     {
    1111       68706 :         printTableFooter *footers = footers_with_default(cont);
    1112             : 
    1113       68706 :         if (opt_border == 2 && !cancel_pressed)
    1114          32 :             _print_horizontal_line(col_count, width_wrap, opt_border,
    1115             :                                    PRINT_RULE_BOTTOM, format, fout);
    1116             : 
    1117             :         /* print footers */
    1118       68706 :         if (footers && !opt_tuples_only && !cancel_pressed)
    1119             :         {
    1120             :             printTableFooter *f;
    1121             : 
    1122      140132 :             for (f = footers; f; f = f->next)
    1123       71690 :                 fprintf(fout, "%s\n", f->data);
    1124             :         }
    1125             : 
    1126       68706 :         fputc('\n', fout);
    1127             :     }
    1128             : 
    1129          40 : cleanup:
    1130             :     /* clean up */
    1131      195016 :     for (i = 0; i < col_count; i++)
    1132             :     {
    1133      126270 :         free(col_lineptrs[i]);
    1134      126270 :         free(format_buf[i]);
    1135             :     }
    1136       68746 :     free(width_header);
    1137       68746 :     free(width_average);
    1138       68746 :     free(max_width);
    1139       68746 :     free(width_wrap);
    1140       68746 :     free(max_nl_lines);
    1141       68746 :     free(curr_nl_line);
    1142       68746 :     free(col_lineptrs);
    1143       68746 :     free(max_bytes);
    1144       68746 :     free(format_buf);
    1145       68746 :     free(header_done);
    1146       68746 :     free(bytes_output);
    1147       68746 :     free(wrap);
    1148             : 
    1149       68746 :     if (is_local_pager)
    1150         212 :         ClosePager(fout);
    1151             : }
    1152             : 
    1153             : 
    1154             : static void
    1155        1082 : print_aligned_vertical_line(const printTextFormat *format,
    1156             :                             const unsigned short opt_border,
    1157             :                             unsigned long record,
    1158             :                             unsigned int hwidth,
    1159             :                             unsigned int dwidth,
    1160             :                             printTextRule pos,
    1161             :                             FILE *fout)
    1162             : {
    1163        1082 :     const printTextLineFormat *lformat = &format->lrule[pos];
    1164             :     unsigned int i;
    1165        1082 :     int         reclen = 0;
    1166             : 
    1167        1082 :     if (opt_border == 2)
    1168         312 :         fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
    1169         770 :     else if (opt_border == 1)
    1170         498 :         fputs(lformat->hrule, fout);
    1171             : 
    1172        1082 :     if (record)
    1173             :     {
    1174        1034 :         if (opt_border == 0)
    1175         272 :             reclen = fprintf(fout, "* Record %lu", record);
    1176             :         else
    1177         762 :             reclen = fprintf(fout, "[ RECORD %lu ]", record);
    1178             :     }
    1179        1082 :     if (opt_border != 2)
    1180         770 :         reclen++;
    1181        1082 :     if (reclen < 0)
    1182           0 :         reclen = 0;
    1183        5100 :     for (i = reclen; i < hwidth; i++)
    1184        4018 :         fputs(opt_border > 0 ? lformat->hrule : " ", fout);
    1185        1082 :     reclen -= hwidth;
    1186             : 
    1187        1082 :     if (opt_border > 0)
    1188             :     {
    1189         810 :         if (reclen-- <= 0)
    1190         702 :             fputs(lformat->hrule, fout);
    1191         810 :         if (reclen-- <= 0)
    1192         702 :             fputs(lformat->midvrule, fout);
    1193         810 :         if (reclen-- <= 0)
    1194         702 :             fputs(lformat->hrule, fout);
    1195             :     }
    1196             :     else
    1197             :     {
    1198         272 :         if (reclen-- <= 0)
    1199         240 :             fputc(' ', fout);
    1200             :     }
    1201        1082 :     if (reclen < 0)
    1202         942 :         reclen = 0;
    1203       18588 :     for (i = reclen; i < dwidth; i++)
    1204       17506 :         fputs(opt_border > 0 ? lformat->hrule : " ", fout);
    1205        1082 :     if (opt_border == 2)
    1206         312 :         fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
    1207        1082 :     fputc('\n', fout);
    1208        1082 : }
    1209             : 
    1210             : static void
    1211         366 : print_aligned_vertical(const printTableContent *cont,
    1212             :                        FILE *fout, bool is_pager)
    1213             : {
    1214         366 :     bool        opt_tuples_only = cont->opt->tuples_only;
    1215         366 :     unsigned short opt_border = cont->opt->border;
    1216         366 :     const printTextFormat *format = get_line_style(cont->opt);
    1217         366 :     const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
    1218         366 :     int         encoding = cont->opt->encoding;
    1219         366 :     unsigned long record = cont->opt->prior_records + 1;
    1220             :     const char *const *ptr;
    1221             :     unsigned int i,
    1222         366 :                 hwidth = 0,
    1223         366 :                 dwidth = 0,
    1224         366 :                 hheight = 1,
    1225         366 :                 dheight = 1,
    1226         366 :                 hformatsize = 0,
    1227         366 :                 dformatsize = 0;
    1228             :     struct lineptr *hlineptr,
    1229             :                *dlineptr;
    1230         366 :     bool        is_local_pager = false,
    1231         366 :                 hmultiline = false,
    1232         366 :                 dmultiline = false;
    1233         366 :     int         output_columns = 0; /* Width of interactive console */
    1234             : 
    1235         366 :     if (cancel_pressed)
    1236           0 :         return;
    1237             : 
    1238         366 :     if (opt_border > 2)
    1239           0 :         opt_border = 2;
    1240             : 
    1241         366 :     if (cont->cells[0] == NULL && cont->opt->start_table &&
    1242          12 :         cont->opt->stop_table)
    1243             :     {
    1244          12 :         printTableFooter *footers = footers_with_default(cont);
    1245             : 
    1246          12 :         if (!opt_tuples_only && !cancel_pressed && footers)
    1247             :         {
    1248             :             printTableFooter *f;
    1249             : 
    1250          24 :             for (f = footers; f; f = f->next)
    1251          12 :                 fprintf(fout, "%s\n", f->data);
    1252             :         }
    1253             : 
    1254          12 :         fputc('\n', fout);
    1255             : 
    1256          12 :         return;
    1257             :     }
    1258             : 
    1259             :     /*
    1260             :      * Deal with the pager here instead of in printTable(), because we could
    1261             :      * get here via print_aligned_text() in expanded auto mode, and so we have
    1262             :      * to recalculate the pager requirement based on vertical output.
    1263             :      */
    1264         354 :     if (!is_pager)
    1265             :     {
    1266         338 :         IsPagerNeeded(cont, 0, true, &fout, &is_pager);
    1267         338 :         is_local_pager = is_pager;
    1268             :     }
    1269             : 
    1270             :     /* Find the maximum dimensions for the headers */
    1271        2154 :     for (i = 0; i < cont->ncolumns; i++)
    1272             :     {
    1273             :         int         width,
    1274             :                     height,
    1275             :                     fs;
    1276             : 
    1277        1800 :         pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
    1278             :                    encoding, &width, &height, &fs);
    1279        1800 :         if (width > hwidth)
    1280         424 :             hwidth = width;
    1281        1800 :         if (height > hheight)
    1282             :         {
    1283          48 :             hheight = height;
    1284          48 :             hmultiline = true;
    1285             :         }
    1286        1800 :         if (fs > hformatsize)
    1287         424 :             hformatsize = fs;
    1288             :     }
    1289             : 
    1290             :     /* find longest data cell */
    1291        3570 :     for (i = 0, ptr = cont->cells; *ptr; ptr++, i++)
    1292             :     {
    1293             :         int         width,
    1294             :                     height,
    1295             :                     fs;
    1296             : 
    1297        3216 :         pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
    1298             :                    &width, &height, &fs);
    1299        3216 :         if (width > dwidth)
    1300         642 :             dwidth = width;
    1301        3216 :         if (height > dheight)
    1302             :         {
    1303          52 :             dheight = height;
    1304          52 :             dmultiline = true;
    1305             :         }
    1306        3216 :         if (fs > dformatsize)
    1307         642 :             dformatsize = fs;
    1308             :     }
    1309             : 
    1310             :     /*
    1311             :      * We now have all the information we need to setup the formatting
    1312             :      * structures
    1313             :      */
    1314         354 :     dlineptr = pg_malloc((sizeof(*dlineptr)) * (dheight + 1));
    1315         354 :     hlineptr = pg_malloc((sizeof(*hlineptr)) * (hheight + 1));
    1316             : 
    1317         354 :     dlineptr->ptr = pg_malloc(dformatsize);
    1318         354 :     hlineptr->ptr = pg_malloc(hformatsize);
    1319             : 
    1320         354 :     if (cont->opt->start_table)
    1321             :     {
    1322             :         /* print title */
    1323         346 :         if (!opt_tuples_only && cont->title)
    1324          12 :             fprintf(fout, "%s\n", cont->title);
    1325             :     }
    1326             : 
    1327             :     /*
    1328             :      * Choose target output width: \pset columns, or $COLUMNS, or ioctl
    1329             :      */
    1330         354 :     if (cont->opt->columns > 0)
    1331         136 :         output_columns = cont->opt->columns;
    1332         218 :     else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
    1333             :     {
    1334          16 :         if (cont->opt->env_columns > 0)
    1335           0 :             output_columns = cont->opt->env_columns;
    1336             : #ifdef TIOCGWINSZ
    1337             :         else
    1338             :         {
    1339             :             struct winsize screen_size;
    1340             : 
    1341          16 :             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
    1342           0 :                 output_columns = screen_size.ws_col;
    1343             :         }
    1344             : #endif
    1345             :     }
    1346             : 
    1347             :     /*
    1348             :      * Calculate available width for data in wrapped mode
    1349             :      */
    1350         354 :     if (cont->opt->format == PRINT_WRAPPED)
    1351             :     {
    1352             :         unsigned int swidth,
    1353          68 :                     rwidth = 0,
    1354             :                     newdwidth;
    1355             : 
    1356          68 :         if (opt_border == 0)
    1357             :         {
    1358             :             /*
    1359             :              * For border = 0, one space in the middle.  (If we discover we
    1360             :              * need to wrap, the spacer column will be replaced by a wrap
    1361             :              * marker, and we'll make room below for another wrap marker at
    1362             :              * the end of the line.  But for now, assume no wrap is needed.)
    1363             :              */
    1364          20 :             swidth = 1;
    1365             : 
    1366             :             /* We might need a column for header newline markers, too */
    1367          20 :             if (hmultiline)
    1368           8 :                 swidth++;
    1369             :         }
    1370          48 :         else if (opt_border == 1)
    1371             :         {
    1372             :             /*
    1373             :              * For border = 1, two spaces and a vrule in the middle.  (As
    1374             :              * above, we might need one more column for a wrap marker.)
    1375             :              */
    1376          28 :             swidth = 3;
    1377             : 
    1378             :             /* We might need a column for left header newline markers, too */
    1379          28 :             if (hmultiline && (format == &pg_asciiformat_old))
    1380           4 :                 swidth++;
    1381             :         }
    1382             :         else
    1383             :         {
    1384             :             /*
    1385             :              * For border = 2, two more for the vrules at the beginning and
    1386             :              * end of the lines, plus spacer columns adjacent to these.  (We
    1387             :              * won't need extra columns for wrap/newline markers, we'll just
    1388             :              * repurpose the spacers.)
    1389             :              */
    1390          20 :             swidth = 7;
    1391             :         }
    1392             : 
    1393             :         /* Reserve a column for data newline indicators, too, if needed */
    1394          68 :         if (dmultiline &&
    1395          16 :             opt_border < 2 && format != &pg_asciiformat_old)
    1396           8 :             swidth++;
    1397             : 
    1398             :         /* Determine width required for record header lines */
    1399          68 :         if (!opt_tuples_only)
    1400             :         {
    1401          64 :             if (cont->nrows > 0)
    1402          64 :                 rwidth = 1 + (int) log10(cont->nrows);
    1403          64 :             if (opt_border == 0)
    1404          20 :                 rwidth += 9;    /* "* RECORD " */
    1405          44 :             else if (opt_border == 1)
    1406          24 :                 rwidth += 12;   /* "-[ RECORD  ]" */
    1407             :             else
    1408          20 :                 rwidth += 15;   /* "+-[ RECORD  ]-+" */
    1409             :         }
    1410             : 
    1411             :         /* We might need to do the rest of the calculation twice */
    1412             :         for (;;)
    1413          16 :         {
    1414             :             unsigned int width;
    1415             : 
    1416             :             /* Total width required to not wrap data */
    1417          84 :             width = hwidth + swidth + dwidth;
    1418             :             /* ... and not the header lines, either */
    1419          84 :             if (width < rwidth)
    1420           0 :                 width = rwidth;
    1421             : 
    1422          84 :             if (output_columns > 0)
    1423             :             {
    1424             :                 unsigned int min_width;
    1425             : 
    1426             :                 /* Minimum acceptable width: room for just 3 columns of data */
    1427          84 :                 min_width = hwidth + swidth + 3;
    1428             :                 /* ... but not less than what the record header lines need */
    1429          84 :                 if (min_width < rwidth)
    1430          24 :                     min_width = rwidth;
    1431             : 
    1432          84 :                 if (output_columns >= width)
    1433             :                 {
    1434             :                     /* Plenty of room, use native data width */
    1435             :                     /* (but at least enough for the record header lines) */
    1436          16 :                     newdwidth = width - hwidth - swidth;
    1437             :                 }
    1438          68 :                 else if (output_columns < min_width)
    1439             :                 {
    1440             :                     /* Set data width to match min_width */
    1441          16 :                     newdwidth = min_width - hwidth - swidth;
    1442             :                 }
    1443             :                 else
    1444             :                 {
    1445             :                     /* Set data width to match output_columns */
    1446          52 :                     newdwidth = output_columns - hwidth - swidth;
    1447             :                 }
    1448             :             }
    1449             :             else
    1450             :             {
    1451             :                 /* Don't know the wrap limit, so use native data width */
    1452             :                 /* (but at least enough for the record header lines) */
    1453           0 :                 newdwidth = width - hwidth - swidth;
    1454             :             }
    1455             : 
    1456             :             /*
    1457             :              * If we will need to wrap data and didn't already allocate a data
    1458             :              * newline/wrap marker column, do so and recompute.
    1459             :              */
    1460          84 :             if (newdwidth < dwidth && !dmultiline &&
    1461          16 :                 opt_border < 2 && format != &pg_asciiformat_old)
    1462             :             {
    1463          16 :                 dmultiline = true;
    1464          16 :                 swidth++;
    1465             :             }
    1466             :             else
    1467             :                 break;
    1468             :         }
    1469             : 
    1470          68 :         dwidth = newdwidth;
    1471             :     }
    1472             : 
    1473             :     /* print records */
    1474        3570 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1475             :     {
    1476             :         printTextRule pos;
    1477             :         int         dline,
    1478             :                     hline,
    1479             :                     dcomplete,
    1480             :                     hcomplete,
    1481             :                     offset,
    1482             :                     chars_to_output;
    1483             : 
    1484        3216 :         if (cancel_pressed)
    1485           0 :             break;
    1486             : 
    1487        3216 :         if (i == 0)
    1488         346 :             pos = PRINT_RULE_TOP;
    1489             :         else
    1490        2870 :             pos = PRINT_RULE_MIDDLE;
    1491             : 
    1492             :         /* Print record header (e.g. "[ RECORD N ]") above each record */
    1493        3216 :         if (i % cont->ncolumns == 0)
    1494             :         {
    1495        1050 :             unsigned int lhwidth = hwidth;
    1496             : 
    1497        1050 :             if ((opt_border < 2) &&
    1498          64 :                 (hmultiline) &&
    1499             :                 (format == &pg_asciiformat_old))
    1500          32 :                 lhwidth++;      /* for newline indicators */
    1501             : 
    1502        1050 :             if (!opt_tuples_only)
    1503        1034 :                 print_aligned_vertical_line(format, opt_border, record++,
    1504             :                                             lhwidth, dwidth, pos, fout);
    1505          16 :             else if (i != 0 || !cont->opt->start_table || opt_border == 2)
    1506           8 :                 print_aligned_vertical_line(format, opt_border, 0, lhwidth,
    1507             :                                             dwidth, pos, fout);
    1508             :         }
    1509             : 
    1510             :         /* Format the header */
    1511        6432 :         pg_wcsformat((const unsigned char *) cont->headers[i % cont->ncolumns],
    1512        3216 :                      strlen(cont->headers[i % cont->ncolumns]),
    1513             :                      encoding, hlineptr, hheight);
    1514             :         /* Format the data */
    1515        3216 :         pg_wcsformat((const unsigned char *) *ptr, strlen(*ptr), encoding,
    1516             :                      dlineptr, dheight);
    1517             : 
    1518             :         /*
    1519             :          * Loop through header and data in parallel dealing with newlines and
    1520             :          * wrapped lines until they're both exhausted
    1521             :          */
    1522        3216 :         dline = hline = 0;
    1523        3216 :         dcomplete = hcomplete = 0;
    1524        3216 :         offset = 0;
    1525        3216 :         chars_to_output = dlineptr[dline].width;
    1526        8264 :         while (!dcomplete || !hcomplete)
    1527             :         {
    1528             :             /* Left border */
    1529        5048 :             if (opt_border == 2)
    1530        1212 :                 fprintf(fout, "%s", dformat->leftvrule);
    1531             : 
    1532             :             /* Header (never wrapped so just need to deal with newlines) */
    1533        5048 :             if (!hcomplete)
    1534             :             {
    1535        3504 :                 int         swidth = hwidth,
    1536        3504 :                             target_width = hwidth;
    1537             : 
    1538             :                 /*
    1539             :                  * Left spacer or new line indicator
    1540             :                  */
    1541        3504 :                 if ((opt_border == 2) ||
    1542         320 :                     (hmultiline && (format == &pg_asciiformat_old)))
    1543         800 :                     fputs(hline ? format->header_nl_left : " ", fout);
    1544             : 
    1545             :                 /*
    1546             :                  * Header text
    1547             :                  */
    1548        3504 :                 strlen_max_width(hlineptr[hline].ptr, &target_width,
    1549             :                                  encoding);
    1550        3504 :                 fprintf(fout, "%-s", hlineptr[hline].ptr);
    1551             : 
    1552             :                 /*
    1553             :                  * Spacer
    1554             :                  */
    1555        3504 :                 swidth -= target_width;
    1556        3504 :                 if (swidth > 0)
    1557        2182 :                     fprintf(fout, "%*s", swidth, " ");
    1558             : 
    1559             :                 /*
    1560             :                  * New line indicator or separator's space
    1561             :                  */
    1562        3504 :                 if (hlineptr[hline + 1].ptr)
    1563             :                 {
    1564             :                     /* More lines after this one due to a newline */
    1565         288 :                     if ((opt_border > 0) ||
    1566          96 :                         (hmultiline && (format != &pg_asciiformat_old)))
    1567         240 :                         fputs(format->header_nl_right, fout);
    1568         288 :                     hline++;
    1569             :                 }
    1570             :                 else
    1571             :                 {
    1572             :                     /* This was the last line of the header */
    1573        3216 :                     if ((opt_border > 0) ||
    1574          64 :                         (hmultiline && (format != &pg_asciiformat_old)))
    1575        2704 :                         fputs(" ", fout);
    1576        3216 :                     hcomplete = 1;
    1577             :                 }
    1578             :             }
    1579             :             else
    1580             :             {
    1581        1544 :                 unsigned int swidth = hwidth + opt_border;
    1582             : 
    1583        1544 :                 if ((opt_border < 2) &&
    1584         472 :                     (hmultiline) &&
    1585             :                     (format == &pg_asciiformat_old))
    1586         232 :                     swidth++;
    1587             : 
    1588        1544 :                 if ((opt_border == 0) &&
    1589         364 :                     (format != &pg_asciiformat_old) &&
    1590             :                     (hmultiline))
    1591         120 :                     swidth++;
    1592             : 
    1593        1544 :                 fprintf(fout, "%*s", swidth, " ");
    1594             :             }
    1595             : 
    1596             :             /* Separator */
    1597        5048 :             if (opt_border > 0)
    1598             :             {
    1599        3932 :                 if (offset)
    1600         744 :                     fputs(format->midvrule_wrap, fout);
    1601        3188 :                 else if (dline == 0)
    1602        2672 :                     fputs(dformat->midvrule, fout);
    1603             :                 else
    1604         516 :                     fputs(format->midvrule_nl, fout);
    1605             :             }
    1606             : 
    1607             :             /* Data */
    1608        5048 :             if (!dcomplete)
    1609             :             {
    1610        4928 :                 int         target_width = dwidth,
    1611             :                             bytes_to_output,
    1612        4928 :                             swidth = dwidth;
    1613             : 
    1614             :                 /*
    1615             :                  * Left spacer or wrap indicator
    1616             :                  */
    1617        4928 :                 fputs(offset == 0 ? " " : format->wrap_left, fout);
    1618             : 
    1619             :                 /*
    1620             :                  * Data text
    1621             :                  */
    1622        4928 :                 bytes_to_output = strlen_max_width(dlineptr[dline].ptr + offset,
    1623             :                                                    &target_width, encoding);
    1624        4928 :                 fwrite((char *) (dlineptr[dline].ptr + offset),
    1625             :                        1, bytes_to_output, fout);
    1626             : 
    1627        4928 :                 chars_to_output -= target_width;
    1628        4928 :                 offset += bytes_to_output;
    1629             : 
    1630             :                 /* Spacer */
    1631        4928 :                 swidth -= target_width;
    1632             : 
    1633        4928 :                 if (chars_to_output)
    1634             :                 {
    1635             :                     /* continuing a wrapped column */
    1636         940 :                     if ((opt_border > 1) ||
    1637         568 :                         (dmultiline && (format != &pg_asciiformat_old)))
    1638             :                     {
    1639         908 :                         if (swidth > 0)
    1640           0 :                             fprintf(fout, "%*s", swidth, " ");
    1641         908 :                         fputs(format->wrap_right, fout);
    1642             :                     }
    1643             :                 }
    1644        3988 :                 else if (dlineptr[dline + 1].ptr)
    1645             :                 {
    1646             :                     /* reached a newline in the column */
    1647         772 :                     if ((opt_border > 1) ||
    1648         516 :                         (dmultiline && (format != &pg_asciiformat_old)))
    1649             :                     {
    1650         516 :                         if (swidth > 0)
    1651         504 :                             fprintf(fout, "%*s", swidth, " ");
    1652         516 :                         fputs(format->nl_right, fout);
    1653             :                     }
    1654         772 :                     dline++;
    1655         772 :                     offset = 0;
    1656         772 :                     chars_to_output = dlineptr[dline].width;
    1657             :                 }
    1658             :                 else
    1659             :                 {
    1660             :                     /* reached the end of the cell */
    1661        3216 :                     if (opt_border > 1)
    1662             :                     {
    1663         544 :                         if (swidth > 0)
    1664         492 :                             fprintf(fout, "%*s", swidth, " ");
    1665         544 :                         fputs(" ", fout);
    1666             :                     }
    1667        3216 :                     dcomplete = 1;
    1668             :                 }
    1669             : 
    1670             :                 /* Right border */
    1671        4928 :                 if (opt_border == 2)
    1672        1172 :                     fputs(dformat->rightvrule, fout);
    1673             : 
    1674        4928 :                 fputs("\n", fout);
    1675             :             }
    1676             :             else
    1677             :             {
    1678             :                 /*
    1679             :                  * data exhausted (this can occur if header is longer than the
    1680             :                  * data due to newlines in the header)
    1681             :                  */
    1682         120 :                 if (opt_border < 2)
    1683          80 :                     fputs("\n", fout);
    1684             :                 else
    1685          40 :                     fprintf(fout, "%*s  %s\n", dwidth, "", dformat->rightvrule);
    1686             :             }
    1687             :         }
    1688             :     }
    1689             : 
    1690         354 :     if (cont->opt->stop_table)
    1691             :     {
    1692         346 :         if (opt_border == 2 && !cancel_pressed)
    1693          40 :             print_aligned_vertical_line(format, opt_border, 0, hwidth, dwidth,
    1694             :                                         PRINT_RULE_BOTTOM, fout);
    1695             : 
    1696             :         /* print footers */
    1697         346 :         if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
    1698             :         {
    1699             :             printTableFooter *f;
    1700             : 
    1701           8 :             if (opt_border < 2)
    1702           8 :                 fputc('\n', fout);
    1703          16 :             for (f = cont->footers; f; f = f->next)
    1704           8 :                 fprintf(fout, "%s\n", f->data);
    1705             :         }
    1706             : 
    1707         346 :         fputc('\n', fout);
    1708             :     }
    1709             : 
    1710         354 :     free(hlineptr->ptr);
    1711         354 :     free(dlineptr->ptr);
    1712         354 :     free(hlineptr);
    1713         354 :     free(dlineptr);
    1714             : 
    1715         354 :     if (is_local_pager)
    1716           0 :         ClosePager(fout);
    1717             : }
    1718             : 
    1719             : 
    1720             : /**********************/
    1721             : /* CSV format         */
    1722             : /**********************/
    1723             : 
    1724             : 
    1725             : static void
    1726          72 : csv_escaped_print(const char *str, FILE *fout)
    1727             : {
    1728             :     const char *p;
    1729             : 
    1730          72 :     fputc('"', fout);
    1731         616 :     for (p = str; *p; p++)
    1732             :     {
    1733         544 :         if (*p == '"')
    1734          28 :             fputc('"', fout);  /* double quotes are doubled */
    1735         544 :         fputc(*p, fout);
    1736             :     }
    1737          72 :     fputc('"', fout);
    1738          72 : }
    1739             : 
    1740             : static void
    1741         416 : csv_print_field(const char *str, FILE *fout, char sep)
    1742             : {
    1743             :     /*----------------
    1744             :      * Enclose and escape field contents when one of these conditions is met:
    1745             :      * - the field separator is found in the contents.
    1746             :      * - the field contains a CR or LF.
    1747             :      * - the field contains a double quote.
    1748             :      * - the field is exactly "\.".
    1749             :      * - the field separator is either "\" or ".".
    1750             :      * The last two cases prevent producing a line that the server's COPY
    1751             :      * command would interpret as an end-of-data marker.  We only really
    1752             :      * need to ensure that the complete line isn't exactly "\.", but for
    1753             :      * simplicity we apply stronger restrictions here.
    1754             :      *----------------
    1755             :      */
    1756         416 :     if (strchr(str, sep) != NULL ||
    1757         408 :         strcspn(str, "\r\n\"") != strlen(str) ||
    1758         364 :         strcmp(str, "\\.") == 0 ||
    1759         360 :         sep == '\\' || sep == '.')
    1760          72 :         csv_escaped_print(str, fout);
    1761             :     else
    1762         344 :         fputs(str, fout);
    1763         416 : }
    1764             : 
    1765             : static void
    1766          32 : print_csv_text(const printTableContent *cont, FILE *fout)
    1767             : {
    1768             :     const char *const *ptr;
    1769             :     int         i;
    1770             : 
    1771          32 :     if (cancel_pressed)
    1772           0 :         return;
    1773             : 
    1774             :     /*
    1775             :      * The title and footer are never printed in csv format. The header is
    1776             :      * printed if opt_tuples_only is false.
    1777             :      *
    1778             :      * Despite RFC 4180 saying that end of lines are CRLF, terminate lines
    1779             :      * with '\n', which prints out as the system-dependent EOL string in text
    1780             :      * mode (typically LF on Unix and CRLF on Windows).
    1781             :      */
    1782          32 :     if (cont->opt->start_table && !cont->opt->tuples_only)
    1783             :     {
    1784             :         /* print headers */
    1785         108 :         for (ptr = cont->headers; *ptr; ptr++)
    1786             :         {
    1787          80 :             if (ptr != cont->headers)
    1788          52 :                 fputc(cont->opt->csvFieldSep[0], fout);
    1789          80 :             csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
    1790             :         }
    1791          28 :         fputc('\n', fout);
    1792             :     }
    1793             : 
    1794             :     /* print cells */
    1795         168 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1796             :     {
    1797         136 :         csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
    1798         136 :         if ((i + 1) % cont->ncolumns)
    1799          96 :             fputc(cont->opt->csvFieldSep[0], fout);
    1800             :         else
    1801          40 :             fputc('\n', fout);
    1802             :     }
    1803             : }
    1804             : 
    1805             : static void
    1806          12 : print_csv_vertical(const printTableContent *cont, FILE *fout)
    1807             : {
    1808             :     const char *const *ptr;
    1809             :     int         i;
    1810             : 
    1811             :     /* print records */
    1812         112 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1813             :     {
    1814         100 :         if (cancel_pressed)
    1815           0 :             return;
    1816             : 
    1817             :         /* print name of column */
    1818         100 :         csv_print_field(cont->headers[i % cont->ncolumns], fout,
    1819         100 :                         cont->opt->csvFieldSep[0]);
    1820             : 
    1821             :         /* print field separator */
    1822         100 :         fputc(cont->opt->csvFieldSep[0], fout);
    1823             : 
    1824             :         /* print field value */
    1825         100 :         csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
    1826             : 
    1827         100 :         fputc('\n', fout);
    1828             :     }
    1829             : }
    1830             : 
    1831             : 
    1832             : /**********************/
    1833             : /* HTML               */
    1834             : /**********************/
    1835             : 
    1836             : 
    1837             : void
    1838         548 : html_escaped_print(const char *in, FILE *fout)
    1839             : {
    1840             :     const char *p;
    1841         548 :     bool        leading_space = true;
    1842             : 
    1843        4600 :     for (p = in; *p; p++)
    1844             :     {
    1845        4052 :         switch (*p)
    1846             :         {
    1847          36 :             case '&':
    1848          36 :                 fputs("&amp;", fout);
    1849          36 :                 break;
    1850          96 :             case '<':
    1851          96 :                 fputs("&lt;", fout);
    1852          96 :                 break;
    1853          96 :             case '>':
    1854          96 :                 fputs("&gt;", fout);
    1855          96 :                 break;
    1856          48 :             case '\n':
    1857          48 :                 fputs("<br />\n", fout);
    1858          48 :                 break;
    1859          64 :             case '"':
    1860          64 :                 fputs("&quot;", fout);
    1861          64 :                 break;
    1862         180 :             case ' ':
    1863             :                 /* protect leading space, for EXPLAIN output */
    1864         180 :                 if (leading_space)
    1865          96 :                     fputs("&nbsp;", fout);
    1866             :                 else
    1867          84 :                     fputs(" ", fout);
    1868         180 :                 break;
    1869        3532 :             default:
    1870        3532 :                 fputc(*p, fout);
    1871             :         }
    1872        4052 :         if (*p != ' ')
    1873        3872 :             leading_space = false;
    1874             :     }
    1875         548 : }
    1876             : 
    1877             : 
    1878             : static void
    1879          20 : print_html_text(const printTableContent *cont, FILE *fout)
    1880             : {
    1881          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    1882          20 :     unsigned short opt_border = cont->opt->border;
    1883          20 :     const char *opt_table_attr = cont->opt->tableAttr;
    1884             :     unsigned int i;
    1885             :     const char *const *ptr;
    1886             : 
    1887          20 :     if (cancel_pressed)
    1888           0 :         return;
    1889             : 
    1890          20 :     if (cont->opt->start_table)
    1891             :     {
    1892          20 :         fprintf(fout, "<table border=\"%d\"", opt_border);
    1893          20 :         if (opt_table_attr)
    1894           4 :             fprintf(fout, " %s", opt_table_attr);
    1895          20 :         fputs(">\n", fout);
    1896             : 
    1897             :         /* print title */
    1898          20 :         if (!opt_tuples_only && cont->title)
    1899             :         {
    1900           4 :             fputs("  <caption>", fout);
    1901           4 :             html_escaped_print(cont->title, fout);
    1902           4 :             fputs("</caption>\n", fout);
    1903             :         }
    1904             : 
    1905             :         /* print headers */
    1906          20 :         if (!opt_tuples_only)
    1907             :         {
    1908          16 :             fputs("  <tr>\n", fout);
    1909          92 :             for (ptr = cont->headers; *ptr; ptr++)
    1910             :             {
    1911          76 :                 fputs("    <th align=\"center\">", fout);
    1912          76 :                 html_escaped_print(*ptr, fout);
    1913          76 :                 fputs("</th>\n", fout);
    1914             :             }
    1915          16 :             fputs("  </tr>\n", fout);
    1916             :         }
    1917             :     }
    1918             : 
    1919             :     /* print cells */
    1920         184 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1921             :     {
    1922         164 :         if (i % cont->ncolumns == 0)
    1923             :         {
    1924          36 :             if (cancel_pressed)
    1925           0 :                 break;
    1926          36 :             fputs("  <tr valign=\"top\">\n", fout);
    1927             :         }
    1928             : 
    1929         164 :         fprintf(fout, "    <td align=\"%s\">", cont->aligns[(i) % cont->ncolumns] == 'r' ? "right" : "left");
    1930             :         /* is string only whitespace? */
    1931         164 :         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
    1932          24 :             fputs("&nbsp; ", fout);
    1933             :         else
    1934         140 :             html_escaped_print(*ptr, fout);
    1935             : 
    1936         164 :         fputs("</td>\n", fout);
    1937             : 
    1938         164 :         if ((i + 1) % cont->ncolumns == 0)
    1939          36 :             fputs("  </tr>\n", fout);
    1940             :     }
    1941             : 
    1942          20 :     if (cont->opt->stop_table)
    1943             :     {
    1944          20 :         printTableFooter *footers = footers_with_default(cont);
    1945             : 
    1946          20 :         fputs("</table>\n", fout);
    1947             : 
    1948             :         /* print footers */
    1949          20 :         if (!opt_tuples_only && footers != NULL && !cancel_pressed)
    1950             :         {
    1951             :             printTableFooter *f;
    1952             : 
    1953          16 :             fputs("<p>", fout);
    1954          32 :             for (f = footers; f; f = f->next)
    1955             :             {
    1956          16 :                 html_escaped_print(f->data, fout);
    1957          16 :                 fputs("<br />\n", fout);
    1958             :             }
    1959          16 :             fputs("</p>", fout);
    1960             :         }
    1961             : 
    1962          20 :         fputc('\n', fout);
    1963             :     }
    1964             : }
    1965             : 
    1966             : 
    1967             : static void
    1968          20 : print_html_vertical(const printTableContent *cont, FILE *fout)
    1969             : {
    1970          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    1971          20 :     unsigned short opt_border = cont->opt->border;
    1972          20 :     const char *opt_table_attr = cont->opt->tableAttr;
    1973          20 :     unsigned long record = cont->opt->prior_records + 1;
    1974             :     unsigned int i;
    1975             :     const char *const *ptr;
    1976             : 
    1977          20 :     if (cancel_pressed)
    1978           0 :         return;
    1979             : 
    1980          20 :     if (cont->opt->start_table)
    1981             :     {
    1982          20 :         fprintf(fout, "<table border=\"%d\"", opt_border);
    1983          20 :         if (opt_table_attr)
    1984           4 :             fprintf(fout, " %s", opt_table_attr);
    1985          20 :         fputs(">\n", fout);
    1986             : 
    1987             :         /* print title */
    1988          20 :         if (!opt_tuples_only && cont->title)
    1989             :         {
    1990           4 :             fputs("  <caption>", fout);
    1991           4 :             html_escaped_print(cont->title, fout);
    1992           4 :             fputs("</caption>\n", fout);
    1993             :         }
    1994             :     }
    1995             : 
    1996             :     /* print records */
    1997         184 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1998             :     {
    1999         164 :         if (i % cont->ncolumns == 0)
    2000             :         {
    2001          36 :             if (cancel_pressed)
    2002           0 :                 break;
    2003          36 :             if (!opt_tuples_only)
    2004          28 :                 fprintf(fout,
    2005             :                         "\n  <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n",
    2006             :                         record++);
    2007             :             else
    2008           8 :                 fputs("\n  <tr><td colspan=\"2\">&nbsp;</td></tr>\n", fout);
    2009             :         }
    2010         164 :         fputs("  <tr valign=\"top\">\n"
    2011             :               "    <th>", fout);
    2012         164 :         html_escaped_print(cont->headers[i % cont->ncolumns], fout);
    2013         164 :         fputs("</th>\n", fout);
    2014             : 
    2015         164 :         fprintf(fout, "    <td align=\"%s\">", cont->aligns[i % cont->ncolumns] == 'r' ? "right" : "left");
    2016             :         /* is string only whitespace? */
    2017         164 :         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
    2018          24 :             fputs("&nbsp; ", fout);
    2019             :         else
    2020         140 :             html_escaped_print(*ptr, fout);
    2021             : 
    2022         164 :         fputs("</td>\n  </tr>\n", fout);
    2023             :     }
    2024             : 
    2025          20 :     if (cont->opt->stop_table)
    2026             :     {
    2027          20 :         fputs("</table>\n", fout);
    2028             : 
    2029             :         /* print footers */
    2030          20 :         if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
    2031             :         {
    2032             :             printTableFooter *f;
    2033             : 
    2034           4 :             fputs("<p>", fout);
    2035           8 :             for (f = cont->footers; f; f = f->next)
    2036             :             {
    2037           4 :                 html_escaped_print(f->data, fout);
    2038           4 :                 fputs("<br />\n", fout);
    2039             :             }
    2040           4 :             fputs("</p>", fout);
    2041             :         }
    2042             : 
    2043          20 :         fputc('\n', fout);
    2044             :     }
    2045             : }
    2046             : 
    2047             : 
    2048             : /*************************/
    2049             : /* ASCIIDOC              */
    2050             : /*************************/
    2051             : 
    2052             : 
    2053             : static void
    2054         436 : asciidoc_escaped_print(const char *in, FILE *fout)
    2055             : {
    2056             :     const char *p;
    2057             : 
    2058        3060 :     for (p = in; *p; p++)
    2059             :     {
    2060        2624 :         switch (*p)
    2061             :         {
    2062          84 :             case '|':
    2063          84 :                 fputs("\\|", fout);
    2064          84 :                 break;
    2065        2540 :             default:
    2066        2540 :                 fputc(*p, fout);
    2067             :         }
    2068             :     }
    2069         436 : }
    2070             : 
    2071             : static void
    2072          20 : print_asciidoc_text(const printTableContent *cont, FILE *fout)
    2073             : {
    2074          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2075          20 :     unsigned short opt_border = cont->opt->border;
    2076             :     unsigned int i;
    2077             :     const char *const *ptr;
    2078             : 
    2079          20 :     if (cancel_pressed)
    2080           0 :         return;
    2081             : 
    2082          20 :     if (cont->opt->start_table)
    2083             :     {
    2084             :         /* print table in new paragraph - enforce preliminary new line */
    2085          20 :         fputs("\n", fout);
    2086             : 
    2087             :         /* print title */
    2088          20 :         if (!opt_tuples_only && cont->title)
    2089             :         {
    2090           4 :             fputs(".", fout);
    2091           4 :             fputs(cont->title, fout);
    2092           4 :             fputs("\n", fout);
    2093             :         }
    2094             : 
    2095             :         /* print table [] header definition */
    2096          20 :         fprintf(fout, "[%scols=\"", !opt_tuples_only ? "options=\"header\"," : "");
    2097         104 :         for (i = 0; i < cont->ncolumns; i++)
    2098             :         {
    2099          84 :             if (i != 0)
    2100          64 :                 fputs(",", fout);
    2101          84 :             fprintf(fout, "%s", cont->aligns[(i) % cont->ncolumns] == 'r' ? ">l" : "<l");
    2102             :         }
    2103          20 :         fputs("\"", fout);
    2104          20 :         switch (opt_border)
    2105             :         {
    2106           4 :             case 0:
    2107           4 :                 fputs(",frame=\"none\",grid=\"none\"", fout);
    2108           4 :                 break;
    2109          12 :             case 1:
    2110          12 :                 fputs(",frame=\"none\"", fout);
    2111          12 :                 break;
    2112           4 :             case 2:
    2113           4 :                 fputs(",frame=\"all\",grid=\"all\"", fout);
    2114           4 :                 break;
    2115             :         }
    2116          20 :         fputs("]\n", fout);
    2117          20 :         fputs("|====\n", fout);
    2118             : 
    2119             :         /* print headers */
    2120          20 :         if (!opt_tuples_only)
    2121             :         {
    2122          80 :             for (ptr = cont->headers; *ptr; ptr++)
    2123             :             {
    2124          64 :                 if (ptr != cont->headers)
    2125          48 :                     fputs(" ", fout);
    2126          64 :                 fputs("^l|", fout);
    2127          64 :                 asciidoc_escaped_print(*ptr, fout);
    2128             :             }
    2129          16 :             fputs("\n", fout);
    2130             :         }
    2131             :     }
    2132             : 
    2133             :     /* print cells */
    2134         160 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2135             :     {
    2136         140 :         if (i % cont->ncolumns == 0)
    2137             :         {
    2138          36 :             if (cancel_pressed)
    2139           0 :                 break;
    2140             :         }
    2141             : 
    2142         140 :         if (i % cont->ncolumns != 0)
    2143         104 :             fputs(" ", fout);
    2144         140 :         fputs("|", fout);
    2145             : 
    2146             :         /* protect against needless spaces */
    2147         140 :         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
    2148             :         {
    2149          24 :             if ((i + 1) % cont->ncolumns != 0)
    2150          24 :                 fputs(" ", fout);
    2151             :         }
    2152             :         else
    2153         116 :             asciidoc_escaped_print(*ptr, fout);
    2154             : 
    2155         140 :         if ((i + 1) % cont->ncolumns == 0)
    2156          36 :             fputs("\n", fout);
    2157             :     }
    2158             : 
    2159          20 :     fputs("|====\n", fout);
    2160             : 
    2161          20 :     if (cont->opt->stop_table)
    2162             :     {
    2163          20 :         printTableFooter *footers = footers_with_default(cont);
    2164             : 
    2165             :         /* print footers */
    2166          20 :         if (!opt_tuples_only && footers != NULL && !cancel_pressed)
    2167             :         {
    2168             :             printTableFooter *f;
    2169             : 
    2170          16 :             fputs("\n....\n", fout);
    2171          32 :             for (f = footers; f; f = f->next)
    2172             :             {
    2173          16 :                 fputs(f->data, fout);
    2174          16 :                 fputs("\n", fout);
    2175             :             }
    2176          16 :             fputs("....\n", fout);
    2177             :         }
    2178             :     }
    2179             : }
    2180             : 
    2181             : static void
    2182          20 : print_asciidoc_vertical(const printTableContent *cont, FILE *fout)
    2183             : {
    2184          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2185          20 :     unsigned short opt_border = cont->opt->border;
    2186          20 :     unsigned long record = cont->opt->prior_records + 1;
    2187             :     unsigned int i;
    2188             :     const char *const *ptr;
    2189             : 
    2190          20 :     if (cancel_pressed)
    2191           0 :         return;
    2192             : 
    2193          20 :     if (cont->opt->start_table)
    2194             :     {
    2195             :         /* print table in new paragraph - enforce preliminary new line */
    2196          20 :         fputs("\n", fout);
    2197             : 
    2198             :         /* print title */
    2199          20 :         if (!opt_tuples_only && cont->title)
    2200             :         {
    2201           4 :             fputs(".", fout);
    2202           4 :             fputs(cont->title, fout);
    2203           4 :             fputs("\n", fout);
    2204             :         }
    2205             : 
    2206             :         /* print table [] header definition */
    2207          20 :         fputs("[cols=\"h,l\"", fout);
    2208          20 :         switch (opt_border)
    2209             :         {
    2210           4 :             case 0:
    2211           4 :                 fputs(",frame=\"none\",grid=\"none\"", fout);
    2212           4 :                 break;
    2213          12 :             case 1:
    2214          12 :                 fputs(",frame=\"none\"", fout);
    2215          12 :                 break;
    2216           4 :             case 2:
    2217           4 :                 fputs(",frame=\"all\",grid=\"all\"", fout);
    2218           4 :                 break;
    2219             :         }
    2220          20 :         fputs("]\n", fout);
    2221          20 :         fputs("|====\n", fout);
    2222             :     }
    2223             : 
    2224             :     /* print records */
    2225         160 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2226             :     {
    2227         140 :         if (i % cont->ncolumns == 0)
    2228             :         {
    2229          36 :             if (cancel_pressed)
    2230           0 :                 break;
    2231          36 :             if (!opt_tuples_only)
    2232          28 :                 fprintf(fout,
    2233             :                         "2+^|Record %lu\n",
    2234             :                         record++);
    2235             :             else
    2236           8 :                 fputs("2+|\n", fout);
    2237             :         }
    2238             : 
    2239         140 :         fputs("<l|", fout);
    2240         140 :         asciidoc_escaped_print(cont->headers[i % cont->ncolumns], fout);
    2241             : 
    2242         140 :         fprintf(fout, " %s|", cont->aligns[i % cont->ncolumns] == 'r' ? ">l" : "<l");
    2243             :         /* is string only whitespace? */
    2244         140 :         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
    2245          24 :             fputs(" ", fout);
    2246             :         else
    2247         116 :             asciidoc_escaped_print(*ptr, fout);
    2248         140 :         fputs("\n", fout);
    2249             :     }
    2250             : 
    2251          20 :     fputs("|====\n", fout);
    2252             : 
    2253          20 :     if (cont->opt->stop_table)
    2254             :     {
    2255             :         /* print footers */
    2256          20 :         if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
    2257             :         {
    2258             :             printTableFooter *f;
    2259             : 
    2260           4 :             fputs("\n....\n", fout);
    2261           8 :             for (f = cont->footers; f; f = f->next)
    2262             :             {
    2263           4 :                 fputs(f->data, fout);
    2264           4 :                 fputs("\n", fout);
    2265             :             }
    2266           4 :             fputs("....\n", fout);
    2267             :         }
    2268             :     }
    2269             : }
    2270             : 
    2271             : 
    2272             : /*************************/
    2273             : /* LaTeX                 */
    2274             : /*************************/
    2275             : 
    2276             : 
    2277             : static void
    2278        1636 : latex_escaped_print(const char *in, FILE *fout)
    2279             : {
    2280             :     const char *p;
    2281             : 
    2282       14376 :     for (p = in; *p; p++)
    2283       12740 :         switch (*p)
    2284             :         {
    2285             :                 /*
    2286             :                  * We convert ASCII characters per the recommendations in
    2287             :                  * Scott Pakin's "The Comprehensive LATEX Symbol List",
    2288             :                  * available from CTAN.  For non-ASCII, you're on your own.
    2289             :                  */
    2290         144 :             case '#':
    2291         144 :                 fputs("\\#", fout);
    2292         144 :                 break;
    2293         128 :             case '$':
    2294         128 :                 fputs("\\$", fout);
    2295         128 :                 break;
    2296         144 :             case '%':
    2297         144 :                 fputs("\\%", fout);
    2298         144 :                 break;
    2299         144 :             case '&':
    2300         144 :                 fputs("\\&", fout);
    2301         144 :                 break;
    2302         144 :             case '<':
    2303         144 :                 fputs("\\textless{}", fout);
    2304         144 :                 break;
    2305         144 :             case '>':
    2306         144 :                 fputs("\\textgreater{}", fout);
    2307         144 :                 break;
    2308         144 :             case '\\':
    2309         144 :                 fputs("\\textbackslash{}", fout);
    2310         144 :                 break;
    2311         144 :             case '^':
    2312         144 :                 fputs("\\^{}", fout);
    2313         144 :                 break;
    2314         312 :             case '_':
    2315         312 :                 fputs("\\_", fout);
    2316         312 :                 break;
    2317         144 :             case '{':
    2318         144 :                 fputs("\\{", fout);
    2319         144 :                 break;
    2320         144 :             case '|':
    2321         144 :                 fputs("\\textbar{}", fout);
    2322         144 :                 break;
    2323         144 :             case '}':
    2324         144 :                 fputs("\\}", fout);
    2325         144 :                 break;
    2326         144 :             case '~':
    2327         144 :                 fputs("\\~{}", fout);
    2328         144 :                 break;
    2329         144 :             case '\n':
    2330             :                 /* This is not right, but doing it right seems too hard */
    2331         144 :                 fputs("\\\\", fout);
    2332         144 :                 break;
    2333       10572 :             default:
    2334       10572 :                 fputc(*p, fout);
    2335             :         }
    2336        1636 : }
    2337             : 
    2338             : 
    2339             : static void
    2340          24 : print_latex_text(const printTableContent *cont, FILE *fout)
    2341             : {
    2342          24 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2343          24 :     unsigned short opt_border = cont->opt->border;
    2344             :     unsigned int i;
    2345             :     const char *const *ptr;
    2346             : 
    2347          24 :     if (cancel_pressed)
    2348           0 :         return;
    2349             : 
    2350          24 :     if (opt_border > 3)
    2351           0 :         opt_border = 3;
    2352             : 
    2353          24 :     if (cont->opt->start_table)
    2354             :     {
    2355             :         /* print title */
    2356          24 :         if (!opt_tuples_only && cont->title)
    2357             :         {
    2358           4 :             fputs("\\begin{center}\n", fout);
    2359           4 :             latex_escaped_print(cont->title, fout);
    2360           4 :             fputs("\n\\end{center}\n\n", fout);
    2361             :         }
    2362             : 
    2363             :         /* begin environment and set alignments and borders */
    2364          24 :         fputs("\\begin{tabular}{", fout);
    2365             : 
    2366          24 :         if (opt_border >= 2)
    2367           8 :             fputs("| ", fout);
    2368         136 :         for (i = 0; i < cont->ncolumns; i++)
    2369             :         {
    2370         112 :             fputc(*(cont->aligns + i), fout);
    2371         112 :             if (opt_border != 0 && i < cont->ncolumns - 1)
    2372          76 :                 fputs(" | ", fout);
    2373             :         }
    2374          24 :         if (opt_border >= 2)
    2375           8 :             fputs(" |", fout);
    2376             : 
    2377          24 :         fputs("}\n", fout);
    2378             : 
    2379          24 :         if (!opt_tuples_only && opt_border >= 2)
    2380           8 :             fputs("\\hline\n", fout);
    2381             : 
    2382             :         /* print headers */
    2383          24 :         if (!opt_tuples_only)
    2384             :         {
    2385         112 :             for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
    2386             :             {
    2387          92 :                 if (i != 0)
    2388          72 :                     fputs(" & ", fout);
    2389          92 :                 fputs("\\textit{", fout);
    2390          92 :                 latex_escaped_print(*ptr, fout);
    2391          92 :                 fputc('}', fout);
    2392             :             }
    2393          20 :             fputs(" \\\\\n", fout);
    2394          20 :             fputs("\\hline\n", fout);
    2395             :         }
    2396             :     }
    2397             : 
    2398             :     /* print cells */
    2399         220 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2400             :     {
    2401         196 :         latex_escaped_print(*ptr, fout);
    2402             : 
    2403         196 :         if ((i + 1) % cont->ncolumns == 0)
    2404             :         {
    2405          44 :             fputs(" \\\\\n", fout);
    2406          44 :             if (opt_border == 3)
    2407           8 :                 fputs("\\hline\n", fout);
    2408          44 :             if (cancel_pressed)
    2409           0 :                 break;
    2410             :         }
    2411             :         else
    2412         152 :             fputs(" & ", fout);
    2413             :     }
    2414             : 
    2415          24 :     if (cont->opt->stop_table)
    2416             :     {
    2417          24 :         printTableFooter *footers = footers_with_default(cont);
    2418             : 
    2419          24 :         if (opt_border == 2)
    2420           4 :             fputs("\\hline\n", fout);
    2421             : 
    2422          24 :         fputs("\\end{tabular}\n\n\\noindent ", fout);
    2423             : 
    2424             :         /* print footers */
    2425          24 :         if (footers && !opt_tuples_only && !cancel_pressed)
    2426             :         {
    2427             :             printTableFooter *f;
    2428             : 
    2429          40 :             for (f = footers; f; f = f->next)
    2430             :             {
    2431          20 :                 latex_escaped_print(f->data, fout);
    2432          20 :                 fputs(" \\\\\n", fout);
    2433             :             }
    2434             :         }
    2435             : 
    2436          24 :         fputc('\n', fout);
    2437             :     }
    2438             : }
    2439             : 
    2440             : 
    2441             : /*************************/
    2442             : /* LaTeX longtable       */
    2443             : /*************************/
    2444             : 
    2445             : 
    2446             : static void
    2447          28 : print_latex_longtable_text(const printTableContent *cont, FILE *fout)
    2448             : {
    2449          28 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2450          28 :     unsigned short opt_border = cont->opt->border;
    2451             :     unsigned int i;
    2452          28 :     const char *opt_table_attr = cont->opt->tableAttr;
    2453          28 :     const char *next_opt_table_attr_char = opt_table_attr;
    2454          28 :     const char *last_opt_table_attr_char = NULL;
    2455             :     const char *const *ptr;
    2456             : 
    2457          28 :     if (cancel_pressed)
    2458           0 :         return;
    2459             : 
    2460          28 :     if (opt_border > 3)
    2461           0 :         opt_border = 3;
    2462             : 
    2463          28 :     if (cont->opt->start_table)
    2464             :     {
    2465             :         /* begin environment and set alignments and borders */
    2466          28 :         fputs("\\begin{longtable}{", fout);
    2467             : 
    2468          28 :         if (opt_border >= 2)
    2469          12 :             fputs("| ", fout);
    2470             : 
    2471         156 :         for (i = 0; i < cont->ncolumns; i++)
    2472             :         {
    2473             :             /* longtable supports either a width (p) or an alignment (l/r) */
    2474             :             /* Are we left-justified and was a proportional width specified? */
    2475         128 :             if (*(cont->aligns + i) == 'l' && opt_table_attr)
    2476             :             {
    2477             : #define LONGTABLE_WHITESPACE    " \t\n"
    2478             : 
    2479             :                 /* advance over whitespace */
    2480          12 :                 next_opt_table_attr_char += strspn(next_opt_table_attr_char,
    2481             :                                                    LONGTABLE_WHITESPACE);
    2482             :                 /* We have a value? */
    2483          24 :                 if (next_opt_table_attr_char[0] != '\0')
    2484             :                 {
    2485           4 :                     fputs("p{", fout);
    2486           4 :                     fwrite(next_opt_table_attr_char, strcspn(next_opt_table_attr_char,
    2487             :                                                              LONGTABLE_WHITESPACE), 1, fout);
    2488           4 :                     last_opt_table_attr_char = next_opt_table_attr_char;
    2489           4 :                     next_opt_table_attr_char += strcspn(next_opt_table_attr_char,
    2490             :                                                         LONGTABLE_WHITESPACE);
    2491           4 :                     fputs("\\textwidth}", fout);
    2492             :                 }
    2493             :                 /* use previous value */
    2494           8 :                 else if (last_opt_table_attr_char != NULL)
    2495             :                 {
    2496           8 :                     fputs("p{", fout);
    2497           8 :                     fwrite(last_opt_table_attr_char, strcspn(last_opt_table_attr_char,
    2498             :                                                              LONGTABLE_WHITESPACE), 1, fout);
    2499           8 :                     fputs("\\textwidth}", fout);
    2500             :                 }
    2501             :                 else
    2502           0 :                     fputc('l', fout);
    2503             :             }
    2504             :             else
    2505         116 :                 fputc(*(cont->aligns + i), fout);
    2506             : 
    2507         128 :             if (opt_border != 0 && i < cont->ncolumns - 1)
    2508          88 :                 fputs(" | ", fout);
    2509             :         }
    2510             : 
    2511          28 :         if (opt_border >= 2)
    2512          12 :             fputs(" |", fout);
    2513             : 
    2514          28 :         fputs("}\n", fout);
    2515             : 
    2516             :         /* print headers */
    2517          28 :         if (!opt_tuples_only)
    2518             :         {
    2519             :             /* firsthead */
    2520          24 :             if (opt_border >= 2)
    2521          12 :                 fputs("\\toprule\n", fout);
    2522         132 :             for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
    2523             :             {
    2524         108 :                 if (i != 0)
    2525          84 :                     fputs(" & ", fout);
    2526         108 :                 fputs("\\small\\textbf{\\textit{", fout);
    2527         108 :                 latex_escaped_print(*ptr, fout);
    2528         108 :                 fputs("}}", fout);
    2529             :             }
    2530          24 :             fputs(" \\\\\n", fout);
    2531          24 :             fputs("\\midrule\n\\endfirsthead\n", fout);
    2532             : 
    2533             :             /* secondary heads */
    2534          24 :             if (opt_border >= 2)
    2535          12 :                 fputs("\\toprule\n", fout);
    2536         132 :             for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
    2537             :             {
    2538         108 :                 if (i != 0)
    2539          84 :                     fputs(" & ", fout);
    2540         108 :                 fputs("\\small\\textbf{\\textit{", fout);
    2541         108 :                 latex_escaped_print(*ptr, fout);
    2542         108 :                 fputs("}}", fout);
    2543             :             }
    2544          24 :             fputs(" \\\\\n", fout);
    2545             :             /* If the line under the row already appeared, don't do another */
    2546          24 :             if (opt_border != 3)
    2547          16 :                 fputs("\\midrule\n", fout);
    2548          24 :             fputs("\\endhead\n", fout);
    2549             : 
    2550             :             /* table name, caption? */
    2551          24 :             if (!opt_tuples_only && cont->title)
    2552             :             {
    2553             :                 /* Don't output if we are printing a line under each row */
    2554           4 :                 if (opt_border == 2)
    2555           0 :                     fputs("\\bottomrule\n", fout);
    2556           4 :                 fputs("\\caption[", fout);
    2557           4 :                 latex_escaped_print(cont->title, fout);
    2558           4 :                 fputs(" (Continued)]{", fout);
    2559           4 :                 latex_escaped_print(cont->title, fout);
    2560           4 :                 fputs("}\n\\endfoot\n", fout);
    2561           4 :                 if (opt_border == 2)
    2562           0 :                     fputs("\\bottomrule\n", fout);
    2563           4 :                 fputs("\\caption[", fout);
    2564           4 :                 latex_escaped_print(cont->title, fout);
    2565           4 :                 fputs("]{", fout);
    2566           4 :                 latex_escaped_print(cont->title, fout);
    2567           4 :                 fputs("}\n\\endlastfoot\n", fout);
    2568             :             }
    2569             :             /* output bottom table line? */
    2570          20 :             else if (opt_border >= 2)
    2571             :             {
    2572          12 :                 fputs("\\bottomrule\n\\endfoot\n", fout);
    2573          12 :                 fputs("\\bottomrule\n\\endlastfoot\n", fout);
    2574             :             }
    2575             :         }
    2576             :     }
    2577             : 
    2578             :     /* print cells */
    2579         256 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2580             :     {
    2581             :         /* Add a line under each row? */
    2582         228 :         if (i != 0 && i % cont->ncolumns != 0)
    2583         176 :             fputs("\n&\n", fout);
    2584         228 :         fputs("\\raggedright{", fout);
    2585         228 :         latex_escaped_print(*ptr, fout);
    2586         228 :         fputc('}', fout);
    2587         228 :         if ((i + 1) % cont->ncolumns == 0)
    2588             :         {
    2589          52 :             fputs(" \\tabularnewline\n", fout);
    2590          52 :             if (opt_border == 3)
    2591          16 :                 fputs(" \\hline\n", fout);
    2592             :         }
    2593         228 :         if (cancel_pressed)
    2594           0 :             break;
    2595             :     }
    2596             : 
    2597          28 :     if (cont->opt->stop_table)
    2598          28 :         fputs("\\end{longtable}\n", fout);
    2599             : }
    2600             : 
    2601             : 
    2602             : static void
    2603          52 : print_latex_vertical(const printTableContent *cont, FILE *fout)
    2604             : {
    2605          52 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2606          52 :     unsigned short opt_border = cont->opt->border;
    2607          52 :     unsigned long record = cont->opt->prior_records + 1;
    2608             :     unsigned int i;
    2609             :     const char *const *ptr;
    2610             : 
    2611          52 :     if (cancel_pressed)
    2612           0 :         return;
    2613             : 
    2614          52 :     if (opt_border > 2)
    2615          12 :         opt_border = 2;
    2616             : 
    2617          52 :     if (cont->opt->start_table)
    2618             :     {
    2619             :         /* print title */
    2620          52 :         if (!opt_tuples_only && cont->title)
    2621             :         {
    2622           8 :             fputs("\\begin{center}\n", fout);
    2623           8 :             latex_escaped_print(cont->title, fout);
    2624           8 :             fputs("\n\\end{center}\n\n", fout);
    2625             :         }
    2626             : 
    2627             :         /* begin environment and set alignments and borders */
    2628          52 :         fputs("\\begin{tabular}{", fout);
    2629          52 :         if (opt_border == 0)
    2630           8 :             fputs("cl", fout);
    2631          44 :         else if (opt_border == 1)
    2632          24 :             fputs("c|l", fout);
    2633          20 :         else if (opt_border == 2)
    2634          20 :             fputs("|c|l|", fout);
    2635          52 :         fputs("}\n", fout);
    2636             :     }
    2637             : 
    2638             :     /* print records */
    2639         476 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2640             :     {
    2641             :         /* new record */
    2642         424 :         if (i % cont->ncolumns == 0)
    2643             :         {
    2644          96 :             if (cancel_pressed)
    2645           0 :                 break;
    2646          96 :             if (!opt_tuples_only)
    2647             :             {
    2648          80 :                 if (opt_border == 2)
    2649             :                 {
    2650          40 :                     fputs("\\hline\n", fout);
    2651          40 :                     fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++);
    2652             :                 }
    2653             :                 else
    2654          40 :                     fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %lu}} \\\\\n", record++);
    2655             :             }
    2656          96 :             if (opt_border >= 1)
    2657          80 :                 fputs("\\hline\n", fout);
    2658             :         }
    2659             : 
    2660         424 :         latex_escaped_print(cont->headers[i % cont->ncolumns], fout);
    2661         424 :         fputs(" & ", fout);
    2662         424 :         latex_escaped_print(*ptr, fout);
    2663         424 :         fputs(" \\\\\n", fout);
    2664             :     }
    2665             : 
    2666          52 :     if (cont->opt->stop_table)
    2667             :     {
    2668          52 :         if (opt_border == 2)
    2669          20 :             fputs("\\hline\n", fout);
    2670             : 
    2671          52 :         fputs("\\end{tabular}\n\n\\noindent ", fout);
    2672             : 
    2673             :         /* print footers */
    2674          52 :         if (cont->footers && !opt_tuples_only && !cancel_pressed)
    2675             :         {
    2676             :             printTableFooter *f;
    2677             : 
    2678          16 :             for (f = cont->footers; f; f = f->next)
    2679             :             {
    2680           8 :                 latex_escaped_print(f->data, fout);
    2681           8 :                 fputs(" \\\\\n", fout);
    2682             :             }
    2683             :         }
    2684             : 
    2685          52 :         fputc('\n', fout);
    2686             :     }
    2687             : }
    2688             : 
    2689             : 
    2690             : /*************************/
    2691             : /* Troff -ms             */
    2692             : /*************************/
    2693             : 
    2694             : 
    2695             : static void
    2696         596 : troff_ms_escaped_print(const char *in, FILE *fout)
    2697             : {
    2698             :     const char *p;
    2699             : 
    2700        4792 :     for (p = in; *p; p++)
    2701        4196 :         switch (*p)
    2702             :         {
    2703          84 :             case '\\':
    2704          84 :                 fputs("\\(rs", fout);
    2705          84 :                 break;
    2706        4112 :             default:
    2707        4112 :                 fputc(*p, fout);
    2708             :         }
    2709         596 : }
    2710             : 
    2711             : 
    2712             : static void
    2713          20 : print_troff_ms_text(const printTableContent *cont, FILE *fout)
    2714             : {
    2715          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2716          20 :     unsigned short opt_border = cont->opt->border;
    2717             :     unsigned int i;
    2718             :     const char *const *ptr;
    2719             : 
    2720          20 :     if (cancel_pressed)
    2721           0 :         return;
    2722             : 
    2723          20 :     if (opt_border > 2)
    2724           0 :         opt_border = 2;
    2725             : 
    2726          20 :     if (cont->opt->start_table)
    2727             :     {
    2728             :         /* print title */
    2729          20 :         if (!opt_tuples_only && cont->title)
    2730             :         {
    2731           4 :             fputs(".LP\n.DS C\n", fout);
    2732           4 :             troff_ms_escaped_print(cont->title, fout);
    2733           4 :             fputs("\n.DE\n", fout);
    2734             :         }
    2735             : 
    2736             :         /* begin environment and set alignments and borders */
    2737          20 :         fputs(".LP\n.TS\n", fout);
    2738          20 :         if (opt_border == 2)
    2739           4 :             fputs("center box;\n", fout);
    2740             :         else
    2741          16 :             fputs("center;\n", fout);
    2742             : 
    2743         116 :         for (i = 0; i < cont->ncolumns; i++)
    2744             :         {
    2745          96 :             fputc(*(cont->aligns + i), fout);
    2746          96 :             if (opt_border > 0 && i < cont->ncolumns - 1)
    2747          64 :                 fputs(" | ", fout);
    2748             :         }
    2749          20 :         fputs(".\n", fout);
    2750             : 
    2751             :         /* print headers */
    2752          20 :         if (!opt_tuples_only)
    2753             :         {
    2754          92 :             for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
    2755             :             {
    2756          76 :                 if (i != 0)
    2757          60 :                     fputc('\t', fout);
    2758          76 :                 fputs("\\fI", fout);
    2759          76 :                 troff_ms_escaped_print(*ptr, fout);
    2760          76 :                 fputs("\\fP", fout);
    2761             :             }
    2762          16 :             fputs("\n_\n", fout);
    2763             :         }
    2764             :     }
    2765             : 
    2766             :     /* print cells */
    2767         184 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2768             :     {
    2769         164 :         troff_ms_escaped_print(*ptr, fout);
    2770             : 
    2771         164 :         if ((i + 1) % cont->ncolumns == 0)
    2772             :         {
    2773          36 :             fputc('\n', fout);
    2774          36 :             if (cancel_pressed)
    2775           0 :                 break;
    2776             :         }
    2777             :         else
    2778         128 :             fputc('\t', fout);
    2779             :     }
    2780             : 
    2781          20 :     if (cont->opt->stop_table)
    2782             :     {
    2783          20 :         printTableFooter *footers = footers_with_default(cont);
    2784             : 
    2785          20 :         fputs(".TE\n.DS L\n", fout);
    2786             : 
    2787             :         /* print footers */
    2788          20 :         if (footers && !opt_tuples_only && !cancel_pressed)
    2789             :         {
    2790             :             printTableFooter *f;
    2791             : 
    2792          32 :             for (f = footers; f; f = f->next)
    2793             :             {
    2794          16 :                 troff_ms_escaped_print(f->data, fout);
    2795          16 :                 fputc('\n', fout);
    2796             :             }
    2797             :         }
    2798             : 
    2799          20 :         fputs(".DE\n", fout);
    2800             :     }
    2801             : }
    2802             : 
    2803             : 
    2804             : static void
    2805          20 : print_troff_ms_vertical(const printTableContent *cont, FILE *fout)
    2806             : {
    2807          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2808          20 :     unsigned short opt_border = cont->opt->border;
    2809          20 :     unsigned long record = cont->opt->prior_records + 1;
    2810             :     unsigned int i;
    2811             :     const char *const *ptr;
    2812          20 :     unsigned short current_format = 0;  /* 0=none, 1=header, 2=body */
    2813             : 
    2814          20 :     if (cancel_pressed)
    2815           0 :         return;
    2816             : 
    2817          20 :     if (opt_border > 2)
    2818           0 :         opt_border = 2;
    2819             : 
    2820          20 :     if (cont->opt->start_table)
    2821             :     {
    2822             :         /* print title */
    2823          20 :         if (!opt_tuples_only && cont->title)
    2824             :         {
    2825           4 :             fputs(".LP\n.DS C\n", fout);
    2826           4 :             troff_ms_escaped_print(cont->title, fout);
    2827           4 :             fputs("\n.DE\n", fout);
    2828             :         }
    2829             : 
    2830             :         /* begin environment and set alignments and borders */
    2831          20 :         fputs(".LP\n.TS\n", fout);
    2832          20 :         if (opt_border == 2)
    2833           4 :             fputs("center box;\n", fout);
    2834             :         else
    2835          16 :             fputs("center;\n", fout);
    2836             : 
    2837             :         /* basic format */
    2838          20 :         if (opt_tuples_only)
    2839           4 :             fputs("c l;\n", fout);
    2840             :     }
    2841             :     else
    2842           0 :         current_format = 2;     /* assume tuples printed already */
    2843             : 
    2844             :     /* print records */
    2845         184 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2846             :     {
    2847             :         /* new record */
    2848         164 :         if (i % cont->ncolumns == 0)
    2849             :         {
    2850          36 :             if (cancel_pressed)
    2851           0 :                 break;
    2852          36 :             if (!opt_tuples_only)
    2853             :             {
    2854          28 :                 if (current_format != 1)
    2855             :                 {
    2856          28 :                     if (opt_border == 2 && record > 1)
    2857           4 :                         fputs("_\n", fout);
    2858          28 :                     if (current_format != 0)
    2859          12 :                         fputs(".T&\n", fout);
    2860          28 :                     fputs("c s.\n", fout);
    2861          28 :                     current_format = 1;
    2862             :                 }
    2863          28 :                 fprintf(fout, "\\fIRecord %lu\\fP\n", record++);
    2864             :             }
    2865          36 :             if (opt_border >= 1)
    2866          28 :                 fputs("_\n", fout);
    2867             :         }
    2868             : 
    2869         164 :         if (!opt_tuples_only)
    2870             :         {
    2871         124 :             if (current_format != 2)
    2872             :             {
    2873          28 :                 if (current_format != 0)
    2874          28 :                     fputs(".T&\n", fout);
    2875          28 :                 if (opt_border != 1)
    2876          16 :                     fputs("c l.\n", fout);
    2877             :                 else
    2878          12 :                     fputs("c | l.\n", fout);
    2879          28 :                 current_format = 2;
    2880             :             }
    2881             :         }
    2882             : 
    2883         164 :         troff_ms_escaped_print(cont->headers[i % cont->ncolumns], fout);
    2884         164 :         fputc('\t', fout);
    2885         164 :         troff_ms_escaped_print(*ptr, fout);
    2886             : 
    2887         164 :         fputc('\n', fout);
    2888             :     }
    2889             : 
    2890          20 :     if (cont->opt->stop_table)
    2891             :     {
    2892          20 :         fputs(".TE\n.DS L\n", fout);
    2893             : 
    2894             :         /* print footers */
    2895          20 :         if (cont->footers && !opt_tuples_only && !cancel_pressed)
    2896             :         {
    2897             :             printTableFooter *f;
    2898             : 
    2899           8 :             for (f = cont->footers; f; f = f->next)
    2900             :             {
    2901           4 :                 troff_ms_escaped_print(f->data, fout);
    2902           4 :                 fputc('\n', fout);
    2903             :             }
    2904             :         }
    2905             : 
    2906          20 :         fputs(".DE\n", fout);
    2907             :     }
    2908             : }
    2909             : 
    2910             : 
    2911             : /********************************/
    2912             : /* Public functions             */
    2913             : /********************************/
    2914             : 
    2915             : 
    2916             : /*
    2917             :  * disable_sigpipe_trap
    2918             :  *
    2919             :  * Turn off SIGPIPE interrupt --- call this before writing to a temporary
    2920             :  * query output file that is a pipe.
    2921             :  *
    2922             :  * No-op on Windows, where there's no SIGPIPE interrupts.
    2923             :  */
    2924             : void
    2925           0 : disable_sigpipe_trap(void)
    2926             : {
    2927             : #ifndef WIN32
    2928           0 :     pqsignal(SIGPIPE, SIG_IGN);
    2929             : #endif
    2930           0 : }
    2931             : 
    2932             : /*
    2933             :  * restore_sigpipe_trap
    2934             :  *
    2935             :  * Restore normal SIGPIPE interrupt --- call this when done writing to a
    2936             :  * temporary query output file that was (or might have been) a pipe.
    2937             :  *
    2938             :  * Note: within psql, we enable SIGPIPE interrupts unless the permanent query
    2939             :  * output file is a pipe, in which case they should be kept off.  This
    2940             :  * approach works only because psql is not currently complicated enough to
    2941             :  * have nested usages of short-lived output files.  Otherwise we'd probably
    2942             :  * need a genuine save-and-restore-state approach; but for now, that would be
    2943             :  * useless complication.  In non-psql programs, this always enables SIGPIPE.
    2944             :  *
    2945             :  * No-op on Windows, where there's no SIGPIPE interrupts.
    2946             :  */
    2947             : void
    2948        6928 : restore_sigpipe_trap(void)
    2949             : {
    2950             : #ifndef WIN32
    2951        6928 :     pqsignal(SIGPIPE, always_ignore_sigpipe ? SIG_IGN : SIG_DFL);
    2952             : #endif
    2953        6928 : }
    2954             : 
    2955             : /*
    2956             :  * set_sigpipe_trap_state
    2957             :  *
    2958             :  * Set the trap state that restore_sigpipe_trap should restore to.
    2959             :  */
    2960             : void
    2961        6928 : set_sigpipe_trap_state(bool ignore)
    2962             : {
    2963        6928 :     always_ignore_sigpipe = ignore;
    2964        6928 : }
    2965             : 
    2966             : 
    2967             : /*
    2968             :  * PageOutput
    2969             :  *
    2970             :  * Tests if pager is needed and returns appropriate FILE pointer.
    2971             :  *
    2972             :  * If the topt argument is NULL no pager is used.
    2973             :  */
    2974             : FILE *
    2975       72626 : PageOutput(int lines, const printTableOpt *topt)
    2976             : {
    2977             :     /* check whether we need / can / are supposed to use pager */
    2978       72626 :     if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout)))
    2979             :     {
    2980             : #ifdef TIOCGWINSZ
    2981           0 :         unsigned short int pager = topt->pager;
    2982           0 :         int         min_lines = topt->pager_min_lines;
    2983             :         int         result;
    2984             :         struct winsize screen_size;
    2985             : 
    2986           0 :         result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
    2987             : 
    2988             :         /* >= accounts for a one-line prompt */
    2989           0 :         if (result == -1
    2990           0 :             || (lines >= screen_size.ws_row && lines >= min_lines)
    2991           0 :             || pager > 1)
    2992             : #endif
    2993             :         {
    2994             :             const char *pagerprog;
    2995             :             FILE       *pagerpipe;
    2996             : 
    2997           0 :             pagerprog = getenv("PSQL_PAGER");
    2998           0 :             if (!pagerprog)
    2999           0 :                 pagerprog = getenv("PAGER");
    3000           0 :             if (!pagerprog)
    3001           0 :                 pagerprog = DEFAULT_PAGER;
    3002             :             else
    3003             :             {
    3004             :                 /* if PAGER is empty or all-white-space, don't use pager */
    3005           0 :                 if (strspn(pagerprog, " \t\r\n") == strlen(pagerprog))
    3006           0 :                     return stdout;
    3007             :             }
    3008           0 :             disable_sigpipe_trap();
    3009           0 :             pagerpipe = popen(pagerprog, "w");
    3010           0 :             if (pagerpipe)
    3011           0 :                 return pagerpipe;
    3012             :             /* if popen fails, silently proceed without pager */
    3013           0 :             restore_sigpipe_trap();
    3014             :         }
    3015             :     }
    3016             : 
    3017       72626 :     return stdout;
    3018             : }
    3019             : 
    3020             : /*
    3021             :  * ClosePager
    3022             :  *
    3023             :  * Close previously opened pager pipe, if any
    3024             :  */
    3025             : void
    3026         258 : ClosePager(FILE *pagerpipe)
    3027             : {
    3028         258 :     if (pagerpipe && pagerpipe != stdout)
    3029             :     {
    3030             :         /*
    3031             :          * If printing was canceled midstream, warn about it.
    3032             :          *
    3033             :          * Some pagers like less use Ctrl-C as part of their command set. Even
    3034             :          * so, we abort our processing and warn the user what we did.  If the
    3035             :          * pager quit as a result of the SIGINT, this message won't go
    3036             :          * anywhere ...
    3037             :          */
    3038           0 :         if (cancel_pressed)
    3039           0 :             fprintf(pagerpipe, _("Interrupted\n"));
    3040             : 
    3041           0 :         pclose(pagerpipe);
    3042           0 :         restore_sigpipe_trap();
    3043             :     }
    3044         258 : }
    3045             : 
    3046             : /*
    3047             :  * Initialise a table contents struct.
    3048             :  *      Must be called before any other printTable method is used.
    3049             :  *
    3050             :  * The title is not duplicated; the caller must ensure that the buffer
    3051             :  * is available for the lifetime of the printTableContent struct.
    3052             :  *
    3053             :  * If you call this, you must call printTableCleanup once you're done with the
    3054             :  * table.
    3055             :  */
    3056             : void
    3057       72676 : printTableInit(printTableContent *const content, const printTableOpt *opt,
    3058             :                const char *title, const int ncolumns, const int nrows)
    3059             : {
    3060       72676 :     content->opt = opt;
    3061       72676 :     content->title = title;
    3062       72676 :     content->ncolumns = ncolumns;
    3063       72676 :     content->nrows = nrows;
    3064             : 
    3065       72676 :     content->headers = pg_malloc0((ncolumns + 1) * sizeof(*content->headers));
    3066             : 
    3067       72676 :     content->cells = pg_malloc0((ncolumns * nrows + 1) * sizeof(*content->cells));
    3068             : 
    3069       72676 :     content->cellmustfree = NULL;
    3070       72676 :     content->footers = NULL;
    3071             : 
    3072       72676 :     content->aligns = pg_malloc0((ncolumns + 1) * sizeof(*content->align));
    3073             : 
    3074       72676 :     content->header = content->headers;
    3075       72676 :     content->cell = content->cells;
    3076       72676 :     content->footer = content->footers;
    3077       72676 :     content->align = content->aligns;
    3078       72676 :     content->cellsadded = 0;
    3079       72676 : }
    3080             : 
    3081             : /*
    3082             :  * Add a header to the table.
    3083             :  *
    3084             :  * Headers are not duplicated; you must ensure that the header string is
    3085             :  * available for the lifetime of the printTableContent struct.
    3086             :  *
    3087             :  * If translate is true, the function will pass the header through gettext.
    3088             :  * Otherwise, the header will not be translated.
    3089             :  *
    3090             :  * align is either 'l' or 'r', and specifies the alignment for cells in this
    3091             :  * column.
    3092             :  */
    3093             : void
    3094      134346 : printTableAddHeader(printTableContent *const content, char *header,
    3095             :                     const bool translate, const char align)
    3096             : {
    3097             : #ifndef ENABLE_NLS
    3098             :     (void) translate;           /* unused parameter */
    3099             : #endif
    3100             : 
    3101      134346 :     if (content->header >= content->headers + content->ncolumns)
    3102             :     {
    3103           0 :         fprintf(stderr, _("Cannot add header to table content: "
    3104             :                           "column count of %d exceeded.\n"),
    3105             :                 content->ncolumns);
    3106           0 :         exit(EXIT_FAILURE);
    3107             :     }
    3108             : 
    3109      268692 :     *content->header = (char *) mbvalidate((unsigned char *) header,
    3110      134346 :                                            content->opt->encoding);
    3111             : #ifdef ENABLE_NLS
    3112      134346 :     if (translate)
    3113       15572 :         *content->header = _(*content->header);
    3114             : #endif
    3115      134346 :     content->header++;
    3116             : 
    3117      134346 :     *content->align = align;
    3118      134346 :     content->align++;
    3119      134346 : }
    3120             : 
    3121             : /*
    3122             :  * Add a cell to the table.
    3123             :  *
    3124             :  * Cells are not duplicated; you must ensure that the cell string is available
    3125             :  * for the lifetime of the printTableContent struct.
    3126             :  *
    3127             :  * If translate is true, the function will pass the cell through gettext.
    3128             :  * Otherwise, the cell will not be translated.
    3129             :  *
    3130             :  * If mustfree is true, the cell string is freed by printTableCleanup().
    3131             :  * Note: Automatic freeing of translatable strings is not supported.
    3132             :  */
    3133             : void
    3134      882862 : printTableAddCell(printTableContent *const content, char *cell,
    3135             :                   const bool translate, const bool mustfree)
    3136             : {
    3137             : #ifndef ENABLE_NLS
    3138             :     (void) translate;           /* unused parameter */
    3139             : #endif
    3140             : 
    3141      882862 :     if (content->cellsadded >= content->ncolumns * content->nrows)
    3142             :     {
    3143           0 :         fprintf(stderr, _("Cannot add cell to table content: "
    3144             :                           "total cell count of %d exceeded.\n"),
    3145           0 :                 content->ncolumns * content->nrows);
    3146           0 :         exit(EXIT_FAILURE);
    3147             :     }
    3148             : 
    3149     1765724 :     *content->cell = (char *) mbvalidate((unsigned char *) cell,
    3150      882862 :                                          content->opt->encoding);
    3151             : 
    3152             : #ifdef ENABLE_NLS
    3153      882862 :     if (translate)
    3154         962 :         *content->cell = _(*content->cell);
    3155             : #endif
    3156             : 
    3157      882862 :     if (mustfree)
    3158             :     {
    3159         156 :         if (content->cellmustfree == NULL)
    3160          84 :             content->cellmustfree =
    3161          84 :                 pg_malloc0((content->ncolumns * content->nrows + 1) * sizeof(bool));
    3162             : 
    3163         156 :         content->cellmustfree[content->cellsadded] = true;
    3164             :     }
    3165      882862 :     content->cell++;
    3166      882862 :     content->cellsadded++;
    3167      882862 : }
    3168             : 
    3169             : /*
    3170             :  * Add a footer to the table.
    3171             :  *
    3172             :  * Footers are added as elements of a singly-linked list, and the content is
    3173             :  * strdup'd, so there is no need to keep the original footer string around.
    3174             :  *
    3175             :  * Footers are never translated by the function.  If you want the footer
    3176             :  * translated you must do so yourself, before calling printTableAddFooter.  The
    3177             :  * reason this works differently to headers and cells is that footers tend to
    3178             :  * be made of up individually translated components, rather than being
    3179             :  * translated as a whole.
    3180             :  */
    3181             : void
    3182        4958 : printTableAddFooter(printTableContent *const content, const char *footer)
    3183             : {
    3184             :     printTableFooter *f;
    3185             : 
    3186        4958 :     f = pg_malloc0(sizeof(*f));
    3187        4958 :     f->data = pg_strdup(footer);
    3188             : 
    3189        4958 :     if (content->footers == NULL)
    3190        1710 :         content->footers = f;
    3191             :     else
    3192        3248 :         content->footer->next = f;
    3193             : 
    3194        4958 :     content->footer = f;
    3195        4958 : }
    3196             : 
    3197             : /*
    3198             :  * Change the content of the last-added footer.
    3199             :  *
    3200             :  * The current contents of the last-added footer are freed, and replaced by the
    3201             :  * content given in *footer.  If there was no previous footer, add a new one.
    3202             :  *
    3203             :  * The content is strdup'd, so there is no need to keep the original string
    3204             :  * around.
    3205             :  */
    3206             : void
    3207          20 : printTableSetFooter(printTableContent *const content, const char *footer)
    3208             : {
    3209          20 :     if (content->footers != NULL)
    3210             :     {
    3211          20 :         free(content->footer->data);
    3212          20 :         content->footer->data = pg_strdup(footer);
    3213             :     }
    3214             :     else
    3215           0 :         printTableAddFooter(content, footer);
    3216          20 : }
    3217             : 
    3218             : /*
    3219             :  * Free all memory allocated to this struct.
    3220             :  *
    3221             :  * Once this has been called, the struct is unusable unless you pass it to
    3222             :  * printTableInit() again.
    3223             :  */
    3224             : void
    3225       72676 : printTableCleanup(printTableContent *const content)
    3226             : {
    3227       72676 :     if (content->cellmustfree)
    3228             :     {
    3229             :         int         i;
    3230             : 
    3231        1316 :         for (i = 0; i < content->nrows * content->ncolumns; i++)
    3232             :         {
    3233        1232 :             if (content->cellmustfree[i])
    3234         156 :                 free(unconstify(char *, content->cells[i]));
    3235             :         }
    3236          84 :         free(content->cellmustfree);
    3237          84 :         content->cellmustfree = NULL;
    3238             :     }
    3239       72676 :     free(content->headers);
    3240       72676 :     free(content->cells);
    3241       72676 :     free(content->aligns);
    3242             : 
    3243       72676 :     content->opt = NULL;
    3244       72676 :     content->title = NULL;
    3245       72676 :     content->headers = NULL;
    3246       72676 :     content->cells = NULL;
    3247       72676 :     content->aligns = NULL;
    3248       72676 :     content->header = NULL;
    3249       72676 :     content->cell = NULL;
    3250       72676 :     content->align = NULL;
    3251             : 
    3252       72676 :     if (content->footers)
    3253             :     {
    3254        6668 :         for (content->footer = content->footers; content->footer;)
    3255             :         {
    3256             :             printTableFooter *f;
    3257             : 
    3258        4958 :             f = content->footer;
    3259        4958 :             content->footer = f->next;
    3260        4958 :             free(f->data);
    3261        4958 :             free(f);
    3262             :         }
    3263             :     }
    3264       72676 :     content->footers = NULL;
    3265       72676 :     content->footer = NULL;
    3266       72676 : }
    3267             : 
    3268             : /*
    3269             :  * IsPagerNeeded
    3270             :  *
    3271             :  * Setup pager if required
    3272             :  */
    3273             : static void
    3274       72368 : IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
    3275             :               FILE **fout, bool *is_pager)
    3276             : {
    3277       72368 :     if (*fout == stdout)
    3278             :     {
    3279             :         int         lines;
    3280             : 
    3281       72368 :         if (expanded)
    3282         530 :             lines = (cont->ncolumns + 1) * cont->nrows;
    3283             :         else
    3284       71838 :             lines = cont->nrows + 1;
    3285             : 
    3286       72368 :         if (!cont->opt->tuples_only)
    3287             :         {
    3288             :             printTableFooter *f;
    3289             : 
    3290             :             /*
    3291             :              * FIXME -- this is slightly bogus: it counts the number of
    3292             :              * footers, not the number of lines in them.
    3293             :              */
    3294       74088 :             for (f = cont->footers; f; f = f->next)
    3295        4942 :                 lines++;
    3296             :         }
    3297             : 
    3298       72368 :         *fout = PageOutput(lines + extra_lines, cont->opt);
    3299       72368 :         *is_pager = (*fout != stdout);
    3300             :     }
    3301             :     else
    3302           0 :         *is_pager = false;
    3303       72368 : }
    3304             : 
    3305             : /*
    3306             :  * Use this to print any table in the supported formats.
    3307             :  *
    3308             :  * cont: table data and formatting options
    3309             :  * fout: where to print to
    3310             :  * is_pager: true if caller has already redirected fout to be a pager pipe
    3311             :  * flog: if not null, also print the table there (for --log-file option)
    3312             :  */
    3313             : void
    3314       72672 : printTable(const printTableContent *cont,
    3315             :            FILE *fout, bool is_pager, FILE *flog)
    3316             : {
    3317       72672 :     bool        is_local_pager = false;
    3318             : 
    3319       72672 :     if (cancel_pressed)
    3320           0 :         return;
    3321             : 
    3322       72672 :     if (cont->opt->format == PRINT_NOTHING)
    3323           0 :         return;
    3324             : 
    3325             :     /* print_aligned_*() handle the pager themselves */
    3326       72672 :     if (!is_pager &&
    3327       72592 :         cont->opt->format != PRINT_ALIGNED &&
    3328        3720 :         cont->opt->format != PRINT_WRAPPED)
    3329             :     {
    3330        3560 :         IsPagerNeeded(cont, 0, (cont->opt->expanded == 1), &fout, &is_pager);
    3331        3560 :         is_local_pager = is_pager;
    3332             :     }
    3333             : 
    3334             :     /* clear any pre-existing error indication on the output stream */
    3335       72672 :     clearerr(fout);
    3336             : 
    3337             :     /* print the stuff */
    3338             : 
    3339       72672 :     if (flog)
    3340           0 :         print_aligned_text(cont, flog, false);
    3341             : 
    3342       72672 :     switch (cont->opt->format)
    3343             :     {
    3344        3292 :         case PRINT_UNALIGNED:
    3345        3292 :             if (cont->opt->expanded == 1)
    3346          68 :                 print_unaligned_vertical(cont, fout);
    3347             :             else
    3348        3224 :                 print_unaligned_text(cont, fout);
    3349        3292 :             break;
    3350       69112 :         case PRINT_ALIGNED:
    3351             :         case PRINT_WRAPPED:
    3352             : 
    3353             :             /*
    3354             :              * In expanded-auto mode, force vertical if a pager is passed in;
    3355             :              * else we may make different decisions for different hunks of the
    3356             :              * query result.
    3357             :              */
    3358       69112 :             if (cont->opt->expanded == 1 ||
    3359       68746 :                 (cont->opt->expanded == 2 && is_pager))
    3360         366 :                 print_aligned_vertical(cont, fout, is_pager);
    3361             :             else
    3362       68746 :                 print_aligned_text(cont, fout, is_pager);
    3363       69112 :             break;
    3364          44 :         case PRINT_CSV:
    3365          44 :             if (cont->opt->expanded == 1)
    3366          12 :                 print_csv_vertical(cont, fout);
    3367             :             else
    3368          32 :                 print_csv_text(cont, fout);
    3369          44 :             break;
    3370          40 :         case PRINT_HTML:
    3371          40 :             if (cont->opt->expanded == 1)
    3372          20 :                 print_html_vertical(cont, fout);
    3373             :             else
    3374          20 :                 print_html_text(cont, fout);
    3375          40 :             break;
    3376          40 :         case PRINT_ASCIIDOC:
    3377          40 :             if (cont->opt->expanded == 1)
    3378          20 :                 print_asciidoc_vertical(cont, fout);
    3379             :             else
    3380          20 :                 print_asciidoc_text(cont, fout);
    3381          40 :             break;
    3382          48 :         case PRINT_LATEX:
    3383          48 :             if (cont->opt->expanded == 1)
    3384          24 :                 print_latex_vertical(cont, fout);
    3385             :             else
    3386          24 :                 print_latex_text(cont, fout);
    3387          48 :             break;
    3388          56 :         case PRINT_LATEX_LONGTABLE:
    3389          56 :             if (cont->opt->expanded == 1)
    3390          28 :                 print_latex_vertical(cont, fout);
    3391             :             else
    3392          28 :                 print_latex_longtable_text(cont, fout);
    3393          56 :             break;
    3394          40 :         case PRINT_TROFF_MS:
    3395          40 :             if (cont->opt->expanded == 1)
    3396          20 :                 print_troff_ms_vertical(cont, fout);
    3397             :             else
    3398          20 :                 print_troff_ms_text(cont, fout);
    3399          40 :             break;
    3400           0 :         default:
    3401           0 :             fprintf(stderr, _("invalid output format (internal error): %d"),
    3402           0 :                     cont->opt->format);
    3403           0 :             exit(EXIT_FAILURE);
    3404             :     }
    3405             : 
    3406       72672 :     if (is_local_pager)
    3407           0 :         ClosePager(fout);
    3408             : }
    3409             : 
    3410             : /*
    3411             :  * Use this to print query results
    3412             :  *
    3413             :  * result: result of a successful query
    3414             :  * opt: formatting options
    3415             :  * fout: where to print to
    3416             :  * is_pager: true if caller has already redirected fout to be a pager pipe
    3417             :  * flog: if not null, also print the data there (for --log-file option)
    3418             :  */
    3419             : void
    3420       70754 : printQuery(const PGresult *result, const printQueryOpt *opt,
    3421             :            FILE *fout, bool is_pager, FILE *flog)
    3422             : {
    3423             :     printTableContent cont;
    3424             :     int         i,
    3425             :                 r,
    3426             :                 c;
    3427             : 
    3428       70754 :     if (cancel_pressed)
    3429           0 :         return;
    3430             : 
    3431       70754 :     printTableInit(&cont, &opt->topt, opt->title,
    3432             :                    PQnfields(result), PQntuples(result));
    3433             : 
    3434             :     /* Assert caller supplied enough translate_columns[] entries */
    3435             :     Assert(opt->translate_columns == NULL ||
    3436             :            opt->n_translate_columns >= cont.ncolumns);
    3437             : 
    3438      193352 :     for (i = 0; i < cont.ncolumns; i++)
    3439             :     {
    3440      122598 :         printTableAddHeader(&cont, PQfname(result, i),
    3441      122598 :                             opt->translate_header,
    3442      122598 :                             column_type_alignment(PQftype(result, i)));
    3443             :     }
    3444             : 
    3445             :     /* set cells */
    3446      361080 :     for (r = 0; r < cont.nrows; r++)
    3447             :     {
    3448     1146536 :         for (c = 0; c < cont.ncolumns; c++)
    3449             :         {
    3450             :             char       *cell;
    3451      856210 :             bool        mustfree = false;
    3452             :             bool        translate;
    3453             : 
    3454      856210 :             if (PQgetisnull(result, r, c))
    3455       89942 :                 cell = opt->nullPrint ? opt->nullPrint : "";
    3456             :             else
    3457             :             {
    3458      766268 :                 cell = PQgetvalue(result, r, c);
    3459      766268 :                 if (cont.aligns[c] == 'r' && opt->topt.numericLocale)
    3460             :                 {
    3461          64 :                     cell = format_numeric_locale(cell);
    3462          64 :                     mustfree = true;
    3463             :                 }
    3464             :             }
    3465             : 
    3466      856210 :             translate = (opt->translate_columns && opt->translate_columns[c]);
    3467      856210 :             printTableAddCell(&cont, cell, translate, mustfree);
    3468             :         }
    3469             :     }
    3470             : 
    3471             :     /* set footers */
    3472       70754 :     if (opt->footers)
    3473             :     {
    3474             :         char      **footer;
    3475             : 
    3476         196 :         for (footer = opt->footers; *footer; footer++)
    3477          96 :             printTableAddFooter(&cont, *footer);
    3478             :     }
    3479             : 
    3480       70754 :     printTable(&cont, fout, is_pager, flog);
    3481       70754 :     printTableCleanup(&cont);
    3482             : }
    3483             : 
    3484             : char
    3485      122710 : column_type_alignment(Oid ftype)
    3486             : {
    3487             :     char        align;
    3488             : 
    3489      122710 :     switch (ftype)
    3490             :     {
    3491       49970 :         case INT2OID:
    3492             :         case INT4OID:
    3493             :         case INT8OID:
    3494             :         case FLOAT4OID:
    3495             :         case FLOAT8OID:
    3496             :         case NUMERICOID:
    3497             :         case OIDOID:
    3498             :         case XIDOID:
    3499             :         case XID8OID:
    3500             :         case CIDOID:
    3501             :         case MONEYOID:
    3502       49970 :             align = 'r';
    3503       49970 :             break;
    3504       72740 :         default:
    3505       72740 :             align = 'l';
    3506       72740 :             break;
    3507             :     }
    3508      122710 :     return align;
    3509             : }
    3510             : 
    3511             : void
    3512        7346 : setDecimalLocale(void)
    3513             : {
    3514             :     struct lconv *extlconv;
    3515             : 
    3516        7346 :     extlconv = localeconv();
    3517             : 
    3518             :     /* Don't accept an empty decimal_point string */
    3519        7346 :     if (*extlconv->decimal_point)
    3520        7346 :         decimal_point = pg_strdup(extlconv->decimal_point);
    3521             :     else
    3522           0 :         decimal_point = ".";  /* SQL output standard */
    3523             : 
    3524             :     /*
    3525             :      * Although the Open Group standard allows locales to supply more than one
    3526             :      * group width, we consider only the first one, and we ignore any attempt
    3527             :      * to suppress grouping by specifying CHAR_MAX.  As in the backend's
    3528             :      * cash.c, we must apply a range check to avoid being fooled by variant
    3529             :      * CHAR_MAX values.
    3530             :      */
    3531        7346 :     groupdigits = *extlconv->grouping;
    3532        7346 :     if (groupdigits <= 0 || groupdigits > 6)
    3533          18 :         groupdigits = 3;        /* most common */
    3534             : 
    3535             :     /* Don't accept an empty thousands_sep string, either */
    3536             :     /* similar code exists in formatting.c */
    3537        7346 :     if (*extlconv->thousands_sep)
    3538        7328 :         thousands_sep = pg_strdup(extlconv->thousands_sep);
    3539             :     /* Make sure thousands separator doesn't match decimal point symbol. */
    3540          18 :     else if (strcmp(decimal_point, ",") != 0)
    3541          18 :         thousands_sep = ",";
    3542             :     else
    3543           0 :         thousands_sep = ".";
    3544        7346 : }
    3545             : 
    3546             : /* get selected or default line style */
    3547             : const printTextFormat *
    3548       69116 : get_line_style(const printTableOpt *opt)
    3549             : {
    3550             :     /*
    3551             :      * Note: this function mainly exists to preserve the convention that a
    3552             :      * printTableOpt struct can be initialized to zeroes to get default
    3553             :      * behavior.
    3554             :      */
    3555       69116 :     if (opt->line_style != NULL)
    3556         504 :         return opt->line_style;
    3557             :     else
    3558       68612 :         return &pg_asciiformat;
    3559             : }
    3560             : 
    3561             : void
    3562        7346 : refresh_utf8format(const printTableOpt *opt)
    3563             : {
    3564        7346 :     printTextFormat *popt = &pg_utf8format;
    3565             : 
    3566             :     const unicodeStyleBorderFormat *border;
    3567             :     const unicodeStyleRowFormat *header;
    3568             :     const unicodeStyleColumnFormat *column;
    3569             : 
    3570        7346 :     popt->name = "unicode";
    3571             : 
    3572        7346 :     border = &unicode_style.border_style[opt->unicode_border_linestyle];
    3573        7346 :     header = &unicode_style.row_style[opt->unicode_header_linestyle];
    3574        7346 :     column = &unicode_style.column_style[opt->unicode_column_linestyle];
    3575             : 
    3576        7346 :     popt->lrule[PRINT_RULE_TOP].hrule = border->horizontal;
    3577        7346 :     popt->lrule[PRINT_RULE_TOP].leftvrule = border->down_and_right;
    3578        7346 :     popt->lrule[PRINT_RULE_TOP].midvrule = column->down_and_horizontal[opt->unicode_border_linestyle];
    3579        7346 :     popt->lrule[PRINT_RULE_TOP].rightvrule = border->down_and_left;
    3580             : 
    3581        7346 :     popt->lrule[PRINT_RULE_MIDDLE].hrule = header->horizontal;
    3582        7346 :     popt->lrule[PRINT_RULE_MIDDLE].leftvrule = header->vertical_and_right[opt->unicode_border_linestyle];
    3583        7346 :     popt->lrule[PRINT_RULE_MIDDLE].midvrule = column->vertical_and_horizontal[opt->unicode_header_linestyle];
    3584        7346 :     popt->lrule[PRINT_RULE_MIDDLE].rightvrule = header->vertical_and_left[opt->unicode_border_linestyle];
    3585             : 
    3586        7346 :     popt->lrule[PRINT_RULE_BOTTOM].hrule = border->horizontal;
    3587        7346 :     popt->lrule[PRINT_RULE_BOTTOM].leftvrule = border->up_and_right;
    3588        7346 :     popt->lrule[PRINT_RULE_BOTTOM].midvrule = column->up_and_horizontal[opt->unicode_border_linestyle];
    3589        7346 :     popt->lrule[PRINT_RULE_BOTTOM].rightvrule = border->left_and_right;
    3590             : 
    3591             :     /* N/A */
    3592        7346 :     popt->lrule[PRINT_RULE_DATA].hrule = "";
    3593        7346 :     popt->lrule[PRINT_RULE_DATA].leftvrule = border->vertical;
    3594        7346 :     popt->lrule[PRINT_RULE_DATA].midvrule = column->vertical;
    3595        7346 :     popt->lrule[PRINT_RULE_DATA].rightvrule = border->vertical;
    3596             : 
    3597        7346 :     popt->midvrule_nl = column->vertical;
    3598        7346 :     popt->midvrule_wrap = column->vertical;
    3599        7346 :     popt->midvrule_blank = column->vertical;
    3600             : 
    3601             :     /* Same for all unicode today */
    3602        7346 :     popt->header_nl_left = unicode_style.header_nl_left;
    3603        7346 :     popt->header_nl_right = unicode_style.header_nl_right;
    3604        7346 :     popt->nl_left = unicode_style.nl_left;
    3605        7346 :     popt->nl_right = unicode_style.nl_right;
    3606        7346 :     popt->wrap_left = unicode_style.wrap_left;
    3607        7346 :     popt->wrap_right = unicode_style.wrap_right;
    3608        7346 :     popt->wrap_right_border = unicode_style.wrap_right_border;
    3609        7346 : }
    3610             : 
    3611             : /*
    3612             :  * Compute the byte distance to the end of the string or *target_width
    3613             :  * display character positions, whichever comes first.  Update *target_width
    3614             :  * to be the number of display character positions actually filled.
    3615             :  */
    3616             : static int
    3617      661002 : strlen_max_width(unsigned char *str, int *target_width, int encoding)
    3618             : {
    3619      661002 :     unsigned char *start = str;
    3620      661002 :     unsigned char *end = str + strlen((char *) str);
    3621      661002 :     int         curr_width = 0;
    3622             : 
    3623     8134942 :     while (str < end)
    3624             :     {
    3625     7474960 :         int         char_width = PQdsplen((char *) str, encoding);
    3626             : 
    3627             :         /*
    3628             :          * If the display width of the new character causes the string to
    3629             :          * exceed its target width, skip it and return.  However, if this is
    3630             :          * the first character of the string (curr_width == 0), we have to
    3631             :          * accept it.
    3632             :          */
    3633     7474960 :         if (*target_width < curr_width + char_width && curr_width != 0)
    3634        1020 :             break;
    3635             : 
    3636     7473940 :         curr_width += char_width;
    3637             : 
    3638     7473940 :         str += PQmblen((char *) str, encoding);
    3639             : 
    3640     7473940 :         if (str > end)           /* Don't overrun invalid string */
    3641           0 :             str = end;
    3642             :     }
    3643             : 
    3644      661002 :     *target_width = curr_width;
    3645             : 
    3646      661002 :     return str - start;
    3647             : }

Generated by: LCOV version 1.13