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

Generated by: LCOV version 1.14