| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Text::Table::More; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 86075 | use 5.010001; | 
|  | 1 |  |  |  |  | 12 |  | 
| 4 | 1 |  |  | 1 |  | 5 | use strict; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 17 |  | 
| 5 | 1 |  |  | 1 |  | 4 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 169 |  | 
| 6 |  |  |  |  |  |  | #use utf8; | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY | 
| 9 |  |  |  |  |  |  | our $DATE = '2022-01-27'; # DATE | 
| 10 |  |  |  |  |  |  | our $DIST = 'Text-Table-More'; # DIST | 
| 11 |  |  |  |  |  |  | our $VERSION = '0.022'; # VERSION | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | # see Module::Features for more details on this | 
| 14 |  |  |  |  |  |  | our %FEATURES = ( | 
| 15 |  |  |  |  |  |  | set_v => { | 
| 16 |  |  |  |  |  |  | TextTable => 1, | 
| 17 |  |  |  |  |  |  | }, | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | features => { | 
| 20 |  |  |  |  |  |  | PerlTrove => { | 
| 21 |  |  |  |  |  |  | "Development Status" => "4 - Beta", | 
| 22 |  |  |  |  |  |  | "Environment" => "Console", | 
| 23 |  |  |  |  |  |  | # Framework | 
| 24 |  |  |  |  |  |  | "Intended Audience" => ["Developers"], | 
| 25 |  |  |  |  |  |  | "License" => "OSI Approved :: Artistic License", | 
| 26 |  |  |  |  |  |  | # Natural Language | 
| 27 |  |  |  |  |  |  | # Operating System | 
| 28 |  |  |  |  |  |  | "Programming Language" => "Perl", | 
| 29 |  |  |  |  |  |  | "Topic" => ["Software Development :: Libraries :: Perl Modules", "Utilities"], | 
| 30 |  |  |  |  |  |  | # Typing | 
| 31 |  |  |  |  |  |  | }, | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | TextTable => { | 
| 34 |  |  |  |  |  |  | can_align_cell_containing_wide_character => 1, | 
| 35 |  |  |  |  |  |  | can_align_cell_containing_color_code     => 1, | 
| 36 |  |  |  |  |  |  | can_align_cell_containing_newline        => 1, | 
| 37 |  |  |  |  |  |  | can_use_box_character                    => 1, | 
| 38 |  |  |  |  |  |  | can_customize_border                     => 1, | 
| 39 |  |  |  |  |  |  | can_halign                               => 1, | 
| 40 |  |  |  |  |  |  | can_halign_individual_row                => 1, | 
| 41 |  |  |  |  |  |  | can_halign_individual_column             => 1, | 
| 42 |  |  |  |  |  |  | can_halign_individual_cell               => 1, | 
| 43 |  |  |  |  |  |  | can_valign                               => 1, | 
| 44 |  |  |  |  |  |  | can_valign_individual_row                => 1, | 
| 45 |  |  |  |  |  |  | can_valign_individual_column             => 1, | 
| 46 |  |  |  |  |  |  | can_valign_individual_cell               => 1, | 
| 47 |  |  |  |  |  |  | can_rowspan                              => 1, | 
| 48 |  |  |  |  |  |  | can_colspan                              => 1, | 
| 49 |  |  |  |  |  |  | can_color                                => 0, | 
| 50 |  |  |  |  |  |  | can_color_theme                          => 0, | 
| 51 |  |  |  |  |  |  | can_set_cell_height                      => 0, | 
| 52 |  |  |  |  |  |  | can_set_cell_height_of_individual_row    => 0, | 
| 53 |  |  |  |  |  |  | can_set_cell_width                       => 0, | 
| 54 |  |  |  |  |  |  | can_set_cell_width_of_individual_column  => 0, | 
| 55 |  |  |  |  |  |  | speed                                    => 'slow', | 
| 56 |  |  |  |  |  |  | can_hpad                                 => 0, | 
| 57 |  |  |  |  |  |  | can_hpad_individual_row                  => 0, | 
| 58 |  |  |  |  |  |  | can_hpad_individual_column               => 0, | 
| 59 |  |  |  |  |  |  | can_hpad_individual_cell                 => 0, | 
| 60 |  |  |  |  |  |  | can_vpad                                 => 0, | 
| 61 |  |  |  |  |  |  | can_vpad_individual_row                  => 0, | 
| 62 |  |  |  |  |  |  | can_vpad_individual_column               => 0, | 
| 63 |  |  |  |  |  |  | can_vpad_individual_cell                 => 0, | 
| 64 |  |  |  |  |  |  | }, | 
| 65 |  |  |  |  |  |  | }, | 
| 66 |  |  |  |  |  |  | ); | 
| 67 |  |  |  |  |  |  |  | 
| 68 | 1 |  |  | 1 |  | 1887 | use List::AllUtils qw(first firstidx max); | 
|  | 1 |  |  |  |  | 16671 |  | 
|  | 1 |  |  |  |  | 80 |  | 
| 69 |  |  |  |  |  |  |  | 
| 70 | 1 |  |  | 1 |  | 6 | use Exporter qw(import); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 3552 |  | 
| 71 |  |  |  |  |  |  | our @EXPORT_OK = qw/ generate_table /; | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | our $_split_lines_func; | 
| 74 |  |  |  |  |  |  | our $_pad_func; | 
| 75 |  |  |  |  |  |  | our $_length_height_func; | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | # consts | 
| 78 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_ROWSPAN()         {0} # number of rowspan, only defined for the rowspan head | 
| 79 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_COLSPAN()         {1} # number of colspan, only defined for the colspan head | 
| 80 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_WIDTH()           {2} # visual width. this does not include the cell padding. | 
| 81 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_HEIGHT()          {3} # visual height. this does not include row separator. | 
| 82 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_ORIG()            {4} # str/hash | 
| 83 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL() {5} # whether this cell is tail of a rowspan | 
| 84 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL() {6} # whether this cell is tail of a colspan | 
| 85 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_ORIG_ROWNUM()     {7} # | 
| 86 |  |  |  |  |  |  | sub IDX_EXPTABLE_CELL_ORIG_COLNUM()     {8} # | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | # whether an exptable cell is the head (1st cell) or tail (the rest) of a | 
| 89 |  |  |  |  |  |  | # rowspan/colspan. these should be macros if possible, for speed. | 
| 90 | 135 | 50 |  | 135 |  | 331 | sub _exptable_cell_is_rowspan_tail { defined($_[0]) &&  $_[0][IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL] } | 
| 91 | 125 | 50 |  | 125 |  | 296 | sub _exptable_cell_is_colspan_tail { defined($_[0]) &&  $_[0][IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL] } | 
| 92 | 133 | 50 | 66 | 133 |  | 369 | sub _exptable_cell_is_tail         { defined($_[0]) && ($_[0][IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL] || $_[0][IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL]) } | 
| 93 | 82 | 50 |  | 82 |  | 234 | sub _exptable_cell_is_rowspan_head { defined($_[0]) && !$_[0][IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL] } | 
| 94 | 0 | 0 |  | 0 |  | 0 | sub _exptable_cell_is_colspan_head { defined($_[0]) && !$_[0][IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL] } | 
| 95 | 156 | 50 |  | 156 |  | 514 | sub _exptable_cell_is_head         { defined($_[0]) && defined $_[0][IDX_EXPTABLE_CELL_ORIG] } | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | sub _divide_int_to_n_ints { | 
| 98 | 218 |  |  | 218 |  | 303 | my ($int, $n) = @_; | 
| 99 | 218 |  |  |  |  | 220 | my $subtot = 0; | 
| 100 | 218 |  |  |  |  | 211 | my $int_subtot = 0; | 
| 101 | 218 |  |  |  |  | 206 | my $prev_int_subtot = 0; | 
| 102 | 218 |  |  |  |  | 217 | my @ints; | 
| 103 | 218 |  |  |  |  | 279 | for (1..$n) { | 
| 104 | 237 |  |  |  |  | 285 | $subtot += $int/$n; | 
| 105 | 237 |  |  |  |  | 398 | $int_subtot = sprintf "%.0f", $subtot; | 
| 106 | 237 |  |  |  |  | 281 | push @ints, $int_subtot - $prev_int_subtot; | 
| 107 | 237 |  |  |  |  | 295 | $prev_int_subtot = $int_subtot; | 
| 108 |  |  |  |  |  |  | } | 
| 109 | 218 |  |  |  |  | 326 | @ints; | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | sub _vpad { | 
| 113 | 109 |  |  | 109 |  | 168 | my ($lines, $num_lines, $width, $which) = @_; | 
| 114 | 109 | 100 |  |  |  | 275 | return $lines if @$lines >= $num_lines; # we don't do truncate | 
| 115 | 14 |  |  |  |  | 15 | my @vpadded_lines; | 
| 116 | 14 |  |  |  |  | 21 | my $pad_line = " " x $width; | 
| 117 | 14 | 100 |  |  |  | 44 | if ($which =~ /^b/) { # bottom padding | 
|  |  | 100 |  |  |  |  |  | 
| 118 | 11 |  |  |  |  | 23 | push @vpadded_lines, @$lines; | 
| 119 | 11 |  |  |  |  | 31 | push @vpadded_lines, $pad_line for @$lines+1 .. $num_lines; | 
| 120 |  |  |  |  |  |  | } elsif ($which =~ /^t/) { # top padding | 
| 121 | 1 |  |  |  |  | 6 | push @vpadded_lines, $pad_line for @$lines+1 .. $num_lines; | 
| 122 | 1 |  |  |  |  | 2 | push @vpadded_lines, @$lines; | 
| 123 |  |  |  |  |  |  | } else { # center padding | 
| 124 | 2 |  |  |  |  | 4 | my $p  = $num_lines - @$lines; | 
| 125 | 2 |  |  |  |  | 5 | my $p1 = int($p/2); | 
| 126 | 2 |  |  |  |  | 3 | my $p2 = $p - $p1; | 
| 127 | 2 |  |  |  |  | 7 | push @vpadded_lines, $pad_line for 1..$p1; | 
| 128 | 2 |  |  |  |  | 3 | push @vpadded_lines, @$lines; | 
| 129 | 2 |  |  |  |  | 8 | push @vpadded_lines, $pad_line for 1..$p2; | 
| 130 |  |  |  |  |  |  | } | 
| 131 | 14 |  |  |  |  | 37 | \@vpadded_lines; | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | sub _get_attr { | 
| 135 | 566 |  |  | 566 |  | 764 | my ($attr_name, $y, $x, $cell_value, $table_args) = @_; | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | CELL_ATTRS_FROM_CELL_VALUE: { | 
| 138 | 566 | 100 |  |  |  | 583 | last unless ref $cell_value eq 'HASH'; | 
|  | 566 |  |  |  |  | 821 |  | 
| 139 | 74 |  |  |  |  | 93 | my $attr_val = $cell_value->{$attr_name}; | 
| 140 | 74 | 100 |  |  |  | 117 | return $attr_val if defined $attr_val; | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | CELL_ATTRS_FROM_CELL_ATTRS_ARG: | 
| 144 |  |  |  |  |  |  | { | 
| 145 | 563 | 100 | 66 |  |  | 570 | last unless defined $x && defined $y; | 
|  | 563 |  |  |  |  | 1089 |  | 
| 146 | 389 |  |  |  |  | 445 | my $cell_attrs = $table_args->{cell_attrs}; | 
| 147 | 389 | 100 |  |  |  | 577 | last unless $cell_attrs; | 
| 148 | 93 |  |  |  |  | 128 | for my $entry (@$cell_attrs) { | 
| 149 | 93 | 100 | 100 |  |  | 193 | next unless $entry->[0] == $y && $entry->[1] == $x; | 
| 150 | 14 |  |  |  |  | 18 | my $attr_val = $entry->[2]{$attr_name}; | 
| 151 | 14 | 100 |  |  |  | 26 | return $attr_val if defined $attr_val; | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | COL_ATTRS: | 
| 156 |  |  |  |  |  |  | { | 
| 157 | 560 | 100 |  |  |  | 549 | last unless defined $x; | 
|  | 560 |  |  |  |  | 747 |  | 
| 158 | 386 |  |  |  |  | 434 | my $col_attrs = $table_args->{col_attrs}; | 
| 159 | 386 | 100 |  |  |  | 524 | last unless $col_attrs; | 
| 160 | 90 |  |  |  |  | 108 | for my $entry (@$col_attrs) { | 
| 161 | 90 | 100 |  |  |  | 152 | next unless $entry->[0] == $x; | 
| 162 | 18 |  |  |  |  | 25 | my $attr_val = $entry->[1]{$attr_name}; | 
| 163 | 18 | 100 |  |  |  | 42 | return $attr_val if defined $attr_val; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | ROW_ATTRS: | 
| 168 |  |  |  |  |  |  | { | 
| 169 | 551 | 50 |  |  |  | 571 | last unless defined $y; | 
|  | 551 |  |  |  |  | 743 |  | 
| 170 | 551 |  |  |  |  | 586 | my $row_attrs = $table_args->{row_attrs}; | 
| 171 | 551 | 100 |  |  |  | 753 | last unless $row_attrs; | 
| 172 | 163 |  |  |  |  | 211 | for my $entry (@$row_attrs) { | 
| 173 | 163 | 100 |  |  |  | 246 | next unless $entry->[0] == $y; | 
| 174 | 62 |  |  |  |  | 77 | my $attr_val = $entry->[1]{$attr_name}; | 
| 175 | 62 | 100 |  |  |  | 105 | return $attr_val if defined $attr_val; | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | TABLE_ARGS: | 
| 180 |  |  |  |  |  |  | { | 
| 181 | 542 |  |  |  |  | 534 | my $attr_val = $table_args->{$attr_name}; | 
|  | 542 |  |  |  |  | 590 |  | 
| 182 | 542 | 100 |  |  |  | 765 | return $attr_val if defined $attr_val; | 
| 183 |  |  |  |  |  |  | } | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 535 |  |  |  |  | 856 | undef; | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | sub _get_exptable_cell_lines { | 
| 189 | 109 |  |  | 109 |  | 176 | my ($table_args, $exptable, $row_heights, $column_widths, | 
| 190 |  |  |  |  |  |  | $bottom_borders, $intercol_width, $y, $x) = @_; | 
| 191 |  |  |  |  |  |  |  | 
| 192 | 109 |  |  |  |  | 133 | my $exptable_cell = $exptable->[$y][$x]; | 
| 193 | 109 |  |  |  |  | 151 | my $cell   = $exptable_cell->[IDX_EXPTABLE_CELL_ORIG]; | 
| 194 | 109 | 100 |  |  |  | 161 | my $text   = ref $cell eq 'HASH' ? $cell->{text} : $cell; | 
| 195 | 109 |  | 100 |  |  | 194 | my $align  = _get_attr('align', $y, $x, $cell, $table_args) // 'left'; | 
| 196 | 109 |  | 100 |  |  | 163 | my $valign = _get_attr('valign', $y, $x, $cell, $table_args) // 'top'; | 
| 197 | 109 | 100 |  |  |  | 184 | my $pad    = $align eq 'left' ? 'r' : $align eq 'right' ? 'l' : 'c'; | 
|  |  | 100 |  |  |  |  |  | 
| 198 | 109 | 100 |  |  |  | 137 | my $vpad   = $valign eq 'top' ? 'b' : $valign eq 'bottom' ? 't' : 'c'; | 
|  |  | 100 |  |  |  |  |  | 
| 199 | 109 |  |  |  |  | 120 | my $height = 0; | 
| 200 | 109 |  |  |  |  | 139 | my $width  = 0; | 
| 201 | 109 |  |  |  |  | 170 | for my $ic (1..$exptable_cell->[IDX_EXPTABLE_CELL_COLSPAN]) { | 
| 202 | 118 |  |  |  |  | 157 | $width += $column_widths->[$x+$ic-1]; | 
| 203 | 118 | 100 |  |  |  | 222 | $width += $intercol_width if $ic > 1; | 
| 204 |  |  |  |  |  |  | } | 
| 205 | 109 |  |  |  |  | 133 | for my $ir (1..$exptable_cell->[IDX_EXPTABLE_CELL_ROWSPAN]) { | 
| 206 | 119 |  |  |  |  | 145 | $height += $row_heights->[$y+$ir-1]; | 
| 207 | 119 | 100 | 100 |  |  | 293 | $height++ if $bottom_borders->[$y+$ir-2] && $ir > 1; | 
| 208 |  |  |  |  |  |  | } | 
| 209 |  |  |  |  |  |  |  | 
| 210 | 109 |  |  |  |  | 165 | my @datalines = map { $_pad_func->($_, $width, $pad, ' ', 'truncate') } | 
|  | 130 |  |  |  |  | 895 |  | 
| 211 |  |  |  |  |  |  | ($_split_lines_func->($text)); | 
| 212 | 109 |  |  |  |  | 2196 | _vpad(\@datalines, $height, $width, $vpad); | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | sub generate_table { | 
| 216 | 18 |  |  | 18 | 1 | 110058 | require Module::Load::Util; | 
| 217 | 18 |  |  |  |  | 3121 | require Text::NonWideChar::Util; | 
| 218 |  |  |  |  |  |  |  | 
| 219 | 18 |  |  |  |  | 278 | my %args = @_; | 
| 220 | 18 | 50 |  |  |  | 50 | my $rows = $args{rows} or die "Please specify rows"; | 
| 221 | 18 |  | 50 |  |  | 59 | my $bs_name = $args{border_style} // 'ASCII::SingleLineDoubleAfterHeader'; | 
| 222 | 18 |  | 100 |  |  | 44 | my $cell_attrs = $args{cell_attrs} // []; | 
| 223 |  |  |  |  |  |  |  | 
| 224 | 18 |  |  |  |  | 70 | my $bs_obj = Module::Load::Util::instantiate_class_with_optional_args({ns_prefix=>"BorderStyle"}, $bs_name); | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | DETERMINE_CODES: { | 
| 227 | 18 |  |  |  |  | 5660 | my $color = $args{color}; | 
|  | 18 |  |  |  |  | 24 |  | 
| 228 | 18 |  |  |  |  | 24 | my $wide_char = $args{wide_char}; | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | # split_lines | 
| 231 | 18 | 100 |  |  |  | 34 | if ($color) { | 
| 232 | 1 |  |  |  |  | 5 | require Text::ANSI::Util; | 
| 233 | 1 |  |  | 9 |  | 7 | $_split_lines_func = sub { Text::ANSI::Util::ta_add_color_resets(split /\R/, $_[0]) }; | 
|  | 9 |  |  |  |  | 38 |  | 
| 234 |  |  |  |  |  |  | } else { | 
| 235 | 17 |  |  | 100 |  | 84 | $_split_lines_func = sub { split /\R/, $_[0] }; | 
|  | 100 |  |  |  |  | 295 |  | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | # pad & length_height | 
| 239 | 18 | 100 |  |  |  | 34 | if ($color) { | 
| 240 | 1 | 50 |  |  |  | 3 | if ($wide_char) { | 
| 241 | 1 |  |  |  |  | 426 | require Text::ANSI::WideUtil; | 
| 242 | 1 |  |  |  |  | 349 | $_pad_func           = \&Text::ANSI::WideUtil::ta_mbpad; | 
| 243 | 1 |  |  |  |  | 3 | $_length_height_func = \&Text::ANSI::WideUtil::ta_mbswidth_height; | 
| 244 |  |  |  |  |  |  | } else { | 
| 245 | 0 |  |  |  |  | 0 | require Text::ANSI::Util; | 
| 246 | 0 |  |  |  |  | 0 | $_pad_func           = \&Text::ANSI::Util::ta_pad; | 
| 247 | 0 |  |  |  |  | 0 | $_length_height_func = \&Text::ANSI::Util::ta_length_height; | 
| 248 |  |  |  |  |  |  | } | 
| 249 |  |  |  |  |  |  | } else { | 
| 250 | 17 | 50 |  |  |  | 26 | if ($wide_char) { | 
| 251 | 0 |  |  |  |  | 0 | require Text::WideChar::Util; | 
| 252 | 0 |  |  |  |  | 0 | $_pad_func           = \&Text::WideChar::Util::mbpad; | 
| 253 | 0 |  |  |  |  | 0 | $_length_height_func = \&Text::WideChar::Util::mbswidth_height; | 
| 254 |  |  |  |  |  |  | } else { | 
| 255 | 17 |  |  |  |  | 1109 | require String::Pad; | 
| 256 | 17 |  |  |  |  | 548 | require Text::NonWideChar::Util; | 
| 257 | 17 |  |  |  |  | 28 | $_pad_func           = \&String::Pad::pad; | 
| 258 | 17 |  |  |  |  | 25 | $_length_height_func = \&Text::NonWideChar::Util::length_height; | 
| 259 |  |  |  |  |  |  | } | 
| 260 |  |  |  |  |  |  | } | 
| 261 |  |  |  |  |  |  | } | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | # XXX when we allow cell attrs right_border and left_border, this will | 
| 264 |  |  |  |  |  |  | # become array too like $exptable_bottom_borders. | 
| 265 | 18 |  |  |  |  | 54 | my $intercol_width = length(" " . $bs_obj->get_border_char(3, 1) . " "); | 
| 266 |  |  |  |  |  |  |  | 
| 267 | 18 |  |  |  |  | 396 | my $exptable = []; # [ [[$orig_rowidx,$orig_colidx,$rowspan,$colspan,...], ...], [[...], ...], ... ] | 
| 268 | 18 |  |  |  |  | 31 | my $exptable_bottom_borders = []; # idx=exptable rownum, val=bool | 
| 269 | 18 |  |  |  |  | 19 | my $M = 0; # number of rows in the exptable | 
| 270 | 18 |  |  |  |  | 22 | my $N = 0; # number of columns in the exptable | 
| 271 |  |  |  |  |  |  | CONSTRUCT_EXPTABLE: { | 
| 272 |  |  |  |  |  |  | # 1. the first step is to construct a 2D array we call "exptable" (short | 
| 273 |  |  |  |  |  |  | # for expanded table), which is like the original table but with all the | 
| 274 |  |  |  |  |  |  | # spanning rows/columns split into the smaller boxes so it's easier to | 
| 275 |  |  |  |  |  |  | # draw later. for example, a table cell with colspan=2 will become 2 | 
| 276 |  |  |  |  |  |  | # exptable cells. an m-row x n-column table will become M-row x N-column | 
| 277 |  |  |  |  |  |  | # exptable, where M>=m, N>=n. | 
| 278 |  |  |  |  |  |  |  | 
| 279 | 18 |  |  |  |  | 19 | my $rownum; | 
|  | 18 |  |  |  |  | 19 |  | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | # 1a. first substep: construct exptable and calculate everything except | 
| 282 |  |  |  |  |  |  | # each exptable cell's width and height, because this will require | 
| 283 |  |  |  |  |  |  | # information from the previous substeps. | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 18 |  |  |  |  | 21 | $rownum = -1; | 
| 286 | 18 |  |  |  |  | 32 | for my $row (@$rows) { | 
| 287 | 53 |  |  |  |  | 56 | $rownum++; | 
| 288 | 53 |  |  |  |  | 55 | my $colnum = -1; | 
| 289 | 53 |  |  |  |  | 53 | my $separator_type = do { | 
| 290 | 53 |  | 100 |  |  | 106 | my $cmp = ($args{header_row}//0)-1 <=> $rownum; | 
| 291 |  |  |  |  |  |  | # 0=none, 2=separator between header/data, 4=separator between | 
| 292 |  |  |  |  |  |  | # data rows, 8=separator between header rows. this is from | 
| 293 |  |  |  |  |  |  | # BorderStyle standard. | 
| 294 | 53 | 100 |  |  |  | 102 | $cmp==0 ? 2 : $cmp==1 ? 8 : 4; | 
|  |  | 100 |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | }; | 
| 296 | 53 |  | 100 |  |  | 185 | $exptable->[$rownum] //= []; | 
| 297 | 53 |  |  |  |  | 89 | push @{ $exptable->[$rownum] }, undef | 
| 298 | 53 | 50 | 66 |  |  | 56 | if (@{ $exptable->[$rownum] } == 0 || | 
|  | 53 |  |  |  |  | 125 |  | 
| 299 |  |  |  |  |  |  | defined($exptable->[$rownum][-1])); | 
| 300 |  |  |  |  |  |  | #use DDC; say "D:exptable->[$rownum] = ", DDC::dump($exptable->[$rownum]); | 
| 301 | 53 |  |  | 58 |  | 130 | my $exptable_colnum = firstidx {!defined} @{ $exptable->[$rownum] }; | 
|  | 58 |  |  |  |  | 94 |  | 
|  | 53 |  |  |  |  | 127 |  | 
| 302 |  |  |  |  |  |  | #say "D:rownum=$rownum, exptable_colnum=$exptable_colnum"; | 
| 303 | 53 | 50 |  |  |  | 140 | if ($exptable_colnum == -1) { $exptable_colnum = 0 } | 
|  | 0 |  |  |  |  | 0 |  | 
| 304 | 53 | 100 | 66 |  |  | 182 | $exptable_bottom_borders->[$rownum] //= $args{separate_rows} ? $separator_type : 0; | 
| 305 |  |  |  |  |  |  |  | 
| 306 | 53 |  |  |  |  | 78 | for my $cell (@$row) { | 
| 307 | 109 |  |  |  |  | 131 | $colnum++; | 
| 308 | 109 |  |  |  |  | 109 | my $text; | 
| 309 |  |  |  |  |  |  |  | 
| 310 | 109 |  |  |  |  | 115 | my $rowspan = 1; | 
| 311 | 109 |  |  |  |  | 127 | my $colspan = 1; | 
| 312 | 109 | 100 |  |  |  | 191 | if (ref $cell eq 'HASH') { | 
| 313 | 17 |  |  |  |  | 27 | $text = $cell->{text}; | 
| 314 | 17 | 100 |  |  |  | 37 | $rowspan = $cell->{rowspan} if $cell->{rowspan}; | 
| 315 | 17 | 100 |  |  |  | 28 | $colspan = $cell->{colspan} if $cell->{colspan}; | 
| 316 |  |  |  |  |  |  | } else { | 
| 317 | 92 |  |  |  |  | 125 | $text = $cell; | 
| 318 | 92 |  |  |  |  | 87 | my $el; | 
| 319 | 92 | 100 | 100 | 21 |  | 306 | $el = first {$_->[0] == $rownum && $_->[1] == $colnum && $_->[2]{rowspan}} @$cell_attrs; | 
|  | 21 |  |  |  |  | 60 |  | 
| 320 | 92 | 50 |  |  |  | 227 | $rowspan = $el->[2]{rowspan} if $el; | 
| 321 | 92 | 100 | 100 | 21 |  | 230 | $el = first {$_->[0] == $rownum && $_->[1] == $colnum && $_->[2]{colspan}} @$cell_attrs; | 
|  | 21 |  |  |  |  | 55 |  | 
| 322 | 92 | 50 |  |  |  | 190 | $colspan = $el->[2]{colspan} if $el; | 
| 323 |  |  |  |  |  |  | } | 
| 324 |  |  |  |  |  |  |  | 
| 325 | 109 |  |  |  |  | 123 | my @widths; | 
| 326 |  |  |  |  |  |  | my @heights; | 
| 327 |  |  |  |  |  |  | ROW: | 
| 328 | 109 |  |  |  |  | 166 | for my $ir (1..$rowspan) { | 
| 329 | 119 |  |  |  |  | 142 | for my $ic (1..$colspan) { | 
| 330 | 133 |  |  |  |  | 133 | my $exptable_cell; | 
| 331 | 133 |  |  |  |  | 209 | $exptable->[$rownum+$ir-1][$exptable_colnum+$ic-1] = $exptable_cell = []; | 
| 332 |  |  |  |  |  |  |  | 
| 333 | 133 |  |  |  |  | 186 | $exptable_cell->[IDX_EXPTABLE_CELL_ORIG_ROWNUM] = $rownum; | 
| 334 | 133 |  |  |  |  | 184 | $exptable_cell->[IDX_EXPTABLE_CELL_ORIG_COLNUM] = $colnum; | 
| 335 |  |  |  |  |  |  |  | 
| 336 | 133 | 100 | 100 |  |  | 304 | if ($ir == 1 && $ic == 1) { | 
| 337 | 109 |  |  |  |  | 138 | $exptable_cell->[IDX_EXPTABLE_CELL_ROWSPAN]     = $rowspan; | 
| 338 | 109 |  |  |  |  | 116 | $exptable_cell->[IDX_EXPTABLE_CELL_COLSPAN]     = $colspan; | 
| 339 | 109 |  |  |  |  | 167 | $exptable_cell->[IDX_EXPTABLE_CELL_ORIG]        = $cell; | 
| 340 |  |  |  |  |  |  | } else { | 
| 341 | 24 | 100 |  |  |  | 43 | $exptable_cell->[IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL] = 1 if $ir > 1; | 
| 342 | 24 | 100 |  |  |  | 45 | $exptable_cell->[IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL] = 1 if $ic > 1; | 
| 343 |  |  |  |  |  |  | } | 
| 344 |  |  |  |  |  |  | #use DDC; dd $exptable; say ''; # debug | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | # determine whether we should draw bottom border of each row | 
| 348 | 119 | 100 | 100 |  |  | 262 | if ($rownum+$ir-1 == 0 && ($args{header_row}//0) > 0) { | 
|  |  |  | 100 |  |  |  |  | 
| 349 | 32 |  |  |  |  | 39 | $exptable_bottom_borders->[0] = $separator_type; | 
| 350 |  |  |  |  |  |  | } else { | 
| 351 | 87 |  |  |  |  | 87 | my $val; | 
| 352 | 87 | 100 |  |  |  | 150 | $val = _get_attr('bottom_border', $rownum+$ir-1, 0, $cell, \%args);     $exptable_bottom_borders->[$rownum+$ir-1] = $separator_type if $val; | 
|  | 87 |  |  |  |  | 144 |  | 
| 353 | 87 | 50 |  |  |  | 129 | $val = _get_attr('top_border'   , $rownum+$ir-1, 0, $cell, \%args);     $exptable_bottom_borders->[$rownum+$ir-2] = $separator_type if $val; | 
|  | 87 |  |  |  |  | 124 |  | 
| 354 | 87 | 100 |  |  |  | 134 | $val = _get_attr('bottom_border', $rownum+$ir-1, undef, undef, \%args); $exptable_bottom_borders->[$rownum+$ir-1] = $separator_type if $val; | 
|  | 87 |  |  |  |  | 118 |  | 
| 355 | 87 | 50 |  |  |  | 126 | $val = _get_attr('top_border'   , $rownum+$ir-1, undef, undef, \%args); $exptable_bottom_borders->[$rownum+$ir-2] = $separator_type if $val; | 
|  | 87 |  |  |  |  | 133 |  | 
| 356 |  |  |  |  |  |  | } | 
| 357 |  |  |  |  |  |  |  | 
| 358 | 119 | 100 |  |  |  | 209 | $M = $rownum+$ir if $M < $rownum+$ir; | 
| 359 |  |  |  |  |  |  | } | 
| 360 |  |  |  |  |  |  |  | 
| 361 | 109 |  |  |  |  | 111 | $exptable_colnum += $colspan; | 
| 362 | 109 |  |  |  |  | 218 | $exptable_colnum++ while defined $exptable->[$rownum][$exptable_colnum]; | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | } # for a row | 
| 365 | 53 | 100 |  |  |  | 100 | $N = $exptable_colnum if $N < $exptable_colnum; | 
| 366 |  |  |  |  |  |  | } # for rows | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | # 1b. calculate the heigth and width of each exptable cell (as required | 
| 369 |  |  |  |  |  |  | # by the text, or specified width/height when we allow cell attrs width, | 
| 370 |  |  |  |  |  |  | # height) | 
| 371 |  |  |  |  |  |  |  | 
| 372 | 18 |  |  |  |  | 32 | for my $exptable_rownum (0..$M-1) { | 
| 373 | 53 |  |  |  |  | 68 | for my $exptable_colnum (0..$N-1) { | 
| 374 | 133 |  |  |  |  | 155 | my $exptable_cell = $exptable->[$exptable_rownum][$exptable_colnum]; | 
| 375 | 133 | 100 |  |  |  | 184 | next if _exptable_cell_is_tail($exptable_cell); | 
| 376 | 109 |  |  |  |  | 148 | my $rowspan = $exptable_cell->[IDX_EXPTABLE_CELL_ROWSPAN]; | 
| 377 | 109 |  |  |  |  | 124 | my $colspan = $exptable_cell->[IDX_EXPTABLE_CELL_COLSPAN]; | 
| 378 | 109 |  |  |  |  | 119 | my $cell = $exptable_cell->[IDX_EXPTABLE_CELL_ORIG]; | 
| 379 | 109 | 100 |  |  |  | 170 | my $text = ref $cell eq 'HASH' ? $cell->{text} : $cell; | 
| 380 | 109 |  |  |  |  | 169 | my $lh = $_length_height_func->($text); | 
| 381 |  |  |  |  |  |  | #use DDC; say "D:length_height[$exptable_rownum,$exptable_colnum] = (".DDC::dump($text)."): ".DDC::dump($lh); | 
| 382 | 109 |  |  |  |  | 1720 | my $tot_intercol_widths = ($colspan-1) * $intercol_width; | 
| 383 | 109 | 50 |  |  |  | 128 | my $tot_interrow_heights = 0; for (1..$rowspan-1) { $tot_interrow_heights++ if $exptable_bottom_borders->[$exptable_rownum+$_-1] } | 
|  | 109 |  |  |  |  | 159 |  | 
|  | 10 |  |  |  |  | 22 |  | 
| 384 |  |  |  |  |  |  | #say "D:interrow_heights=$tot_interrow_heights"; | 
| 385 | 109 |  |  |  |  | 212 | my @heights = _divide_int_to_n_ints(max(0, $lh->[1] - $tot_interrow_heights), $rowspan); | 
| 386 | 109 |  |  |  |  | 207 | my @widths  = _divide_int_to_n_ints(max(0, $lh->[0] - $tot_intercol_widths ), $colspan); | 
| 387 | 109 |  |  |  |  | 152 | for my $ir (1..$rowspan) { | 
| 388 | 119 |  |  |  |  | 136 | for my $ic (1..$colspan) { | 
| 389 | 133 |  |  |  |  | 210 | $exptable->[$exptable_rownum+$ir-1][$exptable_colnum+$ic-1][IDX_EXPTABLE_CELL_HEIGHT]  = $heights[$ir-1]; | 
| 390 | 133 |  |  |  |  | 300 | $exptable->[$exptable_rownum+$ir-1][$exptable_colnum+$ic-1][IDX_EXPTABLE_CELL_WIDTH]   = $widths [$ic-1]; | 
| 391 |  |  |  |  |  |  | } | 
| 392 |  |  |  |  |  |  | } | 
| 393 |  |  |  |  |  |  | } | 
| 394 |  |  |  |  |  |  | } # for rows | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | } # CONSTRUCT_EXPTABLE | 
| 397 |  |  |  |  |  |  | #use DDC; dd $exptable; # debug | 
| 398 |  |  |  |  |  |  | #print "D: exptable size: $M x $N (HxW)\n"; # debug | 
| 399 |  |  |  |  |  |  | #use DDC; print "bottom borders: "; dd $exptable_bottom_borders; # debug | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | OPTIMIZE_EXPTABLE: { | 
| 402 |  |  |  |  |  |  | # TODO | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | # 2. we reduce extraneous columns and rows if there are colspan that are | 
| 405 |  |  |  |  |  |  | # too many. for example, if all exptable cells in column 1 has colspan=2 | 
| 406 |  |  |  |  |  |  | # (or one row has colspan=2 and another row has colspan=3), we might as | 
| 407 |  |  |  |  |  |  | # remove 1 column because the extra column span doesn't have any | 
| 408 |  |  |  |  |  |  | # content. same case for extraneous row spans. | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | # 2a. remove extra undefs. skip this. doesn't make a difference. | 
| 411 |  |  |  |  |  |  | #for my $exptable_row (@{ $exptable }) { | 
| 412 |  |  |  |  |  |  | #    splice @$exptable_row, $N if @$exptable_row > $N; | 
| 413 |  |  |  |  |  |  | #} | 
| 414 |  |  |  |  |  |  |  | 
| 415 | 18 |  |  |  |  | 19 | 1; | 
|  | 18 |  |  |  |  | 19 |  | 
| 416 |  |  |  |  |  |  | } # OPTIMIZE_EXPTABLE | 
| 417 |  |  |  |  |  |  | #use DDC; dd $exptable; # debug | 
| 418 |  |  |  |  |  |  |  | 
| 419 | 18 |  |  |  |  | 25 | my $exptable_column_widths  = []; # idx=exptable colnum | 
| 420 | 18 |  |  |  |  | 21 | my $exptable_row_heights    = []; # idx=exptable rownum | 
| 421 |  |  |  |  |  |  | DETERMINE_SIZE_OF_EACH_EXPTABLE_COLUMN_AND_ROW: { | 
| 422 |  |  |  |  |  |  | # 3. before we draw the exptable, we need to determine the width and | 
| 423 |  |  |  |  |  |  | # height of each exptable column and row. | 
| 424 |  |  |  |  |  |  | #use DDC; | 
| 425 | 18 |  |  |  |  | 21 | for my $ir (0..$M-1) { | 
|  | 18 |  |  |  |  | 21 |  | 
| 426 | 53 |  |  |  |  | 67 | my $exptable_row = $exptable->[$ir]; | 
| 427 |  |  |  |  |  |  | $exptable_row_heights->[$ir] = max( | 
| 428 | 53 |  | 100 |  |  | 69 | 1, map {$_->[IDX_EXPTABLE_CELL_HEIGHT] // 0} @$exptable_row); | 
|  | 139 |  |  |  |  | 288 |  | 
| 429 |  |  |  |  |  |  | } | 
| 430 |  |  |  |  |  |  |  | 
| 431 | 18 |  |  |  |  | 29 | for my $ic (0..$N-1) { | 
| 432 |  |  |  |  |  |  | $exptable_column_widths->[$ic] = max( | 
| 433 | 40 | 50 |  |  |  | 55 | 1, map {$exptable->[$_][$ic] ? $exptable->[$_][$ic][IDX_EXPTABLE_CELL_WIDTH] : 0} 0..$M-1); | 
|  | 133 |  |  |  |  | 247 |  | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  | } # DETERMINE_SIZE_OF_EACH_EXPTABLE_COLUMN_AND_ROW | 
| 436 |  |  |  |  |  |  | #use DDC; print "column widths: "; dd $exptable_column_widths; # debug | 
| 437 |  |  |  |  |  |  | #use DDC; print "row heights: "; dd $exptable_row_heights; # debug | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | # each elem is an arrayref containing characters to render a line of the | 
| 440 |  |  |  |  |  |  | # table, e.g. for element [0] the row is all borders. for element [1]: | 
| 441 |  |  |  |  |  |  | # [$left_border_str, $exptable_cell_content1, $border_between_col, | 
| 442 |  |  |  |  |  |  | # $exptable_cell_content2, ...]. all will be joined together with "\n" to | 
| 443 |  |  |  |  |  |  | # form the final rendered table. | 
| 444 | 18 |  |  |  |  | 21 | my @buf; | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  | DRAW_EXPTABLE: { | 
| 447 |  |  |  |  |  |  | # 4. finally we draw the (exp)table. | 
| 448 |  |  |  |  |  |  |  | 
| 449 | 18 |  |  |  |  | 20 | my $y = 0; | 
|  | 18 |  |  |  |  | 21 |  | 
| 450 |  |  |  |  |  |  |  | 
| 451 | 18 |  |  |  |  | 26 | for my $ir (0..$M-1) { | 
| 452 |  |  |  |  |  |  | DRAW_TOP_BORDER: | 
| 453 |  |  |  |  |  |  | { | 
| 454 | 53 | 100 |  |  |  | 75 | last unless $ir == 0; | 
| 455 | 17 | 100 | 100 |  |  | 41 | my $b_y = ($args{header_row}//0) > 0 ? 0 : 6; | 
| 456 | 17 |  |  |  |  | 44 | my $b_topleft    = $bs_obj->get_border_char($b_y, 0); | 
| 457 | 17 |  |  |  |  | 361 | my $b_topline    = $bs_obj->get_border_char($b_y, 1); | 
| 458 | 17 |  |  |  |  | 325 | my $b_topbetwcol = $bs_obj->get_border_char($b_y, 2); | 
| 459 | 17 |  |  |  |  | 291 | my $b_topright   = $bs_obj->get_border_char($b_y, 3); | 
| 460 | 17 | 0 | 33 |  |  | 295 | last unless length $b_topleft || length $b_topline || length $b_topbetwcol || length $b_topright; | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 461 | 17 |  |  |  |  | 35 | $buf[$y][0] = $b_topleft; | 
| 462 | 17 |  |  |  |  | 28 | for my $ic (0..$N-1) { | 
| 463 | 40 | 100 |  |  |  | 70 | my $cell_right = $ic < $N-1 ? $exptable->[$ir][$ic+1] : undef; | 
| 464 | 40 |  | 100 |  |  | 77 | my $cell_right_has_content = defined $cell_right && _exptable_cell_is_head($cell_right); | 
| 465 | 40 |  |  |  |  | 77 | $buf[$y][$ic*4+2] = $bs_obj->get_border_char($b_y, 1, $exptable_column_widths->[$ic]+2); # +1, +2, +3 | 
| 466 | 40 | 100 |  |  |  | 789 | $buf[$y][$ic*4+4] = $ic == $N-1 ? $b_topright : ($cell_right_has_content ? $b_topbetwcol : $b_topline); | 
|  |  | 100 |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | } | 
| 468 | 17 |  |  |  |  | 25 | $y++; | 
| 469 |  |  |  |  |  |  | } # DRAW_TOP_BORDER | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | # DRAW_DATA_OR_HEADER_ROW | 
| 472 |  |  |  |  |  |  | { | 
| 473 |  |  |  |  |  |  | # draw leftmost border, which we always do. | 
| 474 | 53 | 100 | 100 |  |  | 56 | my $b_y = $ir == 0 && $args{header_row} ? 1 : 3; | 
|  | 53 |  |  |  |  | 55 |  | 
|  | 53 |  |  |  |  | 117 |  | 
| 475 | 53 |  |  |  |  | 93 | for my $i (1 .. $exptable_row_heights->[$ir]) { | 
| 476 | 62 |  |  |  |  | 254 | $buf[$y+$i-1][0] = $bs_obj->get_border_char($b_y, 0); | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  |  | 
| 479 | 53 |  |  |  |  | 947 | my $lines; | 
| 480 | 53 |  |  |  |  | 79 | for my $ic (0..$N-1) { | 
| 481 | 133 |  |  |  |  | 155 | my $cell = $exptable->[$ir][$ic]; | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | # draw cell content. also possibly draw border between | 
| 484 |  |  |  |  |  |  | # cells. we don't draw border inside a row/colspan. | 
| 485 | 133 | 100 |  |  |  | 209 | if (_exptable_cell_is_head($cell)) { | 
| 486 | 109 |  |  |  |  | 172 | $lines = _get_exptable_cell_lines( | 
| 487 |  |  |  |  |  |  | \%args, $exptable, $exptable_row_heights, $exptable_column_widths, | 
| 488 |  |  |  |  |  |  | $exptable_bottom_borders, $intercol_width, $ir, $ic); | 
| 489 | 109 |  |  |  |  | 156 | for my $i (0..$#{$lines}) { | 
|  | 109 |  |  |  |  | 182 |  | 
| 490 | 153 |  |  |  |  | 283 | $buf[$y+$i][$ic*4+0] = $bs_obj->get_border_char($b_y, 1); | 
| 491 | 153 |  |  |  |  | 2813 | $buf[$y+$i][$ic*4+1] = " "; | 
| 492 | 153 |  |  |  |  | 244 | $buf[$y+$i][$ic*4+2] = $lines->[$i]; | 
| 493 | 153 |  |  |  |  | 266 | $buf[$y+$i][$ic*4+3] = " "; | 
| 494 |  |  |  |  |  |  | } | 
| 495 |  |  |  |  |  |  | #use DDC; say "D: Drawing exptable_cell($ir,$ic): ", DDC::dump($lines); | 
| 496 |  |  |  |  |  |  | } | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | # draw rightmost border, which we always do. | 
| 499 | 133 | 100 |  |  |  | 280 | if ($ic == $N-1) { | 
| 500 | 53 | 100 | 100 |  |  | 128 | my $b_y = $ir == 0 && $args{header_row} ? 1 : 3; | 
| 501 | 53 |  |  |  |  | 84 | for my $i (1 .. $exptable_row_heights->[$ir]) { | 
| 502 | 62 |  |  |  |  | 266 | $buf[$y+$i-1][$ic*4+4] = $bs_obj->get_border_char($b_y, 2); | 
| 503 |  |  |  |  |  |  | } | 
| 504 |  |  |  |  |  |  | } | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | } | 
| 507 |  |  |  |  |  |  | } # DRAW_DATA_OR_HEADER_ROW | 
| 508 | 53 |  |  |  |  | 974 | $y += $exptable_row_heights->[$ir]; | 
| 509 |  |  |  |  |  |  |  | 
| 510 |  |  |  |  |  |  | DRAW_ROW_SEPARATOR: | 
| 511 |  |  |  |  |  |  | { | 
| 512 | 53 | 100 |  |  |  | 60 | last unless $ir < $M-1; | 
|  | 53 |  |  |  |  | 104 |  | 
| 513 | 36 | 100 |  |  |  | 60 | last unless $exptable_bottom_borders->[$ir]; | 
| 514 | 31 |  |  |  |  | 46 | my $b_y = $exptable_bottom_borders->[$ir]; | 
| 515 | 31 |  |  |  |  | 47 | my $b_betwrowleft    = $bs_obj->get_border_char($b_y, 0); | 
| 516 | 31 |  |  |  |  | 524 | my $b_betwrowline    = $bs_obj->get_border_char($b_y, 1); | 
| 517 | 31 |  |  |  |  | 504 | my $b_betwrowbetwcol = $bs_obj->get_border_char($b_y, 2); | 
| 518 | 31 |  |  |  |  | 502 | my $b_betwrowright   = $bs_obj->get_border_char($b_y, 3); | 
| 519 | 31 | 0 | 33 |  |  | 515 | last unless length $b_betwrowleft || length $b_betwrowline || length $b_betwrowbetwcol || length $b_betwrowright; | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 520 | 31 |  |  |  |  | 47 | my $b_betwrowbetwcol_notop = $bs_obj->get_border_char($b_y, 4); | 
| 521 | 31 |  |  |  |  | 514 | my $b_betwrowbetwcol_nobot = $bs_obj->get_border_char($b_y, 5); | 
| 522 | 31 |  |  |  |  | 503 | my $b_betwrowbetwcol_noleft  = $bs_obj->get_border_char($b_y, 6); | 
| 523 | 31 |  |  |  |  | 499 | my $b_betwrowbetwcol_noright = $bs_obj->get_border_char($b_y, 7); | 
| 524 | 31 | 100 |  |  |  | 519 | my $b_ydataorheader = $args{header_row} == $ir+1 ? 2 : $args{header_row} < $ir+1 ? 3 : 1; | 
|  |  | 100 |  |  |  |  |  | 
| 525 | 31 |  |  |  |  | 52 | my $b_dataorheaderrowleft    = $bs_obj->get_border_char($b_ydataorheader, 0, 1); | 
| 526 | 31 |  |  |  |  | 501 | my $b_dataorheaderrowbetwcol = $bs_obj->get_border_char($b_ydataorheader, 1, 1); | 
| 527 | 31 |  |  |  |  | 523 | my $b_dataorheaderrowright   = $bs_obj->get_border_char($b_ydataorheader, 2, 1); | 
| 528 | 31 |  |  |  |  | 504 | for my $ic (0..$N-1) { | 
| 529 | 82 | 100 |  |  |  | 147 | my $cell_right       = $ic < $N-1 ? $exptable->[$ir][$ic+1] : undef; | 
| 530 | 82 | 50 |  |  |  | 144 | my $cell_bottom      = $ir < $M-1 ? $exptable->[$ir+1][$ic] : undef; | 
| 531 | 82 | 100 | 66 |  |  | 252 | my $cell_rightbottom = $ir < $M-1 && $ic < $N-1 ? $exptable->[$ir+1][$ic+1] : undef; | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | # leftmost border | 
| 534 | 82 | 100 |  |  |  | 127 | if ($ic == 0) { | 
| 535 | 31 | 100 |  |  |  | 52 | $buf[$y][0] = _exptable_cell_is_rowspan_tail($cell_bottom) ? $b_dataorheaderrowleft : $b_betwrowleft; | 
| 536 |  |  |  |  |  |  | } | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | # along the width of cell content | 
| 539 | 82 | 100 |  |  |  | 118 | if (_exptable_cell_is_rowspan_head($cell_bottom)) { | 
| 540 | 67 |  |  |  |  | 132 | $buf[$y][$ic*4+2] = $bs_obj->get_border_char($b_y, 1, $exptable_column_widths->[$ic]+2); | 
| 541 |  |  |  |  |  |  | } | 
| 542 |  |  |  |  |  |  |  | 
| 543 | 82 |  |  |  |  | 1225 | my $char; | 
| 544 | 82 | 100 |  |  |  | 123 | if ($ic == $N-1) { | 
| 545 |  |  |  |  |  |  | # rightmost | 
| 546 | 31 | 100 |  |  |  | 44 | if (_exptable_cell_is_rowspan_tail($cell_bottom)) { | 
| 547 | 6 |  |  |  |  | 8 | $char = $b_dataorheaderrowright; | 
| 548 |  |  |  |  |  |  | } else { | 
| 549 | 25 |  |  |  |  | 36 | $char = $b_betwrowright; | 
| 550 |  |  |  |  |  |  | } | 
| 551 |  |  |  |  |  |  | } else { | 
| 552 |  |  |  |  |  |  | # between cells | 
| 553 | 51 | 100 |  |  |  | 72 | if (_exptable_cell_is_colspan_tail($cell_right)) { | 
| 554 | 10 | 100 |  |  |  | 12 | if (_exptable_cell_is_colspan_tail($cell_rightbottom)) { | 
| 555 | 5 | 50 |  |  |  | 7 | if (_exptable_cell_is_rowspan_tail($cell_bottom)) { | 
| 556 | 5 |  |  |  |  | 7 | $char = ""; | 
| 557 |  |  |  |  |  |  | } else { | 
| 558 | 0 |  |  |  |  | 0 | $char = $b_betwrowline; | 
| 559 |  |  |  |  |  |  | } | 
| 560 |  |  |  |  |  |  | } else { | 
| 561 | 5 |  |  |  |  | 7 | $char = $b_betwrowbetwcol_notop; | 
| 562 |  |  |  |  |  |  | } | 
| 563 |  |  |  |  |  |  | } else { | 
| 564 | 41 | 100 |  |  |  | 60 | if (_exptable_cell_is_colspan_tail($cell_rightbottom)) { | 
| 565 | 7 |  |  |  |  | 11 | $char = $b_betwrowbetwcol_nobot; | 
| 566 |  |  |  |  |  |  | } else { | 
| 567 | 34 | 100 |  |  |  | 46 | if (_exptable_cell_is_rowspan_tail($cell_bottom)) { | 
|  |  | 100 |  |  |  |  |  | 
| 568 | 4 | 50 |  |  |  | 6 | if (_exptable_cell_is_rowspan_tail($cell_rightbottom)) { | 
| 569 | 0 |  |  |  |  | 0 | $char = $b_dataorheaderrowbetwcol; | 
| 570 |  |  |  |  |  |  | } else { | 
| 571 | 4 |  |  |  |  | 5 | $char = $b_betwrowbetwcol_noleft; | 
| 572 |  |  |  |  |  |  | } | 
| 573 |  |  |  |  |  |  | } elsif (_exptable_cell_is_rowspan_tail($cell_rightbottom)) { | 
| 574 | 6 |  |  |  |  | 7 | $char = $b_betwrowbetwcol_noright; | 
| 575 |  |  |  |  |  |  | } else { | 
| 576 | 24 |  |  |  |  | 35 | $char = $b_betwrowbetwcol; | 
| 577 |  |  |  |  |  |  | } | 
| 578 |  |  |  |  |  |  | } | 
| 579 |  |  |  |  |  |  | } | 
| 580 |  |  |  |  |  |  | } | 
| 581 | 82 |  |  |  |  | 188 | $buf[$y][$ic*4+4] = $char; | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | } | 
| 584 | 31 |  |  |  |  | 43 | $y++; | 
| 585 |  |  |  |  |  |  | } # DRAW_ROW_SEPARATOR | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | DRAW_BOTTOM_BORDER: | 
| 588 |  |  |  |  |  |  | { | 
| 589 | 53 | 100 |  |  |  | 54 | last unless $ir == $M-1; | 
|  | 53 |  |  |  |  | 98 |  | 
| 590 | 17 | 50 | 66 |  |  | 39 | my $b_y = $ir == 0 && $args{header_row} ? 7 : 5; | 
| 591 | 17 |  |  |  |  | 32 | my $b_botleft    = $bs_obj->get_border_char($b_y, 0); | 
| 592 | 17 |  |  |  |  | 286 | my $b_botline    = $bs_obj->get_border_char($b_y, 1); | 
| 593 | 17 |  |  |  |  | 278 | my $b_botbetwcol = $bs_obj->get_border_char($b_y, 2); | 
| 594 | 17 |  |  |  |  | 275 | my $b_botright   = $bs_obj->get_border_char($b_y, 3); | 
| 595 | 17 | 0 | 33 |  |  | 280 | last unless length $b_botleft || length $b_botline || length $b_botbetwcol || length $b_botright; | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 596 | 17 |  |  |  |  | 34 | $buf[$y][0] = $b_botleft; | 
| 597 | 17 |  |  |  |  | 42 | for my $ic (0..$N-1) { | 
| 598 | 40 | 100 |  |  |  | 72 | my $cell_right = $ic < $N-1 ? $exptable->[$ir][$ic+1] : undef; | 
| 599 | 40 |  |  |  |  | 78 | $buf[$y][$ic*4+2] = $bs_obj->get_border_char($b_y, 1, $exptable_column_widths->[$ic]+2); | 
| 600 | 40 | 100 |  |  |  | 732 | $buf[$y][$ic*4+4] = $ic == $N-1 ? $b_botright : (_exptable_cell_is_colspan_tail($cell_right) ? $b_botline : $b_botbetwcol); | 
|  |  | 100 |  |  |  |  |  | 
| 601 |  |  |  |  |  |  | } | 
| 602 | 17 |  |  |  |  | 35 | $y++; | 
| 603 |  |  |  |  |  |  | } # DRAW_BOTTOM_BORDER | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | } | 
| 606 |  |  |  |  |  |  | } # DRAW_EXPTABLE | 
| 607 |  |  |  |  |  |  |  | 
| 608 | 18 | 100 |  |  |  | 24 | for my $row (@buf) { for (@$row) { $_ = "" if !defined($_) } } # debug. remove undef to "" to save dump width | 
|  | 127 |  |  |  |  | 148 |  | 
|  | 1411 |  |  |  |  | 2156 |  | 
| 609 |  |  |  |  |  |  | #use DDC; dd \@buf; | 
| 610 | 18 |  |  |  |  | 32 | join "", (map { my $linebuf = $_; join("", grep {defined} @$linebuf)."\n" } @buf); | 
|  | 127 |  |  |  |  | 133 |  | 
|  | 127 |  |  |  |  | 147 |  | 
|  | 1411 |  |  |  |  | 2031 |  | 
| 611 |  |  |  |  |  |  | } | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | # Back-compat: 'table' is an alias for 'generate_table', but isn't exported | 
| 614 |  |  |  |  |  |  | { | 
| 615 | 1 |  |  | 1 |  | 7 | no warnings 'once'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 60 |  | 
| 616 |  |  |  |  |  |  | *table = \&generate_table; | 
| 617 |  |  |  |  |  |  | } | 
| 618 |  |  |  |  |  |  |  | 
| 619 |  |  |  |  |  |  | 1; | 
| 620 |  |  |  |  |  |  | # ABSTRACT: Generate text table with simple interface and many options | 
| 621 |  |  |  |  |  |  |  | 
| 622 |  |  |  |  |  |  | __END__ |