File Coverage

blib/lib/Image/ButtonMaker/Button.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Image::ButtonMaker::Button;
2 1     1   5 use strict;
  1         1  
  1         34  
3 1     1   5 use utf8;
  1         1  
  1         5  
4 1     1   813 use locale;
  1         228  
  1         4  
5 1     1   26 use Image::ButtonMaker::ButtonClass;
  1         2  
  1         23  
6 1     1   598 use Image::ButtonMaker::ClassContainer;
  1         4  
  1         30  
7 1     1   587 use Image::ButtonMaker::TextContainer;
  0            
  0            
8             use Image::ButtonMaker::TemplateCanvas;
9             use Image::ButtonMaker::ColorCanvas;
10              
11              
12             use Image::Magick;
13              
14             our @property_names = qw(
15             FileType
16              
17             WidthMin
18             WidthMax
19             HeightMin
20             HeightMax
21              
22             ArtWorkType
23             ArtWorkHAlign
24             ArtWorkVAlign
25              
26             MarginLeft
27             MarginRight
28             MarginTop
29             MarginBottom
30              
31             CanvasType
32             CanvasTemplateImg
33             CanvasCutRight
34             CanvasCutLeft
35             CanvasCutTop
36             CanvasCutBottom
37             CanvasMatteColor
38              
39             CanvasBackgroundColor
40             CanvasBorderColor
41             CanvasBorderWidth
42              
43             Text
44             TextFont
45             TextColor
46             TextSize
47             TextAntiAlias
48             TextScale
49             TextUpperCase
50             TextPrefix
51             TextPostfix
52             NoLexicon
53              
54             IconName
55             IconSpace
56             IconVerticalAdjust
57              
58             AfterResizeY
59             AfterResizeX
60             );
61              
62             our $error;
63             our $errorstr;
64              
65             ## Public'ish default attributes
66             my @default = (
67             classname => undef, ## Image::ButtonMaker::ButtonClass name
68             classcontainer => undef,
69             name => undef,
70             properties => {}, ## Properties overriding Class properties
71             print_warnings => 0,
72             die_on_errors => 0,
73             die_on_warnings => 0,
74             );
75              
76              
77             ## Private'ish default attributes
78             my @default_priv = (
79             internals => {}, ## Internal object data
80             );
81              
82              
83             #### Class Methods ###################################################
84             #### Contstructor. Returns undef on error
85             sub new {
86             my $invocant = shift;
87             my $blessing = ref($invocant) || $invocant;
88              
89             reset_error();
90              
91             my %args = @_;
92             my %prototype = ();
93              
94             #### Class argument check
95             if(defined($args{classcontainer})) {
96             $prototype{classcontainer} = $args{classcontainer};
97             }
98              
99             if(defined($args{name})) {
100             $prototype{name} = $args{name};
101             } else {
102             return set_error(1000,"name for button not given");
103             }
104              
105             if(defined($args{classname})) {
106             my $container = $prototype{classcontainer};
107             return
108             set_error(1000, "Class name given, but no class container found")
109             unless($container);
110              
111             return
112             set_error(1000, "Class $args{classname} not found for button $args{name}")
113             unless($container->lookup_class($args{classname}));
114             $prototype{classname} = $args{classname};
115             }
116              
117             #### Property check
118             $prototype{properties} = {};
119              
120             if(defined($args{properties})) {
121             my $prop = $args{properties};
122             return
123             set_error(1000, "Propeties must be a reference")
124             unless(ref($prop) eq 'HASH');
125              
126             foreach my $pname (keys(%$prop)) {
127             return
128             set_error(1000, "Unknown propety $pname")
129             unless($invocant->is_property_legal($pname));
130             $prototype{properties}->{$pname} = $prop->{$pname};
131             }
132             }
133              
134             #### Create the object
135             my $object = {@default, %prototype, @default_priv};
136             bless $object, $blessing;
137              
138             return $object;
139             }
140              
141              
142             #### This method is both instance and class method
143             sub is_property_legal {
144             my $invocant = shift;
145             my $prop = shift;
146              
147             foreach my $p (@property_names) {
148             return 1 if ($p eq $prop);
149             }
150             return 0;
151             }
152              
153              
154             #### Instance Methods ###########################################################
155             sub lookup_name {
156             my $self = shift;
157             return $self->{name};
158             }
159              
160             sub lookup_filename {
161             my $self = shift;
162             my $name = $self->lookup_name;
163             my $type = $self->lookup_property('FileType');
164             $type = 'png' unless($type);
165              
166             return $name.'.'.$type;
167             }
168              
169             sub lookup_property {
170             my $self = shift;
171             my $propname = shift;
172             reset_error();
173              
174              
175             return
176             set_error(2000, "Illegal property $propname")
177             unless($self->is_property_legal($propname));
178              
179             my $props = $self->{properties};
180              
181             return $props->{$propname}
182             if(exists($props->{$propname}));
183              
184              
185             if($self->{classname}) {
186             my $container = $self->{classcontainer};
187             my $class = $container->lookup_class($self->{classname});
188             return
189             set_error(2000, "Class not found ".$self->{classname})
190             unless($class);
191              
192             return $class->lookup_property($propname);
193             }
194              
195             return undef;
196             }
197              
198             sub set_property {
199             my $self = shift;
200             my $propname = shift;
201             my $propvalue = shift;
202              
203             return
204             set_error(2000, "Illegal property $propname")
205             unless($self->is_property_legal($propname));
206              
207             $self->{properties}{$propname} = $propvalue;
208              
209             return 1;
210             }
211              
212             #### Render Button (this sub is not as scary as it looks)
213             sub render {
214             my $self = shift;
215             reset_error();
216              
217             my $idata = $self->{internals};
218              
219             #### Prepare Artwork
220             my $artobject;
221             my $artWorkType = $self->lookup_property('ArtWorkType');
222             if($artWorkType eq 'text') {
223             $artobject = $self->__prepare_simple_text;
224             }
225             elsif($artWorkType eq 'icon+text') {
226             $artobject = $self->__prepare_icon_plus_text;
227             }
228             elsif($artWorkType eq 'text+icon') {
229             $artobject = $self->__prepare_text_plus_icon;
230             }
231             else {
232             return set_error(3000, "ArtWorkType : $artWorkType not recognized");
233             }
234              
235             return undef
236             unless($artobject);
237              
238             #### Compute Artwork Size
239             my ($artWidth, $artHeight, $artMaxAsc, $artMinDesc) = $artobject->compute_size;
240             $idata->{artWidth} = $artWidth;
241             $idata->{artHeight} = $artHeight;
242             $idata->{artMaxAsc} = $artMaxAsc;
243             $idata->{artMinDesc} = $artMinDesc;
244              
245             #### Prepare Canvas
246             my $canvasobject;
247             if($self->lookup_property('CanvasType') eq 'pixmap') {
248             $canvasobject = $self->__prepare_pix_canvas;
249             }
250             elsif($self->lookup_property('CanvasType') eq 'color') {
251             $canvasobject = $self->__prepare_color_canvas;
252             }
253              
254             return undef if($error);
255              
256             ## Render part should not depend on low level implementation of objects
257              
258             ## Fetch margins
259             my $leftMargin = $self->lookup_property('MarginLeft') || 0;
260             my $rightMargin = $self->lookup_property('MarginRight') || 0;
261             my $topMargin = $self->lookup_property('MarginTop') || 0;
262             my $bottomMargin = $self->lookup_property('MarginBottom') || 0;
263              
264             #### Compute canvas width and height
265             ## Compute width
266             my $canvasWidth = $artWidth + $rightMargin + $leftMargin;
267             {
268             my $minimumWidth = $self->lookup_property('WidthMin') || 1;
269             my $maximumWidth = $self->lookup_property('WidthMax') || 10000;
270             $self->warn("Minium Width is bigger than MaximumWidth: $minimumWidth > $maximumWidth")
271             if($minimumWidth > $maximumWidth);
272             if($minimumWidth) {
273             $canvasWidth = $minimumWidth if($canvasWidth < $minimumWidth);
274             }
275             if($maximumWidth) {
276             if($canvasWidth > $maximumWidth) {
277             $canvasWidth = $maximumWidth;
278             $self->warn("Truncated Width. The artwork is to big to fit in now.");
279             }
280             }
281             }
282              
283             ## Compute height
284             my $canvasHeight = 0;
285             {
286             my $haligntype = $self->lookup_property('ArtWorkVAlign');
287             if($haligntype eq 'baseline') {
288             $canvasHeight = $artMaxAsc + $topMargin + $bottomMargin;
289             }
290             else {
291             $canvasHeight = $artHeight + $topMargin + $bottomMargin;
292             }
293             }
294              
295             {
296             my $minimumHeight = $self->lookup_property('HeightMin') || 1;
297             my $maximumHeight = $self->lookup_property('HeightMax') || 10000;
298             $self->warn("MiniumHeight is bigger than MaximumHeight: $minimumHeight > $maximumHeight")
299             if($minimumHeight > $maximumHeight);
300             if($minimumHeight) {
301             $canvasHeight = $minimumHeight if($canvasHeight < $minimumHeight);
302             }
303             if($maximumHeight) {
304             if($canvasHeight > $maximumHeight) {
305             $canvasHeight = $maximumHeight;
306             $self->warn("Truncated Height. The artwork is to big to fit in now.");
307             }
308             }
309             }
310              
311             #### Render Canvas
312             my $canvas = $canvasobject->render($canvasWidth, $canvasHeight);
313              
314             #### Compute alignment of artwork on canvas
315             ## horizontal alignment:
316             my $hAlign = 0;
317             {
318             my $restSpace = $canvasWidth - $leftMargin - $rightMargin;
319             my $haligntype = $self->lookup_property('ArtWorkHAlign');
320             if($haligntype eq 'left') {
321             $hAlign = $leftMargin;
322             }
323             elsif($haligntype eq 'right') {
324             $hAlign = $leftMargin + $restSpace - $artWidth;
325             }
326             elsif($haligntype eq 'center') {
327             use integer;
328             $hAlign = $leftMargin + ($restSpace - $artWidth)/2;
329             }
330             else {
331             $self->warn("Unknown horizontal align type: $haligntype");
332             }
333             }
334              
335             ## vertical alignment
336             my $vAlign = 0;
337             {
338             my $restSpace = $canvasHeight - $topMargin - $bottomMargin;
339             my $valigntype = $self->lookup_property('ArtWorkVAlign');
340             if($valigntype eq 'top') {
341             $vAlign = $topMargin;
342             }
343             elsif($valigntype eq 'bottom') {
344             $vAlign = $topMargin + $restSpace - $artHeight;
345             }
346             elsif($valigntype eq 'baseline') {
347             $vAlign = $topMargin + $restSpace - $artMaxAsc;
348             }
349             else {
350             print "What is $valigntype?\n";
351             $self->warn("Unknown vertical align type: $valigntype");
352             }
353             }
354              
355             ## Render Artwork
356             my $artwork = $artobject->render($canvas, $hAlign, $vAlign);
357              
358             $self->__after_resize($artwork);
359              
360             return $artwork;
361             }
362              
363              
364             #### Write image to file ##################################################
365             ## Target directory is optional
366             sub write {
367             my $self = shift;
368             my $target_dir = shift;
369              
370             reset_error();
371              
372             my $name = $self->{name};
373             return set_error(4000, "Button name not defined")
374             unless($name);
375              
376             my $img = $self->render;
377             return undef if($error);
378            
379             my $filename = $self->lookup_filename;
380              
381             $filename = "$target_dir/$filename" if(length($target_dir));
382              
383             my $err = $img->Write($filename);
384             return set_error(4000, "Could not write file $filename: $err")
385             if($err);
386              
387             return $img;
388             }
389              
390             ### Print Warnings to Standard Error
391             sub warn {
392             my $self = shift;
393             print STDERR (@_, "\n") if($self->{print_warnings});
394             die "EXITING" if($self->{die_on_warnings});
395             return;
396             }
397              
398             #### Package methods #########################################################
399             #### Set and reset package-wide error codes
400             sub reset_error {
401             $error = 0;
402             $errorstr = '';
403             return;
404             }
405              
406              
407             sub set_error {
408             $error = shift;
409             $errorstr = shift;
410             #FIXME(!!!) die_on_errors unimplemented
411             return @_;
412             }
413              
414              
415             ### Private'ish methods #########################################################
416             sub __prepare_pix_canvas {
417             my $self = shift;
418             reset_error();
419              
420             my $imageName = $self->lookup_property('CanvasTemplateImg');
421             return
422             set_error(3000, "Could not find file for canvas: $imageName")
423             unless(-f $imageName);
424              
425             my $i = Image::Magick->new();
426             my $res = $i->Read($imageName);
427              
428             return
429             set_error(3000, "Could not read image file $imageName")
430             if($res);
431              
432             my $cut_left = $self->lookup_property('CanvasCutLeft') || 0;
433             my $cut_right = $self->lookup_property('CanvasCutRight') || 0;
434             my $cut_top = $self->lookup_property('CanvasCutTop') || 0;
435             my $cut_bottom = $self->lookup_property('CanvasCutBottom') || 0;
436             my $matte_color= $self->lookup_property('CanvasMatteColor') || 'rgba(128,128,128,255)';
437              
438             my $canvas = Image::ButtonMaker::TemplateCanvas->new(cut_left => $cut_left,
439             cut_right => $cut_right,
440             cut_top => $cut_top,
441             cut_bottom => $cut_bottom,
442             matte_color=> $matte_color,
443             template => $i
444             );
445              
446             return
447             set_error(3000, "Could not create new pixmap canvas :".$Image::ButtonMaker::TemplateCanvas::errorstr)
448             unless($canvas);
449              
450             return $canvas;
451             }
452              
453              
454             sub __prepare_color_canvas {
455             my $self = shift;
456             reset_error();
457              
458             my $background_color = $self->lookup_property('CanvasBackgroundColor');
459             my $border_color = $self->lookup_property('CanvasBorderColor');
460             my $border_width = $self->lookup_property('CanvasBorderWidth');
461              
462              
463             my %args;
464             $args{background_color} = $background_color if(length($background_color));
465             $args{border_color} = $border_color if(length($border_color));
466             $args{border_width} = $border_width if($border_width);
467              
468             my $canvas = Image::ButtonMaker::ColorCanvas->new(%args);
469              
470             return
471             set_error(3000, "Could not create new color canvas :".$Image::ButtonMaker::ColorCanvas::errorstr)
472             unless($canvas);
473              
474             return $canvas;
475             }
476              
477              
478             sub __add_text_to_container {
479             my $self = shift;
480             my $container = shift;
481              
482             my $text = $self->lookup_property('Text');
483             $text = $text . $self->lookup_property('TextPostfix')
484             if($self->lookup_property('TextPostfix'));
485             $text = $self->lookup_property('TextPrefix') . $text
486             if($self->lookup_property('TextPrefix'));
487             $text = uc($text)
488             if($self->lookup_property('TextUpperCase'));
489              
490             my $font = $self->lookup_property('TextFont');
491             my $size = $self->lookup_property('TextSize');
492             my $fill = $self->lookup_property('TextColor');
493             my $aali = $self->lookup_property('TextAntiAlias');
494              
495             $aali = 'true' if(lc($aali) eq 'yes');
496             $aali = 'false' if(lc($aali) eq 'no');
497             $aali = 'true' unless($aali);
498              
499             my $scale = $self->lookup_property('TextScale');
500             $scale = 1 unless defined($scale);
501              
502             my $res = $container->add_cell(type => 'text',
503             font => $font,
504             text => $text,
505             size => $size,
506             fill => $fill,
507             antialias => $aali,
508             scale => $scale,
509             );
510             return
511             set_error(3000, "Could not add cell : ".$container->get_errstr()."")
512             if($res);
513             return 1;
514             }
515              
516              
517             sub __add_icon_to_container {
518             my $self = shift;
519             my $container = shift;
520              
521             my $iconFile = $self->lookup_property('IconName');
522             return set_error(3000, "No Icon name specified")
523             unless($iconFile);
524             return set_error(3000, "Could not find icon file : $iconFile")
525             unless(-f $iconFile);
526              
527             my $icon = Image::Magick->new();
528             my $res = $icon->Read($iconFile);
529             return set_error(3000, "Could not load icon file: $iconFile")
530             if($res);
531              
532             my $vertfix = $self->lookup_property('IconVerticalAdjust');
533              
534             $res = $container->add_cell(
535             type => 'icon',
536             image => $icon,
537             vertfix => $vertfix,
538             );
539             return 1;
540             }
541              
542             sub __prepare_simple_text {
543             my $self = shift;
544              
545             my $tc = Image::ButtonMaker::TextContainer->new(
546             layout => 'horizontal',
547             align => 'baseline',
548             );
549              
550             ## There is no proper error handling in TextContainer constructor
551             return set_error(3000, "Could not create TextContainer")
552             unless($tc);
553              
554             my $res = $self->__add_text_to_container($tc);
555             return undef unless($res);
556              
557             ## return the canvas object. call compute_size and render on it later
558             return $tc;
559             }
560              
561              
562             sub __prepare_text_plus_icon {
563             my $self = shift;
564              
565             my $tc = Image::ButtonMaker::TextContainer->new(
566             layout => 'horizontal',
567             align => 'baseline',
568             );
569              
570             ## There is no proper error handling in TextContainer constructor
571             return set_error(3000, "Could not create TextContainer")
572             unless($tc);
573              
574             my $res = $self->__add_text_to_container($tc);
575             return undef unless($res);
576              
577             my $space = $self->lookup_property('IconSpace');
578             if($space) {
579             return set_error(3000, "Invalid IconSpace Param : $space")
580             unless($space =~ m|^\d+$|);
581             $tc->add_cell(type => 'space',
582             width => $space,
583             );
584             }
585             $res = $self->__add_icon_to_container($tc);
586             return undef unless($res);
587             return $tc;
588              
589             }
590              
591             sub __prepare_icon_plus_text {
592             my $self = shift;
593              
594             my $tc = Image::ButtonMaker::TextContainer->new(
595             layout => 'horizontal',
596             align => 'baseline',
597             );
598              
599             ## There is no proper error handling in TextContainer constructor
600             return set_error(3000, "Could not create TextContainer")
601             unless($tc);
602              
603             my $res = $self->__add_icon_to_container($tc);
604             return undef unless($res);
605              
606             my $space = $self->lookup_property('IconSpace');
607             if($space) {
608             return set_error(3000, "Invalid IconSpace Param : $space")
609             unless($space =~ m|^\d+$|);
610             $tc->add_cell(type => 'space',
611             width => $space,
612             );
613             }
614              
615             $res = $self->__add_text_to_container($tc);
616             return undef unless($res);
617             return $tc;
618              
619             }
620              
621             #### Resizing the picture after it has been rendered
622             sub __after_resize {
623             my $self = shift;
624             my $pic = shift;
625             my $afterResizeX = $self->lookup_property('AfterResizeX');
626             my $afterResizeY = $self->lookup_property('AfterResizeY');
627             $self->warn("Both AfterResizeX and AfterResizeY are set")
628             if($afterResizeX && $afterResizeY);
629              
630             my($height, $width) = $pic->Get('rows', 'columns');
631              
632             if($afterResizeX) {
633             my $factor = $afterResizeX/$width;
634             my $newHeight = $height * $factor;
635             $newHeight = sprintf("%.0f",$newHeight);
636             $pic->Resize(height => $newHeight, width => $afterResizeX);
637             }
638              
639             if($afterResizeY) {
640             my $factor = $afterResizeY/$height;
641             my $newWidth = $width * $factor;
642             $newWidth = sprintf("%.0f", $newWidth);
643             $pic->Resize(height => $afterResizeY, width => $newWidth);
644             }
645              
646             return $pic;
647             }
648              
649             ### The End ################################################################
650             1;
651              
652             __END__