File Coverage

blib/lib/Text/Table/More.pm
Criterion Covered Total %
statement 330 340 97.0
branch 169 198 85.3
condition 62 90 68.8
subroutine 21 22 95.4
pod 1 1 100.0
total 583 651 89.5


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__