File Coverage

blib/lib/IIIF/Request.pm
Criterion Covered Total %
statement 111 111 100.0
branch 77 82 93.9
condition 64 73 87.6
subroutine 11 11 100.0
pod 5 6 83.3
total 268 283 94.7


line stmt bran cond sub pod time code
1             package IIIF::Request;
2 4     4   178599 use 5.014001;
  4         30  
3              
4             our $VERSION = "0.07";
5              
6 4     4   1633 use Plack::Util::Accessor qw(region size rotation quality format);
  4         993  
  4         22  
7 4     4   241 use Carp qw(croak);
  4         8  
  4         171  
8 4     4   22 use List::Util qw(min);
  4         7  
  4         1300  
9              
10             our $XY = qr{[0-9]+}; # non-negative integer
11             our $WH = qr{[1-9][0-9]*}; # positive integer
12             our $NUM = qr{[0-9]*(\.[0-9]+)?}; # non-negative
13             our $REGION = qr{full|square|($XY,$XY,$WH,$WH)|pct:($NUM,$NUM,$NUM,$NUM)};
14             our $SIZE = qr{(\^)?(max|pct:($NUM)|($WH,)|(,$WH)|(!)?($WH,$WH))};
15             our $ROTATION = qr{([!])?($NUM)};
16             our $QUALITY = qr{color|gray|bitonal|default};
17             our $FORMAT = qr{[^.]+};
18              
19 4     4   3138 use overload '""' => \&as_string, fallback => 1;
  4         2579  
  4         24  
