File Coverage

blib/lib/Tags/HTML/Image.pm
Criterion Covered Total %
statement 96 116 82.7
branch 26 42 61.9
condition 3 8 37.5
subroutine 13 13 100.0
pod 1 1 100.0
total 139 180 77.2


line stmt bran cond sub pod time code
1             package Tags::HTML::Image;
2              
3 7     7   496743 use base qw(Tags::HTML);
  7         32  
  7         3818  
4 7     7   55412 use strict;
  7         18  
  7         179  
5 7     7   32 use warnings;
  7         13  
  7         392  
6              
7 7     7   35 use Class::Utils qw(set_params split_params);
  7         1734  
  7         370  
8 7     7   49 use Error::Pure qw(err);
  7         11  
  7         381  
9 7     7   2508 use Mo::utils 0.12 qw(check_code);
  7         11206  
  7         269  
10 7     7   4593 use Mo::utils::CSS 0.02 qw(check_css_class);
  7         74380  
  7         175  
11 7     7   752 use Scalar::Util qw(blessed);
  7         18  
  7         10213  
12              
13             our $VERSION = 0.04;
14              
15             # Constructor.
16             sub new {
17 19     19 1 1779519 my ($class, @params) = @_;
18              
19             # Create object.
20 19         138 my ($object_params_ar, $other_params_ar) = split_params(
21             ['css_class', 'css_comment_height', 'fit_minus',
22             'img_comment_cb', 'img_select_cb', 'img_src_cb', 'img_width', 'title'],
23             @params);
24 19         672 my $self = $class->SUPER::new(@{$other_params_ar});
  19         123  
25              
26             # Image CSS class.
27 19         804 $self->{'css_class'} = 'image';
28              
29             # Image comment height (in pixels).
30 19         50 $self->{'css_comment_height'} = '50';
31              
32             # Length to minus of image fit.
33 19         54 $self->{'fit_minus'} = undef;
34              
35             # Image comment callback.
36 19         41 $self->{'img_comment_cb'} = undef;
37              
38             # Image select callback.
39 19         60 $self->{'img_select_cb'} = undef;
40              
41             # Image src callback across data object.
42 19         45 $self->{'img_src_cb'} = undef;
43              
44             # Image width in pixels.
45 19         39 $self->{'img_width'} = undef;
46              
47             # Image title.
48 19         41 $self->{'title'} = undef;
49              
50             # Process params.
51 19         36 set_params($self, @{$object_params_ar});
  19         67  
52              
53 19         218 check_css_class($self, 'css_class');
54              
55             # Check callback codes.
56 17         424 check_code($self, 'img_comment_cb');
57 16         260 check_code($self, 'img_select_cb');
58 15         168 check_code($self, 'img_src_cb');
59              
60 14         155 $self->_cleanup;
61              
62             # Object.
63 14         162 return $self;
64             }
65              
66             sub _cleanup {
67 15     15   57 my $self = shift;
68              
69 15         30 delete $self->{'_image'};
70 15         49 $self->{'_image_comment_tags'} = [];
71 15         51 $self->{'_image_comment_css'} = [];
72 15         35 $self->{'_image_select_tags'} = [];
73 15         51 $self->{'_image_select_css'} = [];
74 15         27 delete $self->{'_image_url'};
75              
76 15         26 return;
77             }
78              
79             sub _init {
80 9     9   36896 my ($self, $image, @params) = @_;
81              
82 9 100       39 if (! defined $image) {
83 1         8 err 'Image object is required.';
84             }
85 8 100 100     106 if (! blessed($image) || ! $image->isa('Data::Image')) {
86 2         75 err "Image object must be a instance of 'Data::Image'.";
87             }
88              
89 6         21 $self->{'_image'} = $image;
90              
91             # Process image URL.
92 6 100       28 if (defined $self->{'_image'}->url) {
    100          
    100          
93 3         47 $self->{'_image_url'} = $self->{'_image'}->url;
94             } elsif (defined $self->{'_image'}->url_cb) {
95 1         26 $self->{'_image_url'} = $self->{'_image'}->url_cb->($self->{'_image'});
96             } elsif (defined $self->{'img_src_cb'}) {
97 1         22 $self->{'_image_url'} = $self->{'img_src_cb'}->($self->{'_image'});
98             } else {
99 1         21 err 'No image URL.';
100             }
101              
102             # Process comment.
103 5 50       48 if (defined $self->{'img_comment_cb'}) {
104             ($self->{'_image_comment_tags'}, $self->{'_image_comment_css'})
105 0         0 = $self->{'img_comment_cb'}->($self, $image, @params);
106             } else {
107 5 50       22 if (defined $image->comment) {
108 0         0 $self->{'_image_comment_tags'} = [
109             ['d', $image->comment],
110             ];
111             }
112             }
113 5 50       39 if (@{$self->{'_image_comment_tags'}}) {
  5         19  
114 0         0 my $comment_font_size = $self->{'css_comment_height'} / 2;
115 0         0 my $comment_vertical_padding = $self->{'css_comment_height'} / 4;
116 0         0 push @{$self->{'_image_comment_css'}}, (
117             ['s', '.'.$self->{'css_class'}.' figcaption'],
118             ['d', 'position', 'absolute'],
119             ['d', 'bottom', 0],
120             ['d', 'background', 'rgb(0, 0, 0)'],
121             ['d', 'background', 'rgba(0, 0, 0, 0.5)'],
122             ['d', 'color', '#f1f1f1'],
123             ['d', 'width', '100%'],
124             ['d', 'transition', '.5s ease'],
125             ['d', 'opacity', 0],
126             ['d', 'font-size', $comment_font_size.'px'],
127             ['d', 'padding', $comment_vertical_padding.'px 5px'],
128             ['d', 'text-align', 'center'],
129             ['e'],
130              
131 0         0 ['s', 'figure.'.$self->{'css_class'}.':hover figcaption'],
132             ['d', 'opacity', 1],
133             ['e'],
134             );
135             }
136              
137 5 50       20 if (defined $self->{'img_select_cb'}) {
138 0         0 my $select_hr = $self->{'img_select_cb'}->($self, $image, @params);
139 0 0 0     0 if (ref $select_hr eq 'HASH' && exists $select_hr->{'value'}) {
140 0   0     0 $select_hr->{'css_background_color'} ||= 'lightgreen';
141             $self->{'_image_select_tags'} = [
142             ['b', 'i'],
143             ['a', 'class', 'selected'],
144             ['a', 'style', 'background-color: '.$select_hr->{'css_background_color'}.';'],
145             exists $select_hr->{'value'} ? (
146 0 0       0 ['d', $select_hr->{'value'}],
147             ) : (),
148             ['e', 'i'],
149             ];
150             }
151              
152 0         0 push @{$self->{'_image_select_css'}}, (
153 0         0 ['s', '.'.$self->{'css_class'}.' .selected'],
154             ['d', 'border', '1px solid black'],
155             ['d', 'border-radius', '0.5em'],
156             ['d', 'color', 'black'],
157             ['d', 'padding', '0.5em'],
158             ['d', 'position', 'absolute'],
159             ['d', 'right', '10px'],
160             ['d', 'top', '10px'],
161             ['e'],
162             );
163             }
164              
165 5         14 return;
166             }
167              
168             # Process 'Tags'.
169             sub _process {
170 2     2   20 my $self = shift;
171              
172 2 100       5 if (! exists $self->{'_image'}) {
173 1         2 return;
174             }
175              
176             # Begin of figure.
177             $self->{'tags'}->put(
178             ['b', 'figure'],
179 1         12 ['a', 'class', $self->{'css_class'}],
180             );
181              
182             # Begin of image title.
183 1 50       139 if (defined $self->{'title'}) {
184             $self->{'tags'}->put(
185             ['b', 'fieldset'],
186             ['b', 'legend'],
187 0         0 ['d', $self->{'title'}],
188             ['e', 'legend'],
189             );
190             }
191              
192             # Select information.
193 1 50       3 if (@{$self->{'_image_select_tags'}}) {
  1         3  
194             $self->{'tags'}->put(
195 0         0 @{$self->{'_image_select_tags'}},
  0         0  
196             );
197             }
198              
199 1         2 my @alt;
200 1 50       5 if ($self->{'_image'}->comment) {
201 0         0 push @alt, ['a', 'alt', $self->{'_image'}->comment];
202             }
203              
204             # Image.
205             $self->{'tags'}->put(
206             ['b', 'img'],
207             @alt,
208 1         17 ['a', 'src', $self->{'_image_url'}],
209             ['e', 'img'],
210             );
211              
212             # Image comment.
213 1 50       125 if (@{$self->{'_image_comment_tags'}}) {
  1         3  
214             $self->{'tags'}->put(
215             ['b', 'figcaption'],
216 0         0 @{$self->{'_image_comment_tags'}},
  0         0  
217             ['e', 'figcaption'],
218             );
219             }
220              
221             # End of image title.
222 1 50       4 if (defined $self->{'title'}) {
223 0         0 $self->{'tags'}->put(
224             ['e', 'fieldset'],
225             );
226             }
227              
228             # End of figure.
229 1         4 $self->{'tags'}->put(
230             ['e', 'figure'],
231             );
232              
233 1         25 return;
234             }
235              
236             sub _process_css {
237 2     2   33 my $self = shift;
238              
239 2 100       9 if (! exists $self->{'_image'}) {
240 1         4 return;
241             }
242              
243 1         21 my $calc;
244 1 50       6 if (! defined $self->{'img_width'}) {
245 1         4 $calc .= 'calc(100vh';
246 1 50       4 if (defined $self->{'fit_minus'}) {
247 0         0 $calc .= ' - '.$self->{'fit_minus'};
248             }
249 1         3 $calc .= ')';
250             }
251              
252             $self->{'css'}->put(
253             ['s', '.'.$self->{'css_class'}.' img'],
254             ['d', 'display', 'block'],
255             ['d', 'height', '100%'],
256             ['d', 'width', '100%'],
257             ['d', 'object-fit', 'contain'],
258             ['e'],
259              
260             ['s', '.'.$self->{'css_class'}],
261             defined $self->{'img_width'} ? (
262             ['d', 'width', $self->{'img_width'}],
263             ) : (
264             ['d', 'height', $calc],
265             ),
266             ['e'],
267              
268 1         3 @{$self->{'_image_comment_css'}},
269              
270 1 50       25 @{$self->{'_image_select_css'}},
  1         20  
271             );
272              
273 1         345 return;
274             }
275              
276             1;
277              
278             __END__
279              
280             =pod
281              
282             =encoding utf8
283              
284             =head1 NAME
285              
286             Tags::HTML::Image - Tags helper class for image presentation.
287              
288             =head1 SYNOPSIS
289              
290             use Tags::HTML::Image;
291              
292             my $obj = Tags::HTML::Image->new(%params);
293             $obj->cleanup;
294             $obj->init($image);
295             $obj->prepare;
296             $obj->process;
297             $obj->process_css;
298              
299             =head1 METHODS
300              
301             =head2 C<new>
302              
303             my $obj = Tags::HTML::Image->new(%params);
304              
305             Constructor.
306              
307             =over 8
308              
309             =item * C<css_class>
310              
311             Image CSS class.
312              
313             Default value is 'image'.
314              
315             =item * C<css_comment_height>
316              
317             Image comment height (in pixels).
318              
319             Default value is 50.
320              
321             =item * C<fit_minus>
322              
323             Length to minus of image fit.
324              
325             Default value is undef.
326              
327             =item * C<img_comment_cb>
328              
329             Image comment callback.
330              
331             Default value is undef.
332              
333             =item * C<img_select_cb>
334              
335             Image select callback.
336              
337             Default value is undef.
338              
339             =item * C<img_src_cb>
340              
341             Image src callback across data object.
342              
343             Default value is undef.
344              
345             =item * C<img_width>
346              
347             Image width in pixels.
348              
349             Default value is undef.
350              
351             =item * C<tags>
352              
353             'L<Tags::Output>' object.
354              
355             Default value is undef.
356              
357             =item * C<title>
358              
359             Image title.
360              
361             Default value is undef.
362              
363             =back
364              
365             Returns instance of object.
366              
367             =head2 C<cleanup>
368              
369             $obj->cleanup;
370              
371             Process cleanup after page run.
372              
373             Returns undef.
374              
375             =head2 C<init>
376              
377             $obj->init($image);
378              
379             Process initialization in page run.
380              
381             Take L<Data::Image> object as C<$image>,
382              
383             Returns undef.
384              
385             =head2 C<prepare>
386              
387             $obj->prepare;
388              
389             Process initialization before page run.
390              
391             It is not used in this module.
392              
393             Returns undef.
394              
395             =head2 C<process>
396              
397             $obj->process;
398              
399             Process L<Tags> structure for output with hello world message.
400              
401             Returns undef.
402              
403             =head2 C<process_css>
404              
405             $obj->process_css;
406              
407             Process L<CSS::Struct> structure.
408              
409             Returns undef.
410              
411             =head1 ERRORS
412              
413             new():
414             From Class::Utils::set_params():
415             Unknown parameter '%s'.
416             From Mo::utils::check_code():
417             Parameter 'img_comment_cb' must be a code.
418             Parameter 'img_select_cb' must be a code.
419             Parameter 'img_src_cb' must be a code.
420             From Mo::utils::CSS::check_css_class():
421             Parameter 'css_class' has bad CSS class name.
422             Value: %s
423             Parameter 'css_class' has bad CSS class name (number on begin).
424             Value: %s
425             From Tags::HTML::new():
426             Parameter 'css' must be a 'CSS::Struct::Output::*' class.
427             Parameter 'tags' must be a 'Tags::Output::*' class.
428              
429             init():
430             Image object is required.
431             Image object must be a instance of 'Data::Image'.
432             No image URL.
433              
434             process():
435             From Tags::HTML::process():
436             Parameter 'tags' isn't defined.
437              
438             process_css():
439             From Tags::HTML::process_css():
440             Parameter 'css' isn't defined.
441              
442             =head1 EXAMPLE1
443              
444             =for comment filename=create_image_and_print_html.pl
445              
446             use strict;
447             use warnings;
448              
449             use CSS::Struct::Output::Indent;
450             use Data::Image;
451             use DateTime;
452             use Tags::HTML::Image;
453             use Tags::Output::Indent;
454              
455             # Object.
456             my $css = CSS::Struct::Output::Indent->new;
457             my $tags = Tags::Output::Indent->new;
458             my $obj = Tags::HTML::Image->new(
459             'css' => $css,
460             'tags' => $tags,
461             );
462              
463             # Definition of image.
464             my $image = Data::Image->new(
465             'author' => 'Zuzana Zonova',
466             'comment' => 'Michal from Czechia',
467             'dt_created' => DateTime->new(
468             'day' => 1,
469             'month' => 1,
470             'year' => 2022,
471             ),
472             'height' => 2730,
473             'size' => 1040304,
474             'url' => 'https://upload.wikimedia.org/wikipedia/commons/a/a4/Michal_from_Czechia.jpg',
475             'width' => 4096,
476             );
477              
478             # Init.
479             $obj->init($image);
480              
481             # Process HTML and CSS.
482             $obj->process;
483             $obj->process_css;
484              
485             # Print out.
486             print "HTML:\n";
487             print $tags->flush;
488             print "\n\n";
489             print "CSS:\n";
490             print $css->flush;
491              
492             # Output:
493             # HTML:
494             # <figure class="image">
495             # <img alt="Michal from Czechia" src=
496             # "https://upload.wikimedia.org/wikipedia/commons/a/a4/Michal_from_Czechia.jpg"
497             # >
498             # </img>
499             # <figcaption>
500             # Michal from Czechia
501             # </figcaption>
502             # </figure>
503             #
504             # CSS:
505             # .image img {
506             # display: block;
507             # height: 100%;
508             # width: 100%;
509             # object-fit: contain;
510             # }
511             # .image {
512             # height: calc(100vh);
513             # }
514             # .image figcaption {
515             # position: absolute;
516             # bottom: 0;
517             # background: rgb(0, 0, 0);
518             # background: rgba(0, 0, 0, 0.5);
519             # color: #f1f1f1;
520             # width: 100%;
521             # transition: .5s ease;
522             # opacity: 0;
523             # font-size: 25px;
524             # padding: 12.5px 5px;
525             # text-align: center;
526             # }
527             # figure.image:hover figcaption {
528             # opacity: 1;
529             # }
530              
531             =head1 EXAMPLE2
532              
533             =for comment filename=plack_app_image.pl
534              
535             use strict;
536             use warnings;
537            
538             use CSS::Struct::Output::Indent;
539             use Data::Image;
540             use DateTime;
541             use Plack::App::Tags::HTML;
542             use Plack::Runner;
543             use Tags::Output::Indent;
544            
545             my $image = Data::Image->new(
546             'author' => 'Zuzana Zonova',
547             'comment' => 'Michal from Czechia',
548             'dt_created' => DateTime->new(
549             'day' => 1,
550             'month' => 1,
551             'year' => 2022,
552             ),
553             'height' => 2730,
554             'size' => 1040304,
555             'url' => 'https://upload.wikimedia.org/wikipedia/commons/a/a4/Michal_from_Czechia.jpg',
556             'width' => 4096,
557             );
558            
559             my $app = Plack::App::Tags::HTML->new(
560             'component' => 'Tags::HTML::Image',
561             'css' => CSS::Struct::Output::Indent->new,
562             'data_init' => [$image],
563             'tags' => Tags::Output::Indent->new(
564             'xml' => 1,
565             'preserved' => ['style'],
566             ),
567             'title' => 'Image',
568             )->to_app;
569             Plack::Runner->new->run($app);
570              
571             # Output (GET /):
572             # <!DOCTYPE html>
573             # <html lang="en">
574             # <head>
575             # <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
576             # <meta name="viewport" content="width=device-width, initial-scale=1.0" />
577             # <title>
578             # Image
579             # </title>
580             # <style type="text/css">
581             # * {
582             # box-sizing: border-box;
583             # margin: 0;
584             # padding: 0;
585             # }
586             # .image img {
587             # display: block;
588             # height: 100%;
589             # width: 100%;
590             # object-fit: contain;
591             # }
592             # .image {
593             # height: calc(100vh);
594             # }
595             # .image figcaption {
596             # position: absolute;
597             # bottom: 0;
598             # background: rgb(0, 0, 0);
599             # background: rgba(0, 0, 0, 0.5);
600             # color: #f1f1f1;
601             # width: 100%;
602             # transition: .5s ease;
603             # opacity: 0;
604             # font-size: 25px;
605             # padding: 12.5px 5px;
606             # text-align: center;
607             # }
608             # figure.image:hover figcaption {
609             # opacity: 1;
610             # }
611             # </style>
612             # </head>
613             # <body>
614             # <figure class="image">
615             # <img alt="Michal from Czechia" src=
616             # "https://upload.wikimedia.org/wikipedia/commons/a/a4/Michal_from_Czechia.jpg"
617             # />
618             # <figcaption>
619             # Michal from Czechia
620             # </figcaption>
621             # </figure>
622             # </body>
623             # </html>
624              
625             =head1 DEPENDENCIES
626              
627             L<Class::Utils>,
628             L<Error::Pure>,
629             L<Mo::utils>,
630             L<Mo::utils::CSS>,
631             L<Scalar::Util>,
632             L<Tags::HTML>.
633              
634             =head1 REPOSITORY
635              
636             L<https://github.com/michal-josef-spacek/Tags-HTML-Image>
637              
638             =head1 AUTHOR
639              
640             Michal Josef Špaček L<mailto:skim@cpan.org>
641              
642             L<http://skim.cz>
643              
644             =head1 LICENSE AND COPYRIGHT
645              
646             © 2022-2024 Michal Josef Špaček
647              
648             BSD 2-Clause License
649              
650             =head1 VERSION
651              
652             0.04
653              
654             =cut