File Coverage

blib/lib/Image/ButtonMaker/TextContainer.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Image::ButtonMaker::TextContainer;
2              
3 1     1   548 use Image::Magick;
  0            
  0            
4             use strict;
5             use utf8;
6              
7              
8             #### Create a dummy image for QueryFontMetrics calls
9             my $idummy = Image::Magick->new();
10             $idummy->Read('xc:black');
11              
12              
13              
14             #### Prototype for TextContainer objects
15             my @defaults = (
16             layout => 'horizontal',
17             align => 'baseline',
18             cells => [],
19             );
20              
21              
22             my @align_horiz_types = ('baseline', 'top', 'bottom');
23             my @layout_types = ('vertical', 'horizontal');
24              
25              
26             #### Private data for TextContainer objects
27             my @defaults_priv = (
28             error => 0,
29             errstr => '',
30             );
31              
32              
33             #### Error codes
34             use constant ERR_WRONG_CELL_TYPE => 100;
35             use constant ERR_WRONG_CELL_PARAM => 101;
36             use constant ERR_UNSUPPORTED_FEAT => 1000;
37              
38              
39             #### Cell prototypes #####################################
40             my @cell_types = ('text', 'space', 'icon');
41             my %cell_proto = (
42             text => {
43             type => 'text',
44             font => '',
45             size => 10,
46             text => 'NO_TEXT',
47             antialias => 1,
48             fill => 'white',
49             scale => 1.0,
50             },
51              
52             space => {
53             type => 'space',
54             width => 3,
55             },
56              
57             icon => {
58             type => 'icon',
59             image => undef,
60             vertfix => 0,
61             }
62             );
63              
64              
65              
66             ##########################################################
67             ## Public-ish methods
68             sub new {
69             my $self = shift;
70             my @param = @_;
71              
72             my $data = { @defaults, @param };
73              
74             return undef unless(search_array($data->{layout}, \@layout_types));
75              
76             if($data->{layout} eq 'horizontal') {
77             unless(search_array($data->{align}, \@align_horiz_types)) {
78             print STDERR "Alignment ".$data->{align}." not supported for layout horizontal\n";
79             return undef;
80             }
81             }
82              
83             if($data->{layout} eq 'vertical') {
84             print STDERR "Vertical alignment not supported yet\n";
85             return undef;
86             }
87              
88             $data->{cells} = [];
89              
90             bless $data;
91              
92             $data->reset_error;
93              
94             return $data;
95             }
96              
97              
98             #### add cell to array and return undef
99             sub add_cell {
100             my $self = shift;
101             my %param = @_;
102              
103             ## Find cell type
104             my $type = $param{type};
105             $type = 'space' unless $type;
106              
107             if(!search_array($type, \@cell_types)) {
108             return $self->set_error(ERR_WRONG_CELL_TYPE,
109             "Unknown cell type: $type");
110              
111             }
112              
113             my $proto = $cell_proto{$type};
114             my $newcell = { %$proto };
115              
116             foreach my $k (keys(%param)) {
117             ## Return error if prototype doesn't have param
118             if(!exists($proto->{$k})) {
119             return $self->set_error(ERR_WRONG_CELL_PARAM,
120             "Unknown param $k for cell type $type");
121             }
122             $newcell->{$k} = $param{$k};
123             }
124              
125             my $cells = $self->{cells};
126             push @$cells, $newcell;
127              
128             return undef;
129             }
130              
131              
132             #### compute width and height for all the cells together
133             #### Return (width, height) or undef (+ set errorerror)
134             sub compute_size {
135             my $self = shift;
136             $self->reset_error;
137              
138             #### initialize values
139             my ($max_asc, $max_height, $min_desc) = (0,0,0);
140             my $acc_width = 0;
141              
142             my $cells = $self->{cells};
143              
144             foreach my $cell (@$cells) {
145             my $type = $cell->{type};
146              
147             if($type eq 'text') {
148             my %text_param =(font => $cell->{font},
149             pointsize => $cell->{size},
150             scale => $cell->{scale},
151             text => $cell->{text},
152             );
153              
154              
155             my ($x_ppem, $y_ppem, $ascender, $descender, $width, $height, $max_advance) =
156             $idummy->QueryFontMetrics(%text_param);
157              
158              
159             #Height is kinda strange. Recompute:
160             $height = $ascender - $descender;
161              
162             $acc_width += $width;
163             $max_height = $height if($height > $max_height);
164             $max_asc = $ascender if($ascender > $max_asc);
165             $min_desc = $descender if($descender < $min_desc);
166             }
167              
168             elsif($type eq 'space') {
169             $acc_width += $cell->{width};
170             }
171             elsif($type eq 'icon') {
172             my($width, $height) = $cell->{image}->Get('columns', 'rows');
173              
174             $max_height = $height if($height > $max_height);
175             $max_asc = $height if($height > $max_asc);
176             $acc_width += $width;
177             }
178             }
179              
180             if($self->{align} eq 'baseline') {
181             return ($acc_width, $max_asc-$min_desc, $max_asc, $min_desc);
182             }
183             return ($acc_width, $max_height, $max_asc, $min_desc);
184             }
185              
186              
187             #### render cells into a target image
188             sub render {
189             my $self = shift;
190             my ($image, $x_offset, $y_offset) = @_;
191             $x_offset = 0 unless($x_offset);
192             $y_offset = 0 unless($y_offset);
193              
194             $self->reset_error;
195              
196             my $layout = $self->{layout};
197             if($layout ne 'horizontal') {
198             $self->set_error(ERR_UNSUPPORTED_FEAT,
199             "Unsupported layout: $layout");
200             return undef;
201             }
202              
203             my $align = $self->{align};
204             my $cells = $self->{cells};
205              
206             my ($img_width, $img_height, $max_asc, $min_desc) = $self->compute_size;
207              
208             #### If no target image is passed, then generate a target
209             if(!$image) {
210             $image = Image::Magick->new(size => $img_width .'x'.$img_height,
211             matte => 1,
212             );
213             $image->Read('xc:rgba(0,0,0,0)');
214             }
215              
216             my $leftpoint = $x_offset;
217             my $toppoint;
218              
219             foreach my $cell (@$cells) {
220             my $type = $cell->{type};
221              
222             if($type eq 'text') {
223              
224             my %text_param =(font => $cell->{font},
225             pointsize => $cell->{size},
226             text => $cell->{text},
227             );
228              
229             my( $x_ppem, $y_ppem, $ascender, $descender,
230             $width, $height, $max_advance ) =
231             $idummy->QueryFontMetrics(%text_param);
232              
233             $height = $ascender - $descender;
234              
235             $text_param{antialias} = $cell->{antialias};
236             $text_param{fill} = $cell->{fill};
237             $text_param{stroke} = 'rgba(0,0,0,255)';
238             $text_param{scale} = $cell->{scale};
239              
240             if($align eq 'top') {
241             $toppoint = $ascender + $y_offset;
242             }
243             elsif($align eq 'bottom') {
244             $toppoint = $img_height + $descender + $y_offset;;
245             }
246             elsif($align eq 'baseline') {
247             $toppoint = $max_asc + $y_offset;;
248             }
249              
250             $text_param{x} = $leftpoint;
251             $text_param{y} = $toppoint;
252              
253             my $rr = $image->Annotate(%text_param);
254              
255             $leftpoint += $width;
256             }
257             elsif($type eq 'icon') {
258              
259             my $icon = $cell->{image};
260             my($i_width, $i_height) = $icon->Get('columns', 'rows');
261              
262              
263             if($align eq 'top') {
264             $toppoint = $y_offset;;
265             }
266             elsif($align eq 'bottom') {
267             $toppoint = $img_height - $i_height + $y_offset;;
268             }
269             elsif($align eq 'baseline') {
270             $toppoint = $max_asc - $i_height + $y_offset;;
271             }
272              
273             my $vvv = $cell->{vertfix};
274              
275             if($cell->{vertfix}) {
276             $toppoint += $cell->{vertfix};
277             }
278              
279             $toppoint = 0 if($toppoint < 0);
280              
281             $image->Composite(image => $icon,
282             compose => 'Over',
283             geometry=> fromtop($leftpoint, $toppoint)
284             );
285             $leftpoint += $i_width;
286             }
287             elsif($type eq 'space') {
288             $leftpoint += $cell->{width};
289             }
290             }
291              
292             return $image;
293             }
294              
295              
296             #### set alignment
297             sub set_align {
298             my $self = shift;
299             my $align = shift;
300             return 0;
301             }
302              
303              
304             ##########################################################
305             ## Private-ish methods
306              
307             sub reset_error {
308             my $self = shift;
309              
310             $self->{error} = 0;
311             $self->{errstr} = '';
312             return;
313             }
314              
315              
316             sub set_error {
317             my $self = shift;
318              
319             $self->{error} = shift;
320             $self->{errstr} = shift;
321              
322             print $self->{errstr}, "\n";
323             return $self->{errstr};
324             }
325              
326              
327             sub is_error {
328             my $self = shift;
329             return ($self->{error} != 0);
330             }
331              
332             sub get_errstr {
333             my $self = shift;
334             return $self->{errstr};
335             }
336              
337             ##### search_array(value, array_reference)
338             ## retval: 0 or 1
339             sub search_array {
340             my($val, $arr) = @_;
341              
342             foreach my $v (@$arr) {
343             return 1 if($v eq $val);
344             }
345              
346             return 0;
347             }
348              
349             ###### Create geometry string counting from left top corner
350             ## retval: geometry string
351             sub fromtop {
352             my $x = shift;
353             my $y = shift;
354             return '+'.$x.'+'.$y;
355             }
356              
357              
358             1;