line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Text::Table::Span; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY |
4
|
|
|
|
|
|
|
our $DATE = '2021-02-19'; # DATE |
5
|
|
|
|
|
|
|
our $DIST = 'Text-Table-Span'; # DIST |
6
|
|
|
|
|
|
|
our $VERSION = '0.007'; # VERSION |
7
|
|
|
|
|
|
|
|
8
|
1
|
|
|
1
|
|
77311
|
use 5.010001; |
|
1
|
|
|
|
|
15
|
|
9
|
1
|
|
|
1
|
|
6
|
use strict; |
|
1
|
|
|
|
|
8
|
|
|
1
|
|
|
|
|
25
|
|
10
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
47
|
|
11
|
|
|
|
|
|
|
|
12
|
1
|
|
|
1
|
|
693
|
use List::AllUtils qw(first firstidx max); |
|
1
|
|
|
|
|
20138
|
|
|
1
|
|
|
|
|
101
|
|
13
|
1
|
|
|
1
|
|
715
|
use String::Pad qw(pad); |
|
1
|
|
|
|
|
405
|
|
|
1
|
|
|
|
|
59
|
|
14
|
|
|
|
|
|
|
|
15
|
1
|
|
|
1
|
|
8
|
use Exporter qw(import); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
3665
|
|
16
|
|
|
|
|
|
|
our @EXPORT_OK = qw/ generate_table /; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# consts |
19
|
|
|
|
|
|
|
sub IDX_EXPTABLE_CELL_ROWSPAN() {0} # number of rowspan, only defined for the rowspan head |
20
|
|
|
|
|
|
|
sub IDX_EXPTABLE_CELL_COLSPAN() {1} # number of colspan, only defined for the colspan head |
21
|
|
|
|
|
|
|
sub IDX_EXPTABLE_CELL_WIDTH() {2} # visual width. this does not include the cell padding. |
22
|
|
|
|
|
|
|
sub IDX_EXPTABLE_CELL_HEIGHT() {3} # visual height. this does not include row separator. |
23
|
|
|
|
|
|
|
sub IDX_EXPTABLE_CELL_ORIG() {4} # str/hash |
24
|
|
|
|
|
|
|
sub IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL() {5} # whether this cell is tail of a rowspan |
25
|
|
|
|
|
|
|
sub IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL() {6} # whether this cell is tail of a colspan |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# whether an exptable cell is the head (1st cell) or tail (the rest) of a |
28
|
|
|
|
|
|
|
# rowspan/colspan. these should be macros if possible, for speed. |
29
|
15
|
50
|
|
15
|
|
49
|
sub _exptable_cell_is_rowspan_tail { defined($_[0]) && $_[0][IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL] } |
30
|
14
|
50
|
|
14
|
|
43
|
sub _exptable_cell_is_colspan_tail { defined($_[0]) && $_[0][IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL] } |
31
|
12
|
50
|
66
|
12
|
|
45
|
sub _exptable_cell_is_tail { defined($_[0]) && ($_[0][IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL] || $_[0][IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL]) } |
32
|
9
|
50
|
|
9
|
|
33
|
sub _exptable_cell_is_rowspan_head { defined($_[0]) && !$_[0][IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL] } |
33
|
0
|
0
|
|
0
|
|
0
|
sub _exptable_cell_is_colspan_head { defined($_[0]) && !$_[0][IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL] } |
34
|
14
|
50
|
|
14
|
|
63
|
sub _exptable_cell_is_head { defined($_[0]) && defined $_[0][IDX_EXPTABLE_CELL_ORIG] } |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub _divide_int_to_n_ints { |
37
|
18
|
|
|
18
|
|
29
|
my ($int, $n) = @_; |
38
|
18
|
|
|
|
|
23
|
my $subtot = 0; |
39
|
18
|
|
|
|
|
30
|
my $int_subtot = 0; |
40
|
18
|
|
|
|
|
24
|
my $prev_int_subtot = 0; |
41
|
18
|
|
|
|
|
22
|
my @ints; |
42
|
18
|
|
|
|
|
29
|
for (1..$n) { |
43
|
20
|
|
|
|
|
47
|
$subtot += $int/$n; |
44
|
20
|
|
|
|
|
39
|
$int_subtot = sprintf "%.0f", $subtot; |
45
|
20
|
|
|
|
|
30
|
push @ints, $int_subtot - $prev_int_subtot; |
46
|
20
|
|
|
|
|
34
|
$prev_int_subtot = $int_subtot; |
47
|
|
|
|
|
|
|
} |
48
|
18
|
|
|
|
|
33
|
@ints; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub _get_attr { |
52
|
29
|
|
|
29
|
|
55
|
my ($attr_name, $y, $x, $cell_value, $table_args) = @_; |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
CELL_ATTRS_FROM_CELL_VALUE: { |
55
|
29
|
100
|
|
|
|
35
|
last unless ref $cell_value eq 'HASH'; |
|
29
|
|
|
|
|
63
|
|
56
|
2
|
|
|
|
|
5
|
my $attr_val = $cell_value->{$attr_name}; |
57
|
2
|
100
|
|
|
|
7
|
return $attr_val if defined $attr_val; |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
CELL_ATTRS_FROM_CELL_ATTRS_ARG: |
61
|
|
|
|
|
|
|
{ |
62
|
28
|
100
|
66
|
|
|
39
|
last unless defined $x && defined $y; |
|
28
|
|
|
|
|
60
|
|
63
|
8
|
|
|
|
|
13
|
my $cell_attrs = $table_args->{cell_attrs}; |
64
|
8
|
50
|
|
|
|
17
|
last unless $cell_attrs; |
65
|
0
|
|
|
|
|
0
|
for my $entry (@$cell_attrs) { |
66
|
0
|
0
|
0
|
|
|
0
|
next unless $entry->[0] == $y && $entry->[1] == $x; |
67
|
0
|
|
|
|
|
0
|
my $attr_val = $entry->[2]{$attr_name}; |
68
|
0
|
0
|
|
|
|
0
|
return $attr_val if defined $attr_val; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
COL_ATTRS: |
73
|
|
|
|
|
|
|
{ |
74
|
28
|
100
|
|
|
|
34
|
last unless defined $x; |
|
28
|
|
|
|
|
45
|
|
75
|
8
|
|
|
|
|
11
|
my $col_attrs = $table_args->{col_attrs}; |
76
|
8
|
50
|
|
|
|
16
|
last unless $col_attrs; |
77
|
8
|
|
|
|
|
14
|
for my $entry (@$col_attrs) { |
78
|
8
|
100
|
|
|
|
29
|
next unless $entry->[0] == $x; |
79
|
3
|
|
|
|
|
7
|
my $attr_val = $entry->[1]{$attr_name}; |
80
|
3
|
50
|
|
|
|
10
|
return $attr_val if defined $attr_val; |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
ROW_ATTRS: |
85
|
|
|
|
|
|
|
{ |
86
|
25
|
50
|
|
|
|
34
|
last unless defined $y; |
|
25
|
|
|
|
|
37
|
|
87
|
25
|
|
|
|
|
35
|
my $row_attrs = $table_args->{row_attrs}; |
88
|
25
|
50
|
|
|
|
45
|
last unless $row_attrs; |
89
|
25
|
|
|
|
|
34
|
for my $entry (@$row_attrs) { |
90
|
25
|
100
|
|
|
|
49
|
next unless $entry->[0] == $y; |
91
|
8
|
|
|
|
|
13
|
my $attr_val = $entry->[1]{$attr_name}; |
92
|
8
|
100
|
|
|
|
21
|
return $attr_val if defined $attr_val; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
TABLE_ARGS: |
97
|
|
|
|
|
|
|
{ |
98
|
23
|
|
|
|
|
26
|
my $attr_val = $table_args->{$attr_name}; |
|
23
|
|
|
|
|
38
|
|
99
|
23
|
100
|
|
|
|
41
|
return $attr_val if defined $attr_val; |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
20
|
|
|
|
|
32
|
undef; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub _get_exptable_cell_lines { |
106
|
9
|
|
|
9
|
|
17
|
my ($table_args, $exptable, $row_heights, $column_widths, |
107
|
|
|
|
|
|
|
$bottom_borders, $intercol_width, $y, $x) = @_; |
108
|
|
|
|
|
|
|
|
109
|
9
|
|
|
|
|
16
|
my $exptable_cell = $exptable->[$y][$x]; |
110
|
9
|
|
|
|
|
13
|
my $cell = $exptable_cell->[IDX_EXPTABLE_CELL_ORIG]; |
111
|
9
|
100
|
|
|
|
28
|
my $text = ref $cell eq 'HASH' ? $cell->{text} : $cell; |
112
|
9
|
|
50
|
|
|
21
|
my $align = _get_attr('align', $y, $x, $cell, $table_args) // 'left'; |
113
|
9
|
100
|
|
|
|
24
|
my $pad = $align eq 'left' ? 'r' : $align eq 'right' ? 'l' : 'c'; |
|
|
100
|
|
|
|
|
|
114
|
9
|
|
|
|
|
11
|
my $height = 0; |
115
|
9
|
|
|
|
|
15
|
my $width = 0; |
116
|
9
|
|
|
|
|
15
|
for my $ic (1..$exptable_cell->[IDX_EXPTABLE_CELL_COLSPAN]) { |
117
|
10
|
|
|
|
|
18
|
$width += $column_widths->[$x+$ic-1]; |
118
|
10
|
100
|
|
|
|
21
|
$width += $intercol_width if $ic > 1; |
119
|
|
|
|
|
|
|
} |
120
|
9
|
|
|
|
|
15
|
for my $ir (1..$exptable_cell->[IDX_EXPTABLE_CELL_ROWSPAN]) { |
121
|
10
|
|
|
|
|
18
|
$height += $row_heights->[$y+$ir-1]; |
122
|
10
|
100
|
66
|
|
|
33
|
$height++ if $bottom_borders->[$y+$ir-2] && $ir > 1; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
9
|
|
|
|
|
13
|
my @lines; |
126
|
9
|
|
|
|
|
32
|
my @datalines = split /\R/, $text; |
127
|
9
|
|
|
|
|
19
|
for (1..@datalines) { |
128
|
12
|
|
|
|
|
70
|
push @lines, pad($datalines[$_-1], $width, $pad, ' ', 'truncate'); |
129
|
|
|
|
|
|
|
} |
130
|
9
|
|
|
|
|
160
|
for (@datalines+1 .. $height) { |
131
|
2
|
|
|
|
|
8
|
push @lines, " " x $width; |
132
|
|
|
|
|
|
|
} |
133
|
9
|
|
|
|
|
25
|
\@lines; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub generate_table { |
137
|
1
|
|
|
1
|
1
|
2390
|
require Module::Load::Util; |
138
|
1
|
|
|
|
|
2312
|
require Text::NonWideChar::Util; |
139
|
|
|
|
|
|
|
|
140
|
1
|
|
|
|
|
275
|
my %args = @_; |
141
|
1
|
50
|
|
|
|
8
|
my $rows = $args{rows} or die "Please specify rows"; |
142
|
1
|
|
50
|
|
|
9
|
my $bs_name = $args{border_style} // 'ASCII::SingleLineDoubleAfterHeader'; |
143
|
1
|
|
50
|
|
|
7
|
my $cell_attrs = $args{cell_attrs} // []; |
144
|
|
|
|
|
|
|
|
145
|
1
|
|
|
|
|
7
|
my $bs_obj = Module::Load::Util::instantiate_class_with_optional_args({ns_prefix=>"BorderStyle"}, $bs_name); |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# XXX when we allow cell attrs right_border and left_border, this will |
148
|
|
|
|
|
|
|
# become array too like $exptable_bottom_borders. |
149
|
1
|
|
|
|
|
3751
|
my $intercol_width = length(" " . $bs_obj->get_border_char(3, 1) . " "); |
150
|
|
|
|
|
|
|
|
151
|
1
|
|
|
|
|
63
|
my $exptable = []; # [ [[$orig_rowidx,$orig_colidx,$rowspan,$colspan,...], ...], [[...], ...], ... ] |
152
|
1
|
|
|
|
|
4
|
my $exptable_bottom_borders = []; # idx=exptable rownum, val=bool |
153
|
1
|
|
|
|
|
2
|
my $M = 0; # number of rows in the exptable |
154
|
1
|
|
|
|
|
2
|
my $N = 0; # number of columns in the exptable |
155
|
|
|
|
|
|
|
CONSTRUCT_EXPTABLE: { |
156
|
|
|
|
|
|
|
# 1. the first step is to construct a 2D array we call "exptable" (short |
157
|
|
|
|
|
|
|
# for expanded table), which is like the original table but with all the |
158
|
|
|
|
|
|
|
# spanning rows/columns split into the smaller boxes so it's easier to |
159
|
|
|
|
|
|
|
# draw later. for example, a table cell with colspan=2 will become 2 |
160
|
|
|
|
|
|
|
# exptable cells. an m-row x n-column table will become M-row x N-column |
161
|
|
|
|
|
|
|
# exptable, where M>=m, N>=n. |
162
|
|
|
|
|
|
|
|
163
|
1
|
|
|
|
|
2
|
my $rownum; |
|
1
|
|
|
|
|
3
|
|
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# 1a. first substep: construct exptable and calculate everything except |
166
|
|
|
|
|
|
|
# each exptable cell's width and height, because this will require |
167
|
|
|
|
|
|
|
# information from the previous substeps. |
168
|
|
|
|
|
|
|
|
169
|
1
|
|
|
|
|
2
|
$rownum = -1; |
170
|
1
|
|
|
|
|
4
|
for my $row (@$rows) { |
171
|
4
|
|
|
|
|
7
|
$rownum++; |
172
|
4
|
|
|
|
|
7
|
my $colnum = -1; |
173
|
4
|
|
100
|
|
|
20
|
$exptable->[$rownum] //= []; |
174
|
4
|
|
|
|
|
9
|
push @{ $exptable->[$rownum] }, undef |
175
|
4
|
50
|
66
|
|
|
7
|
if (@{ $exptable->[$rownum] } == 0 || |
|
4
|
|
|
|
|
18
|
|
176
|
|
|
|
|
|
|
defined($exptable->[$rownum][-1])); |
177
|
|
|
|
|
|
|
#use DDC; say "D:exptable->[$rownum] = ", DDC::dump($exptable->[$rownum]); |
178
|
4
|
|
|
4
|
|
17
|
my $exptable_colnum = firstidx {!defined} @{ $exptable->[$rownum] }; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
16
|
|
179
|
|
|
|
|
|
|
#say "D:rownum=$rownum, exptable_colnum=$exptable_colnum"; |
180
|
4
|
50
|
|
|
|
16
|
if ($exptable_colnum == -1) { $exptable_colnum = 0 } |
|
0
|
|
|
|
|
0
|
|
181
|
4
|
50
|
33
|
|
|
22
|
$exptable_bottom_borders->[$rownum] //= $args{separate_rows} ? 1:0; |
182
|
|
|
|
|
|
|
|
183
|
4
|
|
|
|
|
7
|
for my $cell (@$row) { |
184
|
9
|
|
|
|
|
13
|
$colnum++; |
185
|
9
|
|
|
|
|
10
|
my $text; |
186
|
|
|
|
|
|
|
|
187
|
9
|
|
|
|
|
12
|
my $rowspan = 1; |
188
|
9
|
|
|
|
|
11
|
my $colspan = 1; |
189
|
9
|
100
|
|
|
|
20
|
if (ref $cell eq 'HASH') { |
190
|
2
|
|
|
|
|
5
|
$text = $cell->{text}; |
191
|
2
|
100
|
|
|
|
9
|
$rowspan = $cell->{rowspan} if $cell->{rowspan}; |
192
|
2
|
100
|
|
|
|
6
|
$colspan = $cell->{colspan} if $cell->{colspan}; |
193
|
|
|
|
|
|
|
} else { |
194
|
7
|
|
|
|
|
11
|
$text = $cell; |
195
|
7
|
|
|
|
|
9
|
my $el; |
196
|
7
|
0
|
0
|
0
|
|
30
|
$el = first {$_->[0] == $rownum && $_->[1] == $colnum && $_->[2]{rowspan}} @$cell_attrs; |
|
0
|
|
|
|
|
0
|
|
197
|
7
|
50
|
|
|
|
23
|
$rowspan = $el->[2]{rowspan} if $el; |
198
|
7
|
0
|
0
|
0
|
|
21
|
$el = first {$_->[0] == $rownum && $_->[1] == $colnum && $_->[2]{colspan}} @$cell_attrs; |
|
0
|
|
|
|
|
0
|
|
199
|
7
|
50
|
|
|
|
23
|
$colspan = $el->[2]{colspan} if $el; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
9
|
|
|
|
|
12
|
my @widths; |
203
|
|
|
|
|
|
|
my @heights; |
204
|
9
|
|
|
|
|
21
|
for my $ir (1..$rowspan) { |
205
|
10
|
|
|
|
|
15
|
for my $ic (1..$colspan) { |
206
|
12
|
|
|
|
|
17
|
my $exptable_cell; |
207
|
12
|
|
|
|
|
27
|
$exptable->[$rownum+$ir-1][$exptable_colnum+$ic-1] = $exptable_cell = []; |
208
|
|
|
|
|
|
|
|
209
|
12
|
100
|
100
|
|
|
39
|
if ($ir == 1 && $ic == 1) { |
210
|
9
|
|
|
|
|
37
|
$exptable_cell->[IDX_EXPTABLE_CELL_ROWSPAN] = $rowspan; |
211
|
9
|
|
|
|
|
14
|
$exptable_cell->[IDX_EXPTABLE_CELL_COLSPAN] = $colspan; |
212
|
9
|
|
|
|
|
19
|
$exptable_cell->[IDX_EXPTABLE_CELL_ORIG] = $cell; |
213
|
|
|
|
|
|
|
} else { |
214
|
3
|
100
|
|
|
|
17
|
$exptable_cell->[IDX_EXPTABLE_CELL_IS_ROWSPAN_TAIL] = 1 if $ir > 1; |
215
|
3
|
100
|
|
|
|
9
|
$exptable_cell->[IDX_EXPTABLE_CELL_IS_COLSPAN_TAIL] = 1 if $ic > 1; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
#use DDC; dd $exptable; say ''; # debug |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
10
|
|
|
|
|
14
|
my $val; |
221
|
10
|
50
|
|
|
|
22
|
$val = _get_attr('bottom_border', $rownum+$ir-1, undef, undef, \%args); $exptable_bottom_borders->[$rownum+$ir-1] = $val if $val; |
|
10
|
|
|
|
|
21
|
|
222
|
10
|
50
|
|
|
|
19
|
$val = _get_attr('top_border' , $rownum+$ir-1, undef, undef, \%args); $exptable_bottom_borders->[$rownum+$ir-2] = $val if $val; |
|
10
|
|
|
|
|
19
|
|
223
|
10
|
50
|
66
|
|
|
29
|
$exptable_bottom_borders->[0] = 1 if $rownum+$ir-1 == 0 && $args{header_row}; |
224
|
|
|
|
|
|
|
|
225
|
10
|
100
|
|
|
|
26
|
$M = $rownum+$ir if $M < $rownum+$ir; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
|
228
|
9
|
|
|
|
|
13
|
$exptable_colnum += $colspan; |
229
|
9
|
|
|
|
|
24
|
$exptable_colnum++ while defined $exptable->[$rownum][$exptable_colnum]; |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
} # for a row |
232
|
4
|
100
|
|
|
|
10
|
$N = $exptable_colnum if $N < $exptable_colnum; |
233
|
|
|
|
|
|
|
} # for rows |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# 1b. calculate the heigth and width of each exptable cell (as required |
236
|
|
|
|
|
|
|
# by the text, or specified width/height when we allow cell attrs width, |
237
|
|
|
|
|
|
|
# height) |
238
|
|
|
|
|
|
|
|
239
|
1
|
|
|
|
|
4
|
for my $exptable_rownum (0..$M-1) { |
240
|
4
|
|
|
|
|
18
|
for my $exptable_colnum (0..$N-1) { |
241
|
12
|
|
|
|
|
19
|
my $exptable_cell = $exptable->[$exptable_rownum][$exptable_colnum]; |
242
|
12
|
100
|
|
|
|
24
|
next if _exptable_cell_is_tail($exptable_cell); |
243
|
9
|
|
|
|
|
15
|
my $rowspan = $exptable_cell->[IDX_EXPTABLE_CELL_ROWSPAN]; |
244
|
9
|
|
|
|
|
14
|
my $colspan = $exptable_cell->[IDX_EXPTABLE_CELL_COLSPAN]; |
245
|
9
|
|
|
|
|
11
|
my $cell = $exptable_cell->[IDX_EXPTABLE_CELL_ORIG]; |
246
|
9
|
100
|
|
|
|
24
|
my $text = ref $cell eq 'HASH' ? $cell->{text} : $cell; |
247
|
9
|
|
|
|
|
18
|
my $lh = Text::NonWideChar::Util::length_height($text); |
248
|
|
|
|
|
|
|
#use DDC; say "D:length_height[$exptable_rownum,$exptable_colnum] = (".DDC::dump($text)."): ".DDC::dump($lh); |
249
|
9
|
|
|
|
|
255
|
my $tot_intercol_widths = ($colspan-1) * $intercol_width; |
250
|
9
|
50
|
|
|
|
11
|
my $tot_interrow_heights = 0; for (1..$rowspan-1) { $tot_interrow_heights++ if $exptable_bottom_borders->[$exptable_rownum+$_-1] } |
|
9
|
|
|
|
|
19
|
|
|
1
|
|
|
|
|
5
|
|
251
|
|
|
|
|
|
|
#say "D:interrow_heights=$tot_interrow_heights"; |
252
|
9
|
|
|
|
|
27
|
my @heights = _divide_int_to_n_ints(max(0, $lh->[1] - $tot_interrow_heights), $rowspan); |
253
|
9
|
|
|
|
|
20
|
my @widths = _divide_int_to_n_ints(max(0, $lh->[0] - $tot_intercol_widths ), $colspan); |
254
|
9
|
|
|
|
|
17
|
for my $ir (1..$rowspan) { |
255
|
10
|
|
|
|
|
16
|
for my $ic (1..$colspan) { |
256
|
12
|
|
|
|
|
22
|
$exptable->[$exptable_rownum+$ir-1][$exptable_colnum+$ic-1][IDX_EXPTABLE_CELL_HEIGHT] = $heights[$ir-1]; |
257
|
12
|
|
|
|
|
36
|
$exptable->[$exptable_rownum+$ir-1][$exptable_colnum+$ic-1][IDX_EXPTABLE_CELL_WIDTH] = $widths [$ic-1]; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
} # for rows |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
} # CONSTRUCT_EXPTABLE |
264
|
|
|
|
|
|
|
#use DDC; dd $exptable; # debug |
265
|
|
|
|
|
|
|
#print "D: exptable size: $M x $N (HxW)\n"; # debug |
266
|
|
|
|
|
|
|
#use DDC; print "bottom borders: "; dd $exptable_bottom_borders; # debug |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
OPTIMIZE_EXPTABLE: { |
269
|
|
|
|
|
|
|
# TODO |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# 2. we reduce extraneous columns and rows if there are colspan that are |
272
|
|
|
|
|
|
|
# too many. for example, if all exptable cells in column 1 has colspan=2 |
273
|
|
|
|
|
|
|
# (or one row has colspan=2 and another row has colspan=3), we might as |
274
|
|
|
|
|
|
|
# remove 1 column because the extra column span doesn't have any |
275
|
|
|
|
|
|
|
# content. same case for extraneous row spans. |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# 2a. remove extra undefs. skip this. doesn't make a difference. |
278
|
|
|
|
|
|
|
#for my $exptable_row (@{ $exptable }) { |
279
|
|
|
|
|
|
|
# splice @$exptable_row, $N if @$exptable_row > $N; |
280
|
|
|
|
|
|
|
#} |
281
|
|
|
|
|
|
|
|
282
|
1
|
|
|
|
|
3
|
1; |
|
1
|
|
|
|
|
2
|
|
283
|
|
|
|
|
|
|
} # OPTIMIZE_EXPTABLE |
284
|
|
|
|
|
|
|
#use DDC; dd $exptable; # debug |
285
|
|
|
|
|
|
|
|
286
|
1
|
|
|
|
|
3
|
my $exptable_column_widths = []; # idx=exptable colnum |
287
|
1
|
|
|
|
|
2
|
my $exptable_row_heights = []; # idx=exptable rownum |
288
|
|
|
|
|
|
|
DETERMINE_SIZE_OF_EACH_EXPTABLE_COLUMN_AND_ROW: { |
289
|
|
|
|
|
|
|
# 3. before we draw the exptable, we need to determine the width and |
290
|
|
|
|
|
|
|
# height of each exptable column and row. |
291
|
|
|
|
|
|
|
#use DDC; |
292
|
1
|
|
|
|
|
2
|
for my $ir (0..$M-1) { |
|
1
|
|
|
|
|
3
|
|
293
|
4
|
|
|
|
|
8
|
my $exptable_row = $exptable->[$ir]; |
294
|
|
|
|
|
|
|
$exptable_row_heights->[$ir] = max( |
295
|
4
|
|
100
|
|
|
7
|
1, map {$_->[IDX_EXPTABLE_CELL_HEIGHT] // 0} @$exptable_row); |
|
13
|
|
|
|
|
47
|
|
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
1
|
|
|
|
|
12
|
for my $ic (0..$N-1) { |
299
|
|
|
|
|
|
|
$exptable_column_widths->[$ic] = max( |
300
|
3
|
50
|
|
|
|
11
|
1, map {$exptable->[$_][$ic] ? $exptable->[$_][$ic][IDX_EXPTABLE_CELL_WIDTH] : 0} 0..$M-1); |
|
12
|
|
|
|
|
28
|
|
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
} # DETERMINE_SIZE_OF_EACH_EXPTABLE_COLUMN_AND_ROW |
303
|
|
|
|
|
|
|
#use DDC; print "column widths: "; dd $exptable_column_widths; # debug |
304
|
|
|
|
|
|
|
#use DDC; print "row heights: "; dd $exptable_row_heights; # debug |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# each elem is an arrayref containing characters to render a line of the |
307
|
|
|
|
|
|
|
# table, e.g. for element [0] the row is all borders. for element [1]: |
308
|
|
|
|
|
|
|
# [$left_border_str, $exptable_cell_content1, $border_between_col, |
309
|
|
|
|
|
|
|
# $exptable_cell_content2, ...]. all will be joined together with "\n" to |
310
|
|
|
|
|
|
|
# form the final rendered table. |
311
|
1
|
|
|
|
|
2
|
my @buf; |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
DRAW_EXPTABLE: { |
314
|
|
|
|
|
|
|
# 4. finally we draw the (exp)table. |
315
|
|
|
|
|
|
|
|
316
|
1
|
|
|
|
|
2
|
my $y = 0; |
|
1
|
|
|
|
|
1
|
|
317
|
|
|
|
|
|
|
|
318
|
1
|
|
|
|
|
3
|
for my $ir (0..$M-1) { |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
DRAW_TOP_BORDER: |
321
|
|
|
|
|
|
|
{ |
322
|
4
|
100
|
|
|
|
10
|
last unless $ir == 0; |
323
|
1
|
50
|
|
|
|
5
|
my $b_y = $args{header_row} ? 0 : 6; |
324
|
1
|
|
|
|
|
6
|
my $b_topleft = $bs_obj->get_border_char($b_y, 0); |
325
|
1
|
|
|
|
|
32
|
my $b_topline = $bs_obj->get_border_char($b_y, 1); |
326
|
1
|
|
|
|
|
21
|
my $b_topbetwcol = $bs_obj->get_border_char($b_y, 2); |
327
|
1
|
|
|
|
|
21
|
my $b_topright = $bs_obj->get_border_char($b_y, 3); |
328
|
1
|
0
|
33
|
|
|
75
|
last unless length $b_topleft || length $b_topline || length $b_topbetwcol || length $b_topright; |
|
|
|
33
|
|
|
|
|
|
|
|
0
|
|
|
|
|
329
|
1
|
|
|
|
|
5
|
$buf[$y][0] = $b_topleft; |
330
|
1
|
|
|
|
|
5
|
for my $ic (0..$N-1) { |
331
|
3
|
100
|
|
|
|
11
|
my $cell_right = $ic < $N-1 ? $exptable->[$ir][$ic+1] : undef; |
332
|
3
|
|
66
|
|
|
13
|
my $cell_right_has_content = defined $cell_right && _exptable_cell_is_head($cell_right); |
333
|
3
|
|
|
|
|
12
|
$buf[$y][$ic*4+2] = $bs_obj->get_border_char($b_y, 1, $exptable_column_widths->[$ic]+2); # +1, +2, +3 |
334
|
3
|
50
|
|
|
|
79
|
$buf[$y][$ic*4+4] = $ic == $N-1 ? $b_topright : ($cell_right_has_content ? $b_topbetwcol : $b_topline); |
|
|
100
|
|
|
|
|
|
335
|
|
|
|
|
|
|
} |
336
|
1
|
|
|
|
|
4
|
$y++; |
337
|
|
|
|
|
|
|
} # DRAW_TOP_BORDER |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
# DRAW_DATA_OR_HEADER_ROW |
340
|
|
|
|
|
|
|
{ |
341
|
|
|
|
|
|
|
# draw leftmost border, which we always do. |
342
|
4
|
100
|
66
|
|
|
7
|
my $b_y = $ir == 0 && $args{header_row} ? 1 : 3; |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
15
|
|
343
|
4
|
|
|
|
|
11
|
for my $i (1 .. $exptable_row_heights->[$ir]) { |
344
|
5
|
|
|
|
|
33
|
$buf[$y+$i-1][0] = $bs_obj->get_border_char($b_y, 0); |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
4
|
|
|
|
|
95
|
my $lines; |
348
|
4
|
|
|
|
|
18
|
for my $ic (0..$N-1) { |
349
|
12
|
|
|
|
|
19
|
my $cell = $exptable->[$ir][$ic]; |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
# draw cell content. also possibly draw border between |
352
|
|
|
|
|
|
|
# cells. we don't draw border inside a row/colspan. |
353
|
12
|
100
|
|
|
|
87
|
if (_exptable_cell_is_head($cell)) { |
354
|
9
|
|
|
|
|
23
|
$lines = _get_exptable_cell_lines( |
355
|
|
|
|
|
|
|
\%args, $exptable, $exptable_row_heights, $exptable_column_widths, |
356
|
|
|
|
|
|
|
$exptable_bottom_borders, $intercol_width, $ir, $ic); |
357
|
9
|
|
|
|
|
13
|
for my $i (0..$#{$lines}) { |
|
9
|
|
|
|
|
28
|
|
358
|
14
|
|
|
|
|
31
|
$buf[$y+$i][$ic*4+0] = $bs_obj->get_border_char($b_y, 1); |
359
|
14
|
|
|
|
|
307
|
$buf[$y+$i][$ic*4+1] = " "; |
360
|
14
|
|
|
|
|
28
|
$buf[$y+$i][$ic*4+2] = $lines->[$i]; |
361
|
14
|
|
|
|
|
26
|
$buf[$y+$i][$ic*4+3] = " "; |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
#use DDC; say "D: Drawing exptable_cell($ir,$ic): ", DDC::dump($lines); |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
# draw rightmost border, which we always do. |
367
|
12
|
100
|
|
|
|
34
|
if ($ic == $N-1) { |
368
|
4
|
100
|
66
|
|
|
16
|
my $b_y = $ir == 0 && $args{header_row} ? 1 : 3; |
369
|
4
|
|
|
|
|
12
|
for my $i (1 .. $exptable_row_heights->[$ir]) { |
370
|
5
|
|
|
|
|
32
|
$buf[$y+$i-1][$ic*4+4] = $bs_obj->get_border_char($b_y, 2); |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
} # DRAW_DATA_OR_HEADER_ROW |
376
|
4
|
|
|
|
|
101
|
$y += $exptable_row_heights->[$ir]; |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
DRAW_ROW_SEPARATOR: |
379
|
|
|
|
|
|
|
{ |
380
|
4
|
100
|
|
|
|
17
|
last unless $ir < $M-1; |
|
4
|
|
|
|
|
18
|
|
381
|
3
|
50
|
|
|
|
8
|
last unless $exptable_bottom_borders->[$ir]; |
382
|
3
|
100
|
66
|
|
|
13
|
my $b_y = $ir == 0 && $args{header_row} ? 2 : 4; |
383
|
3
|
|
|
|
|
15
|
my $b_betwrowleft = $bs_obj->get_border_char($b_y, 0); |
384
|
3
|
|
|
|
|
65
|
my $b_betwrowline = $bs_obj->get_border_char($b_y, 1); |
385
|
3
|
|
|
|
|
60
|
my $b_betwrowbetwcol = $bs_obj->get_border_char($b_y, 2); |
386
|
3
|
|
|
|
|
59
|
my $b_betwrowright = $bs_obj->get_border_char($b_y, 3); |
387
|
3
|
0
|
33
|
|
|
62
|
last unless length $b_betwrowleft || length $b_betwrowline || length $b_betwrowbetwcol || length $b_betwrowright; |
|
|
|
33
|
|
|
|
|
|
|
|
0
|
|
|
|
|
388
|
3
|
|
|
|
|
7
|
my $b_betwrowbetwcol_notop = $bs_obj->get_border_char($b_y, 4); |
389
|
3
|
|
|
|
|
69
|
my $b_betwrowbetwcol_nobot = $bs_obj->get_border_char($b_y, 5); |
390
|
3
|
|
|
|
|
62
|
my $b_betwrowbetwcol_noleft = $bs_obj->get_border_char($b_y, 6); |
391
|
3
|
|
|
|
|
58
|
my $b_betwrowbetwcol_noright = $bs_obj->get_border_char($b_y, 7); |
392
|
3
|
100
|
66
|
|
|
60
|
my $b_yd = $ir == 0 && $args{header_row} ? 2 : 3; |
393
|
3
|
|
|
|
|
8
|
my $b_datarowleft = $bs_obj->get_border_char($b_yd, 0); |
394
|
3
|
|
|
|
|
62
|
my $b_datarowbetwcol = $bs_obj->get_border_char($b_yd, 1); |
395
|
3
|
|
|
|
|
59
|
my $b_datarowright = $bs_obj->get_border_char($b_yd, 2); |
396
|
3
|
|
|
|
|
61
|
for my $ic (0..$N-1) { |
397
|
9
|
|
|
|
|
13
|
my $cell = $exptable->[$ir][$ic]; |
398
|
9
|
100
|
|
|
|
22
|
my $cell_right = $ic < $N-1 ? $exptable->[$ir][$ic+1] : undef; |
399
|
9
|
50
|
|
|
|
19
|
my $cell_bottom = $ir < $M-1 ? $exptable->[$ir+1][$ic] : undef; |
400
|
9
|
100
|
66
|
|
|
39
|
my $cell_rightbottom = $ir < $M-1 && $ic < $N-1 ? $exptable->[$ir+1][$ic+1] : undef; |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# leftmost border |
403
|
9
|
100
|
|
|
|
18
|
if ($ic == 0) { |
404
|
3
|
50
|
|
|
|
7
|
$buf[$y][0] = _exptable_cell_is_rowspan_tail($cell_bottom) ? $b_datarowleft : $b_betwrowleft; |
405
|
|
|
|
|
|
|
} |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
# along the width of cell content |
408
|
9
|
100
|
|
|
|
22
|
if (_exptable_cell_is_rowspan_head($cell_bottom)) { |
409
|
7
|
|
|
|
|
20
|
$buf[$y][$ic*4+2] = $bs_obj->get_border_char($b_y, 1, $exptable_column_widths->[$ic]+2); |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
9
|
|
|
|
|
151
|
my $char; |
413
|
9
|
100
|
|
|
|
19
|
if ($ic == $N-1) { |
414
|
|
|
|
|
|
|
# rightmost |
415
|
3
|
100
|
|
|
|
5
|
if (_exptable_cell_is_rowspan_tail($cell_bottom)) { |
416
|
1
|
|
|
|
|
2
|
$char = $b_datarowright; |
417
|
|
|
|
|
|
|
} else { |
418
|
2
|
|
|
|
|
4
|
$char = $b_betwrowright; |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
} else { |
421
|
|
|
|
|
|
|
# between cells |
422
|
6
|
100
|
|
|
|
12
|
if (_exptable_cell_is_colspan_tail($cell_right)) { |
423
|
1
|
50
|
|
|
|
3
|
if (_exptable_cell_is_colspan_tail($cell_rightbottom)) { |
424
|
1
|
50
|
|
|
|
3
|
if (_exptable_cell_is_rowspan_tail($cell_bottom)) { |
425
|
1
|
|
|
|
|
2
|
$char = ""; |
426
|
|
|
|
|
|
|
} else { |
427
|
0
|
|
|
|
|
0
|
$char = $b_betwrowline; |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
} else { |
430
|
0
|
|
|
|
|
0
|
$char = $b_betwrowbetwcol_notop; |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
} else { |
433
|
5
|
100
|
|
|
|
9
|
if (_exptable_cell_is_colspan_tail($cell_rightbottom)) { |
434
|
1
|
|
|
|
|
2
|
$char = $b_betwrowbetwcol_nobot; |
435
|
|
|
|
|
|
|
} else { |
436
|
4
|
50
|
|
|
|
9
|
if (_exptable_cell_is_rowspan_tail($cell_bottom)) { |
|
|
100
|
|
|
|
|
|
437
|
0
|
0
|
|
|
|
0
|
if (_exptable_cell_is_rowspan_tail($cell_rightbottom)) { |
438
|
0
|
|
|
|
|
0
|
$char = $b_datarowbetwcol; |
439
|
|
|
|
|
|
|
} else { |
440
|
0
|
|
|
|
|
0
|
$char = $b_betwrowbetwcol_noleft; |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
} elsif (_exptable_cell_is_rowspan_tail($cell_rightbottom)) { |
443
|
1
|
|
|
|
|
2
|
$char = $b_betwrowbetwcol_noright; |
444
|
|
|
|
|
|
|
} else { |
445
|
3
|
|
|
|
|
5
|
$char = $b_betwrowbetwcol; |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
} |
450
|
9
|
|
|
|
|
38
|
$buf[$y][$ic*4+4] = $char; |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
} |
453
|
3
|
|
|
|
|
5
|
$y++; |
454
|
|
|
|
|
|
|
} # DRAW_ROW_SEPARATOR |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
DRAW_BOTTOM_BORDER: |
457
|
|
|
|
|
|
|
{ |
458
|
4
|
100
|
|
|
|
8
|
last unless $ir == $M-1; |
|
4
|
|
|
|
|
11
|
|
459
|
1
|
50
|
33
|
|
|
29
|
my $b_y = $ir == 0 && $args{header_row} ? 7 : 5; |
460
|
1
|
|
|
|
|
6
|
my $b_botleft = $bs_obj->get_border_char($b_y, 0); |
461
|
1
|
|
|
|
|
23
|
my $b_botline = $bs_obj->get_border_char($b_y, 1); |
462
|
1
|
|
|
|
|
22
|
my $b_botbetwcol = $bs_obj->get_border_char($b_y, 2); |
463
|
1
|
|
|
|
|
21
|
my $b_botright = $bs_obj->get_border_char($b_y, 3); |
464
|
1
|
0
|
33
|
|
|
23
|
last unless length $b_botleft || length $b_botline || length $b_botbetwcol || length $b_botright; |
|
|
|
33
|
|
|
|
|
|
|
|
0
|
|
|
|
|
465
|
1
|
|
|
|
|
3
|
$buf[$y][0] = $b_botleft; |
466
|
1
|
|
|
|
|
5
|
for my $ic (0..$N-1) { |
467
|
3
|
100
|
|
|
|
9
|
my $cell_right = $ic < $N-1 ? $exptable->[$ir][$ic+1] : undef; |
468
|
3
|
|
|
|
|
8
|
$buf[$y][$ic*4+2] = $bs_obj->get_border_char($b_y, 1, $exptable_column_widths->[$ic]+2); |
469
|
3
|
100
|
|
|
|
68
|
$buf[$y][$ic*4+4] = $ic == $N-1 ? $b_botright : (_exptable_cell_is_colspan_tail($cell_right) ? $b_botline : $b_botbetwcol); |
|
|
100
|
|
|
|
|
|
470
|
|
|
|
|
|
|
} |
471
|
1
|
|
|
|
|
12
|
$y++; |
472
|
|
|
|
|
|
|
} # DRAW_BOTTOM_BORDER |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
} |
475
|
|
|
|
|
|
|
} # DRAW_EXPTABLE |
476
|
|
|
|
|
|
|
|
477
|
1
|
100
|
|
|
|
4
|
for my $row (@buf) { for (@$row) { $_ = "" if !defined($_) } } # debug. remove undef to "" to save dump width |
|
10
|
|
|
|
|
17
|
|
|
130
|
|
|
|
|
253
|
|
478
|
|
|
|
|
|
|
#use DDC; dd \@buf; |
479
|
1
|
|
|
|
|
3
|
join "", (map { my $linebuf = $_; join("", grep {defined} @$linebuf)."\n" } @buf); |
|
10
|
|
|
|
|
14
|
|
|
10
|
|
|
|
|
12
|
|
|
130
|
|
|
|
|
248
|
|
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
# Back-compat: 'table' is an alias for 'generate_table', but isn't exported |
483
|
|
|
|
|
|
|
{ |
484
|
1
|
|
|
1
|
|
18
|
no warnings 'once'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
127
|
|
485
|
|
|
|
|
|
|
*table = \&generate_table; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
1; |
489
|
|
|
|
|
|
|
# ABSTRACT: Text::Table::Tiny + support for column/row spans |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
__END__ |