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

Generated by: LCOV version 2.0-1