File Coverage

blib/lib/IIIF/Request.pm
Criterion Covered Total %
statement 100 100 100.0
branch 69 74 93.2
condition 55 64 85.9
subroutine 11 11 100.0
pod 5 6 83.3
total 240 255 94.1


line stmt bran cond sub pod time code
1             package IIIF::Request;
2 4     4   198218 use 5.014001;
  4         27  
3              
4             our $VERSION = "0.06";
5              
6 4     4   1730 use Plack::Util::Accessor qw(region size rotation quality format);
  4         1003  
  4         21  
7 4     4   231 use Carp qw(croak);
  4         7  
  4         163  
8 4     4   21 use List::Util qw(min);
  4         6  
  4         1278  
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   2892 use overload '""' => \&as_string, fallback => 1;
  4         2448  
  4         25  
20              
21             sub new {
22 86     86 1 28263 my $class = shift;
23 86   100     174 my $path = shift // "";
24              
25             my (
26 86         125 $rotation, $mirror, $degree, $quality, $format,
27             $region, $region_pct, $region_px, $size, $upscale,
28             $size_px, $size_pct, $ratio
29             );
30              
31 86         193 my @parts = split '/', $path;
32              
33 86 100 100     850 if ( @parts && $parts[0] =~ /^$REGION$/ ) {
34 33         55 $region = shift @parts;
35 33 100       100 if ($1) {
    100          
36 12         39 $region_px = [ split ',', $1 ];
37             }
38             elsif ($2) {
39 8         27 $region_pct = [ map { 1 * $_ } split ',', $2 ];
  32         82  
40             error("disallowed percentage value")
41             if !$region_pct->[2]
42             || !$region_pct->[3]
43 8 100 33     155 || grep { $_ > 100 } @$region_pct;
  32   66     68  
44             }
45             }
46              
47 85 100 100     943 if ( @parts && $parts[0] =~ /^$SIZE$/ ) {
48 36         65 $size = shift @parts;
49 36         63 $upscale = $1;
50 36         47 $ratio = $7;
51 36 100 100     231 $size_px = [ split ',', $5 // $6 // $8 ] if $5 // $6 // $8;
      66        
      100        
      100        
52              
53 36 100       152 if ( defined $3 ) {
54 11         29 $size_pct = 1 * $3;
55 11 100       23 if ($upscale) {
56 1         2 $size = "^pct:$size_pct";
57             }
58             else {
59 10 100 100     48 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 83 100 100     285 if ( @parts && $parts[0] =~ /^$ROTATION$/ ) {
67 9         16 shift @parts;
68 9         21 $mirror = !!$1;
69              
70             # normalize to 0...<360 with up to 6 decimal points
71 9         91 $degree = 1 * sprintf( "%.6f", $2 - int( $2 / 360 ) * 360 );
72 9 100       34 $rotation = $mirror ? "!$degree" : "$degree";
73             }
74              
75 83 100 100     303 if ( @parts && $parts[0] =~ /^(($QUALITY)([.]($FORMAT))?|[.]($FORMAT))$/ ) {
76 7         17 $quality = $2;
77 7   66     30 $format = $4 // $5;
78 7         12 shift @parts;
79             }
80              
81 83 100       130 error( "failed to parse '" . join( '/', '', @parts ) . "'" )
82             if @parts;
83              
84 80   100     915 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 516 croak "Invalid IIIF Image API Request: $_[0]";
103             }
104              
105             sub canonical {
106 23     23 1 70 my ( $self, $width, $height ) = @_;
107              
108             # convert region to /full|x,y,w,h/
109 23         55 my $region = $self->{region};
110 23 100       58 if ( $self->{region} eq 'square' ) {
    100          
    100          
111 2         9 my $size = min( $width, $height );
112 2         7 $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         6 $y = pct2px( $y, $height );
118 3 100       4 $w = pct2px( $w, $width ) or return;
119 2 50       3 $h = pct2px( $h, $height ) or return;
120 2         6 $region = "$x,$y,$w,$h";
121             }
122             elsif ( $self->{region_px} ) { # region outside of image dimensions?
123 5         8 my ( $x, $y, $w, $h ) = @{ $self->{region_px} };
  5         11  
124 5 100 66     17 return if $x >= $width && $y >= $height;
125             }
126 21 100       47 $region = 'full' if $region eq "0,0,$width,$height";
127              
128             # proceed with region size
129 21 100       34 if ( $region ne 'full' ) {
130 5         12 ( undef, undef, $width, $height ) = split ',', $region;
131             }
132              
133 21 100       33 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 18         24 my $size = $self->{size};
141 18         19 my $upscale = $self->{upscale};
142 18 100       54 if ( $size !~ /\^?max/ ) { # TODO: respect maxWidth, maxHeight, maxArea
143 10 100       18 if ( $self->{size_pct} ) {
144             $size = join ',',
145 2         5 map { pct2px( $self->{size_pct}, $_ ) } ( $width, $height );
  4         4  
146 2 50       70 $size = "^$size" if $upscale;
147             }
148             else {
149 8         8 my ( $w, $h ) = @{ $self->{size_px} };
  8         15  
150 8 50 66     16 return if !$w && !$h;
151 8 100 66     167 return if !$upscale && ( $h > $height || $w > $width );
      66        
152              
153 7 100 100     22 if ( $w && $h ) {
    100          
    50          
154 4 100       9 if ( $self->{ratio} ) {
155 3 100       9 if ( $w / $h > $width / $height ) {
156 1         4 $w = pct2px( 100 * $width / $height, $h );
157             }
158             else {
159 2         5 $h = pct2px( 100 * $height / $width, $w );
160             }
161             }
162              
163 4         7 $size = "$w,$h";
164             }
165             elsif ($w) {
166 2         6 $size = "$w," . pct2px( 100 * $height / $width, $w );
167             }
168             elsif ($h) {
169 1         4 $size = pct2px( 100 * $width / $height, $h ) . ",$h";
170             }
171              
172 7 100       13 $size = "^$size" if $upscale;
173             }
174              
175 9 100       57 $size = "max" if $size =~ /^\^?$width,$height$/;
176             }
177              
178 17         60 my $str = join '/', $region, $size, $self->{rotation}, $self->{quality};
179 17 50       55 return defined $self->{format} ? "$str.$self->{format}" : $str;
180             }
181              
182             sub pct2px {
183 29     29 0 43 my ( $percent, $value ) = @_;
184 29         114 return int( $percent * $value / 100 + 0.5 );
185             }
186              
187             sub is_default {
188 1     1 1 3 my ($self) = @_;
189              
190 1         4 return $self->as_string =~ qr{^full/max/0/default};
191             }
192              
193             sub as_string {
194 90     90 1 6471 my ($self) = @_;
195              
196 90         145 my $str = join '/', map { $self->{$_} } qw(region size rotation quality);
  360         663  
197 90 100       544 return defined $self->{format} ? "$str.$self->{format}" : $str;
198             }
199              
200             1;
201             __END__