File Coverage

blib/lib/Graphics/ColorNames.pm
Criterion Covered Total %
statement 149 171 87.1
branch 53 74 71.6
condition 17 27 62.9
subroutine 29 29 100.0
pod 6 7 85.7
total 254 308 82.4


line stmt bran cond sub pod time code
1             package Graphics::ColorNames;
2 7     7   5601 use 5.006;
  7         28  
3              
4             # ABSTRACT: defines RGB values for common color names
5              
6 7     7   40 use strict;
  7         15  
  7         146  
7 7     7   34 use warnings;
  7         20  
  7         189  
8              
9 7     7   73 use v5.10;
  7         24  
10              
11 7     7   45 use Exporter qw/ import /;
  7         15  
  7         275  
12              
13 7     7   40 use Carp;
  7         21  
  7         524  
14 7     7   2672 use File::Spec::Functions qw/ file_name_is_absolute /;
  7         5031  
  7         459  
15 7     7   3387 use Module::Load 0.10;
  7         8225  
  7         44  
16 7     7   3305 use Module::Loaded;
  7         4797  
  7         10922  
17              
18             our $VERSION = 'v3.5.0';
19              
20             our %EXPORT_TAGS = (
21             'all' => [qw( hex2tuple tuple2hex all_schemes )],
22             'utility' => [qw( hex2tuple tuple2hex )],
23             );
24             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
25             our @EXPORT = ();
26              
27             sub VERSION {
28 4     4 0 382 my ( $class, $wanted ) = @_;
29 4         1643 require version;
30 4         7698 return version->parse($VERSION);
31             }
32              
33             # We store Schemes in a hash as a quick-and-dirty way to filter
34             # duplicates (which sometimes occur when directories are repeated in
35             # @INC or via symlinks). The order does not matter.
36              
37             my %FoundSchemes = ();
38              
39             sub _load {
40 13     13   40 while ( my $module = shift ) {
41 14 100       62 unless ( is_loaded($module) ) {
42 8         209 load($module);
43 8 50       12815 mark_as_loaded($module) unless ( is_loaded($module) );
44             }
45             }
46             }
47              
48             # TODO - see if using Tie::Hash::Layered gives an improvement
49              
50             sub _load_scheme_from_module {
51 11     11   55 my ($self, $scheme) = @_;
52              
53 11 50       52 my $module =
    50          
54             $scheme =~ /^\+/ ? substr( $scheme, 1 )
55             : $scheme =~ /^Color::Library::Dictionary::/ ? $scheme
56             : __PACKAGE__ . '::' . $scheme;
57              
58 11         23 eval { _load($module); };
  11         25  
59 11 50       252 if ($@) {
60 0         0 croak "Cannot load color naming scheme module $module";
61             }
62              
63 11 50       101 if ($module->can('NamesRgbTable')) {
    0          
64 11         36 $self->load_scheme( $module->NamesRgbTable );
65             }
66             elsif ($module->can('_load_color_list')) {
67 0         0 $self->load_scheme( $module->_load_color_list );
68             }
69             else {
70 0         0 croak "Unknown scheme type: $module";
71             }
72             }
73              
74             sub TIEHASH {
75 13   50 13   4974 my $class = shift || __PACKAGE__;
76 13         97 my $self = {
77             _schemes => [],
78             _iterator => 0,
79             };
80              
81 13         34 bless $self, $class;
82              
83 13 100       46 if (@_) {
84 11         25 foreach my $scheme (@_) {
85 15 100       108 if ( ref $scheme ) {
    100          
    100          
86 3         9 $self->load_scheme($scheme);
87             }
88             elsif ($scheme =~ /^\+?(?:\w+[:][:])*\w+$/) {
89 9         24 $self->_load_scheme_from_module($scheme);
90             }
91             elsif ( file_name_is_absolute($scheme) ) {
92 1         12 $self->_load_scheme_from_file($scheme);
93             }
94             else {
95 2         42 croak "Unknown color scheme: $scheme";
96             }
97             }
98             }
99             else {
100 2         6 $self->_load_scheme_from_module('X');
101             }
102              
103 11         51 return $self;
104             }
105              
106             sub FETCH {
107 6935     6935   22914 my $self = shift;
108 6935   50     13897 my $key = lc( shift || "" );
109              
110             # If we're passing it an RGB value, return that value
111              
112 6935 100       21070 if ( $key =~ m/^(?:\x23|0x)?([0-9a-f]{6})$/ ) {
113 1402         4091 return $1;
114             }
115             else {
116              
117 5533         10402 $key =~ s/[^0-9a-z\%]//g; # ignore non-word characters
118              
119 5533         7365 my $val = undef;
120 5533         7079 my $i = 0;
121 5533   100     10998 while ( ( !defined $val ) && ( $i < @{ $self->{_schemes} } ) ) {
  5544         14629  
122 5536         13786 $val = $self->{_schemes}->[ $i++ ]->{$key};
123             }
124              
125 5533 100       8723 if ( defined $val ) {
126 5525         19272 return sprintf( '%06x', $val ),;
127             }
128             else {
129 8         36 return;
130             }
131             }
132             }
133              
134             sub EXISTS {
135 706     706   10368 my ( $self, $key ) = @_;
136 706         1194 defined( $self->FETCH($key) );
137             }
138              
139             sub FIRSTKEY {
140 23     23   3870 ( my $self = shift )->{_iterator} = 0;
141 23         41 each %{ $self->{_schemes}->[ $self->{_iterator} ] };
  23         120  
142             }
143              
144             sub NEXTKEY {
145 7508     7508   10150 my $self = shift;
146 7508         9110 my ( $key, $val ) = each %{ $self->{_schemes}->[ $self->{_iterator} ] };
  7508         13843  
147 7508 100       13720 unless ( defined $key ) {
148 23         32 ( $key, $val ) = each %{ $self->{_schemes}->[ ++$self->{_iterator} ] };
  23         61  
149             }
150 7508         15812 return $key;
151             }
152              
153             sub load_scheme {
154 19     19 1 79 my $self = shift;
155 19         33 my $scheme = shift;
156              
157 19 100       71 if ( ref($scheme) eq "HASH" ) {
    50          
    50          
158 16         26 push @{ $self->{_schemes} }, $scheme;
  16         92  
159             }
160             elsif ( ref($scheme) eq "CODE" ) {
161 0         0 _load("Tie::Sub");
162 0         0 push @{ $self->{_schemes} }, {};
  0         0  
163 0         0 tie %{ $self->{_schemes}->[-1] }, 'Tie::Sub', $scheme;
  0         0  
164             }
165             elsif ( ref($scheme) eq "ARRAY" ) {
166              
167             # assumes these are Color::Library::Dictionary 0.02 files
168 0         0 my $s = {};
169 0         0 foreach my $rec (@$scheme) {
170 0         0 my $key = $rec->[0];
171 0         0 my $name = $rec->[1];
172 0         0 my $code = $rec->[5];
173 0         0 $name =~ s/[\W\_]//g; # ignore non-word characters
174 0 0       0 $s->{$name} = $code unless ( exists $s->{$name} );
175 0 0       0 if ( $key =~ /^(.+\:.+)\.([0-9]+)$/ ) {
176 0         0 $s->{"$name$2"} = $code;
177             }
178             }
179 0         0 push @{ $self->{_schemes} }, $s;
  0         0  
180             }
181             else {
182             # TODO - use Exception
183 3         9 undef $!;
184 3         7 eval {
185 3 0 100     35 if ( ( ref($scheme) eq 'GLOB' )
      66        
      33        
      33        
186             || ref($scheme) eq "IO::File"
187             || $scheme->isa('IO::File')
188             || ref($scheme) eq "FileHandle"
189             || $scheme->isa('FileHandle') )
190             {
191 3         10 $self->_load_scheme_from_file($scheme);
192             }
193             };
194 3 50       18 if ($@) {
    50          
195 0         0 croak "Error $@ on scheme type ", ref($scheme);
196             }
197             elsif ($!) {
198 0         0 croak "$!";
199             }
200             else {
201             # everything is ok?
202             }
203             }
204             }
205              
206             sub _find_schemes {
207              
208 10     10   19 my $path = shift;
209              
210             # BUG: deep-named schemes such as Graphics::ColorNames::Foo::Bar
211             # are not supported.
212              
213 10 100       231 if ( -d $path ) {
214 3   33     17 my $dh = DirHandle->new($path)
215             || croak "Unable to access directory $path";
216 3         189 while ( defined( my $fn = $dh->read ) ) {
217 9 100 66     327 if ( ( -r File::Spec->catdir( $path, $fn ) )
218             && ( $fn =~ /(.+)\.pm$/ ) )
219             {
220 3         22 $FoundSchemes{$1}++;
221             }
222             }
223             }
224             }
225              
226             sub _readonly_error {
227 4     4   204 croak "Cannot modify a read-only value";
228             }
229              
230             sub DESTROY {
231 13     13   6742 my $self = shift;
232 13         550 delete $self->{_schemes};
233 13         937 delete $self->{_iterator};
234             }
235              
236             BEGIN {
237 7     7   65 no strict 'refs';
  7         34  
  7         552  
238 7     7   36 *STORE = \&_readonly_error;
239 7         29 *DELETE = \&_readonly_error;
240 7         15 *CLEAR = \&_readonly_error; # causes problems with 'undef'
241              
242 7         4807 *new = \&TIEHASH;
243             }
244              
245             # Convert 6-digit hexidecimal code (used for HTML etc.) to an array of
246             # RGB values
247              
248             sub hex2tuple {
249 1391     1391 1 12104 my $rgb = CORE::hex(shift);
250 1391         2532 my ( $red, $green, $blue );
251 1391         1966 $blue = ( $rgb & 0x0000ff );
252 1391         1872 $green = ( $rgb & 0x00ff00 ) >> 8;
253 1391         1733 $red = ( $rgb & 0xff0000 ) >> 16;
254 1391         2867 return ( $red, $green, $blue );
255             }
256              
257             # Convert list of RGB values to 6-digit hexidecimal code (used for HTML, etc.)
258              
259             sub tuple2hex {
260 1378     1378 1 4692 my ( $red, $green, $blue ) = @_;
261 1378         2904 my $rgb = sprintf "%.2x%.2x%.2x", $red, $green, $blue;
262 1378         4172 return $rgb;
263             }
264              
265             sub all_schemes {
266 1 50   1 1 594 unless (%FoundSchemes) {
267              
268 1         5 _load( "DirHandle", "File::Spec" );
269              
270 1         17 foreach my $dir (@INC) {
271 10         210 _find_schemes(
272             File::Spec->catdir( $dir, split( /::/, __PACKAGE__ ) ) );
273             }
274             }
275 1         12 return ( keys %FoundSchemes );
276             }
277              
278             sub _load_scheme_from_file {
279 4     4   7 my $self = shift;
280 4         10 my $file = shift;
281              
282 4 100       12 unless ( ref $file ) {
283 1 50       24 unless ( -r $file ) {
284 0         0 croak "Cannot load scheme from file: \'$file\'";
285             }
286 1         4 _load("IO::File");
287             }
288              
289 4 100       41 my $fh = ref($file) ? $file : ( IO::File->new );
290 4 100       47 unless ( ref $file ) {
291 1 50       45 open( $fh, $file )
292             || croak "Cannot open file: \'$file\'";
293             }
294              
295 4         11 my $scheme = {};
296              
297 4         96 while ( my $line = <$fh> ) {
298 40         87 chomp($line);
299 40         122 $line =~ s/[\!\#].*$//;
300 40 100       111 if ( $line ne "" ) {
301 24         61 my $name = lc( substr( $line, 12 ) );
302 24         101 $name =~ s/[\W]//g; # remove anything that isn't a letter or number
303              
304 24 50       62 croak "Missing color name",
305             unless ( $name ne "" );
306              
307             # TODO? Should we add an option to warn if overlapping names
308             # are defined? This seems to be too common to be useful.
309              
310             # unless (exists $scheme->{$name}) {
311              
312 24         58 $scheme->{$name} = 0;
313 24         44 foreach ( 0, 4, 8 ) {
314 72         136 $scheme->{$name} <<= 8;
315 72         2547 $scheme->{$name} |= ( eval substr( $line, $_, 3 ) );
316             }
317              
318             # }
319             }
320             }
321 4         26 $self->load_scheme($scheme);
322              
323 4 100       18 unless ( ref $file ) {
324 1         18 close $fh;
325             }
326             }
327              
328             sub hex {
329 15     15 1 4865 my ($self, $name, $prefix) = @_;
330 15         39 my $rgb = $self->FETCH($name);
331              
332 15 100       52 return '' unless defined $rgb;
333              
334 12 100       66 return $rgb unless defined $prefix;
335              
336 2         11 return $prefix . $rgb;
337             }
338              
339             sub rgb {
340 16     16 1 5389 my ($self, $name, $separator) = @_;
341 16         37 my $rgb = $self->FETCH($name);
342 16 100       51 my @rgb = (defined $rgb) ? hex2tuple( $rgb ) : ();
343 16 100 100     143 return wantarray ? @rgb : join($separator || ',', @rgb);
344             }
345              
346             1;
347              
348             __END__