LCOV - code coverage report
Current view: top level - src/fe_utils - print.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 1518 1632 93.0 %
Date: 2025-10-10 18:17:38 Functions: 49 49 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.16