20              
21             sub new {
22 96     96 1 24085 my $class = shift;
23 96   100     205 my $path = shift // "";
24              
25             my (
26 96         139 $rotation, $mirror, $degree, $quality, $format,
27             $region, $region_pct, $region_px, $size, $upscale,
28             $size_px, $size_pct, $ratio
29             );
30              
31 96         235 my @parts = split '/', $path;
32              
33 96 100 100     986 if ( @parts && $parts[0] =~ /^$REGION$/ ) {
34 33         57 $region = shift @parts;
35 33 100       110 if ($1) {
    100          
36 12         41 $region_px = [ split ',', $1 ];
37             }
38             elsif ($2) {
39 8         26 $region_pct = [ map { 1 * $_ } split ',', $2 ];
  32         81  
40             error("disallowed percentage value")
41             if !$region_pct->[2]
42             || !$region_pct->[3]
43 8 100 33     50 || grep { $_ > 100 } @$region_pct;
  32   66     70  
44             }
45             }
46              
47 95 100 100     643 if ( @parts && $parts[0] =~ /^$SIZE$/ ) {
48 46         78 $size = shift @parts;
49 46         83 $upscale = $1;
50 46         67 $ratio = $7;
51 46 100 100     300 $size_px = [ split ',', $5 // $6 // $8 ] if $5 // $6 // $8;
      66        
      100        
      100        
52              
53 46 100       103 if ( defined $3 ) {
54 11         32 $size_pct = 1 * $3;
55 11 100       24 if ($upscale) {
56 1         3 $size = "^pct:$size_pct";
57             }
58             else {
59 10 100 100     45 error("disallowed percentage value")
60             if $size_pct == 0.0 || $size_pct > 100.0;
61 8         30 $size = "pct:$size_pct";
62             }
63             }
64             }
65              
66 93 100 100     317 if ( @parts && $parts[0] =~ /^$ROTATION$/ ) {
67 9         19 shift @parts;
68 9         24 $mirror = !!$1;
69              
70             # normalize to 0...<360 with up to 6 decimal points
71 9         108 $degree = 1 * sprintf( "%.6f", $2 - int( $2 / 360 ) * 360 );
72 9 100       37 $rotation = $mirror ? "!$degree" : "$degree";
73             }
74              
75 93 100 100     356 if ( @parts && $parts[0] =~ /^(($QUALITY)([.]($FORMAT))?|[.]($FORMAT))$/ ) {
76 7         22 $quality = $2;
77 7   66     41 $format = $4 // $5;
78 7         13 shift @parts;
79             }
80              
81 93 100       146 error( "failed to parse '" . join( '/', '', @parts ) . "'" )
82             if @parts;
83              
84 90   100     991 bless {
      100        
      100        
      100        
85             region => $region // 'full',
86             region_pct => $region_pct,
87             region_px => $region_px,
88             size => $size // 'max',
89             upscale => $upscale,
90             size_pct => $size_pct,
91             size_px => $size_px,
92             ratio => $ratio,
93             rotation => $rotation // '0',
94             mirror => $mirror,
95             degree => $degree,
96             quality => $quality // 'default',
97             format => $format
98             }, $class;
99             }
100              
101             sub error {
102 6     6 1 615 croak "Invalid IIIF Image API Request: $_[0]";
103             }
104              
105             sub canonical {
106 30     30 1 100 my ( $self, $width, $height, %max ) = @_;
107              
108             # convert region to /full|x,y,w,h/
109 30         69 my $region = $self->{region};
110 30 100       81 if ( $self->{region} eq 'square' ) {
    100          
    100          
111 2         7 my $size = min( $width, $height );
112 2         5 $region = "0,0,$size,$size";
113             }
114             elsif ( $self->{region_pct} ) {
115 3         4 my ( $x, $y, $w, $h ) = @{ $self->{region_pct} };
  3         7  
116 3         5 $x = pct2px( $x, $width );
117 3         5 $y = pct2px( $y, $height );
118 3 100       5 $w = pct2px( $w, $width ) or return;
119 2 50       5 $h = pct2px( $h, $height ) or return;
120 2         5 $region = "$x,$y,$w,$h";
121             }
122             elsif ( $self->{region_px} ) { # region outside of image dimensions?
123 5         7 my ( $x, $y, $w, $h ) = @{ $self->{region_px} };
  5         8  
124 5 100 66     21 return if $x >= $width && $y >= $height;
125             }
126 28 100       63 $region = 'full' if $region eq "0,0,$width,$height";
127              
128             # proceed with region size
129 28 100       42 if ( $region ne 'full' ) {
130 5         11 ( undef, undef, $width, $height ) = split ',', $region;
131             }
132              
133 28 100       47 if ( $self->{size_pct} ) { # too small
134             return
135             if !pct2px( $self->{size_pct}, $width )
136 5 100 100     10 || !pct2px( $self->{size_pct}, $height );
137             }
138              
139             # convert size to /[^]?(max|w,h)/
140 25         29 my $size = $self->{size};
141 25         32 my $upscale = $self->{upscale};
142 25         27 my $ratio = $self->{ratio};
143 25         42 my $size_px = $self->{size_px};
144              
145 25         29 my $maxHeight = $max{maxHeight};
146 25   100     53 my $maxWidth = $max{maxWidth} || $maxHeight;
147              
148 25 100 100     47 if ( $size eq '^max' && $maxHeight ) {
149 2         3 $size_px = [ $maxWidth, $maxHeight ];
150 2         2 $upscale = 1;
151 2         3 $ratio = 1;
152 2         7 $size = '^!' . join ',', @$size_px;
153             }
154              
155 25 100       74 if ( $size !~ /\^?max/ ) {
156 15 100       21 if ( $self->{size_pct} ) {
157             $size = join ',',
158 2         4 map { pct2px( $self->{size_pct}, $_ ) } ( $width, $height );
  4         5  
159 2 50       6 $size = "^$size" if $upscale;
160             }
161             else {
162 13         26 my ( $w, $h ) = @$size_px;
163 13 50 66     22 return if !$w && !$h;
164 13 100 66     51 return if !$upscale && ( $h > $height || $w > $width );
      66        
165              
166 11 100 100     28 if ( $w && $h ) {
    100          
    50          
167 7 100       11 if ($ratio) {
168 5 100       14 if ( $w / $h > $width / $height ) {
169 2         5 $w = pct2px( 100 * $width / $height, $h );
170             }
171             else {
172 3         8 $h = pct2px( 100 * $height / $width, $w );
173             }
174             }
175              
176 7         14 $size = "$w,$h";
177             }
178             elsif ($w) {
179 3         10 $size = "$w," . pct2px( 100 * $height / $width, $w );
180             }
181             elsif ($h) {
182 1         5 $size = pct2px( 100 * $width / $height, $h ) . ",$h";
183             }
184              
185 11 100       22 $size = "^$size" if $upscale;
186             }
187              
188 13 100       96 $size = "max" if $size =~ /^\^?$width,$height$/;
189             }
190              
191             # give up if image too large
192 23 100       42 if ($maxHeight) {
193 5 100       21 ( $width, $height ) = ( $1, $2 ) if $size =~ /^\^?(\d+),(\d+)$/;
194 5 100 100     29 return if $width > $maxWidth || $height > $maxHeight;
195             }
196              
197 20         42 my $str = join '/', $region, $size, $self->{rotation}, $self->{quality};
198 20 50       70 return defined $self->{format} ? "$str.$self->{format}" : $str;
199             }
200              
201             sub pct2px {
202 32     32 0 44 my ( $percent, $value ) = @_;
203 32         94 return int( $percent * $value / 100 + 0.5 );
204             }
205              
206             sub is_default {
207 1     1 1 3 my ($self) = @_;
208              
209 1         5 return $self->as_string =~ qr{^full/max/0/default};
210             }
211              
212             sub as_string {
213 99     99 1 7725 my ($self) = @_;
214              
215 99         156 my $str = join '/', map { $self->{$_} } qw(region size rotation quality);
  396         699  
216 99 100       669 return defined $self->{format} ? "$str.$self->{format}" : $str;
217             }
218              
219             1;
220             __END__