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