File Coverage

blib/lib/Graphics/ColorNames.pm
Criterion Covered Total %
statement 149 177 84.1
branch 53 76 69.7
condition 17 27 62.9
subroutine 29 31 93.5
pod 6 7 85.7
total 254 318 79.8


line stmt bran cond sub pod time code
1             package Graphics::ColorNames;
2 7     7   5433 use 5.006;
  7         29  
3              
4             # ABSTRACT: defines RGB values for common color names
5              
6 7     7   38 use strict;
  7         16  
  7         141  
7 7     7   35 use warnings;
  7         21  
  7         186  
8              
9 7     7   2799 use version;
  7         12855  
  7         48  
10              
11 7     7   643 use Exporter qw/ import /;
  7         17  
  7         258  
12              
13             # use AutoLoader;
14 7     7   44 use Carp;
  7         14  
  7         465  
15 7     7   2681 use File::Spec::Functions qw/ file_name_is_absolute /;
  7         4689  
  7         440  
16 7     7   3219 use Module::Load 0.10;
  7         7736  
  7         81  
17 7     7   3169 use Module::Loaded;
  7         4448  
  7         11717  
18              
19             our $VERSION = 'v3.3.4';
20              
21             our %EXPORT_TAGS = (
22             'all' => [qw( hex2tuple tuple2hex all_schemes )],
23             'utility' => [qw( hex2tuple tuple2hex )],
24             );
25             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
26             our @EXPORT = ();
27              
28             sub VERSION {
29 4     4 0 324 my ( $class, $wanted ) = @_;
30 4         235 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             # If we use AutoLoader, these should be use vars() ?
38              
39             my %FoundSchemes = ();
40              
41             # Since 2.10_02, we've added autoloading color names to the object-
42             # oriented interface.
43              
44             our $AUTOLOAD;
45              
46             sub AUTOLOAD {
47 0     0   0 $AUTOLOAD =~ /^(.*:)*([\w\_]+)$/;
48 0         0 my $name = $2;
49 0         0 my $hex = ( my $self = $_[0] )->FETCH($name);
50 0 0       0 if ( defined $hex ) {
51 0         0 return $hex;
52             }
53             else {
54 0         0 croak "No method or color named $name";
55              
56             # $AutoLoader::AUTOLOAD = $AUTOLOAD;
57             # goto &AutoLoader::AUTOLOAD;
58             }
59             }
60              
61             sub _load {
62 13     13   40 while ( my $module = shift ) {
63 14 100       60 unless ( is_loaded($module) ) {
64 8         176 load($module);
65 8 50       12358 mark_as_loaded($module) unless ( is_loaded($module) );
66             }
67             }
68             }
69              
70             # TODO - see if using Tie::Hash::Layered gives an improvement
71              
72             sub _load_scheme_from_module {
73 11     11   24 my ($self, $scheme) = @_;
74              
75 11 50       46 my $module =
    50          
76             $scheme =~ /^\+/ ? substr( $scheme, 1 )
77             : $scheme =~ /^Color::Library::Dictionary::/ ? $scheme
78             : __PACKAGE__ . '::' . $scheme;
79              
80 11         22 eval { _load($module); };
  11         24  
81 11 50       256 if ($@) {
82 0         0 croak "Cannot load color naming scheme module $module";
83             }
84              
85 11 50       96 if ($module->can('NamesRgbTable')) {
    0          
86 11         37 $self->load_scheme( $module->NamesRgbTable );
87             }
88             elsif ($module->can('_load_color_list')) {
89 0         0 $self->load_scheme( $module->_load_color_list );
90             }
91             else {
92 0         0 croak "Unknown scheme type: $module";
93             }
94             }
95              
96             sub TIEHASH {
97 13   50 13   4311 my $class = shift || __PACKAGE__;
98 13         43 my $self = {
99             _schemes => [],
100             _iterator => 0,
101             };
102              
103 13         30 bless $self, $class;
104              
105 13 100       42 if (@_) {
106 11         26 foreach my $scheme (@_) {
107 15 100       100 if ( ref $scheme ) {
    100          
    100          
108 3         8 $self->load_scheme($scheme);
109             }
110             elsif ($scheme =~ /^\+?(?:\w+[:][:])*\w+$/) {
111 9         25 $self->_load_scheme_from_module($scheme);
112             }
113             elsif ( file_name_is_absolute($scheme) ) {
114 1         10 $self->_load_scheme_from_file($scheme);
115             }
116             else {
117 2         37 croak "Unknown color scheme: $scheme";
118             }
119             }
120             }
121             else {
122 2         5 $self->_load_scheme_from_module('X');
123             }
124              
125 11         44 return $self;
126             }
127              
128             sub FETCH {
129 6934     6934   22661 my $self = shift;
130 6934   50     13948 my $key = lc( shift || "" );
131              
132             # If we're passing it an RGB value, return that value
133              
134 6934 100       20391 if ( $key =~ m/^(?:\x23|0x)?([0-9a-f]{6})$/ ) {
135 1402         3813 return $1;
136             }
137             else {
138              
139 5532         10623 $key =~ s/[^0-9a-z\%]//g; # ignore non-word characters
140              
141 5532         7326 my $val = undef;
142 5532         7126 my $i = 0;
143 5532   100     10892 while ( ( !defined $val ) && ( $i < @{ $self->{_schemes} } ) ) {
  5543         15171  
144 5535         14121 $val = $self->{_schemes}->[ $i++ ]->{$key};
145             }
146              
147 5532 100       8829 if ( defined $val ) {
148 5524         18376 return sprintf( '%06x', $val ),;
149             }
150             else {
151 8         33 return;
152             }
153             }
154             }
155              
156             sub EXISTS {
157 706     706   10134 my ( $self, $key ) = @_;
158 706         1148 defined( $self->FETCH($key) );
159             }
160              
161             sub FIRSTKEY {
162 23     23   3644 ( my $self = shift )->{_iterator} = 0;
163 23         36 each %{ $self->{_schemes}->[ $self->{_iterator} ] };
  23         114  
164             }
165              
166             sub NEXTKEY {
167 7508     7508   10347 my $self = shift;
168 7508         9124 my ( $key, $val ) = each %{ $self->{_schemes}->[ $self->{_iterator} ] };
  7508         13894  
169 7508 100       13476 unless ( defined $key ) {
170 23         36 ( $key, $val ) = each %{ $self->{_schemes}->[ ++$self->{_iterator} ] };
  23         56  
171             }
172 7508         15747 return $key;
173             }
174              
175             sub load_scheme {
176 19     19 1 80 my $self = shift;
177 19         41 my $scheme = shift;
178              
179 19 100       71 if ( ref($scheme) eq "HASH" ) {
    50          
    50          
180 16         31 push @{ $self->{_schemes} }, $scheme;
  16         106  
181             }
182             elsif ( ref($scheme) eq "CODE" ) {
183 0         0 _load("Tie::Sub");
184 0         0 push @{ $self->{_schemes} }, {};
  0         0  
185 0         0 tie %{ $self->{_schemes}->[-1] }, 'Tie::Sub', $scheme;
  0         0  
186             }
187             elsif ( ref($scheme) eq "ARRAY" ) {
188              
189             # assumes these are Color::Library::Dictionary 0.02 files
190 0         0 my $s = {};
191 0         0 foreach my $rec (@$scheme) {
192 0         0 my $key = $rec->[0];
193 0         0 my $name = $rec->[1];
194 0         0 my $code = $rec->[5];
195 0         0 $name =~ s/[\W\_]//g; # ignore non-word characters
196 0 0       0 $s->{$name} = $code unless ( exists $s->{$name} );
197 0 0       0 if ( $key =~ /^(.+\:.+)\.([0-9]+)$/ ) {
198 0         0 $s->{"$name$2"} = $code;
199             }
200             }
201 0         0 push @{ $self->{_schemes} }, $s;
  0         0  
202             }
203             else {
204             # TODO - use Exception
205 3         8 undef $!;
206 3         6 eval {
207 3 0 100     29 if ( ( ref($scheme) eq 'GLOB' )
      66        
      33        
      33        
208             || ref($scheme) eq "IO::File"
209             || $scheme->isa('IO::File')
210             || ref($scheme) eq "FileHandle"
211             || $scheme->isa('FileHandle') )
212             {
213 3         7 $self->_load_scheme_from_file($scheme);
214             }
215             };
216 3 50       15 if ($@) {
    50          
217 0         0 croak "Error $@ on scheme type ", ref($scheme);
218             }
219             elsif ($!) {
220 0         0 croak "$!";
221             }
222             else {
223             # everything is ok?
224             }
225             }
226             }
227              
228             sub _find_schemes {
229              
230 10     10   22 my $path = shift;
231              
232             # BUG: deep-named schemes such as Graphics::ColorNames::Foo::Bar
233             # are not supported.
234              
235 10 100       159 if ( -d $path ) {
236 3   33     14 my $dh = DirHandle->new($path)
237             || croak "Unable to access directory $path";
238 3         186 while ( defined( my $fn = $dh->read ) ) {
239 9 100 66     328 if ( ( -r File::Spec->catdir( $path, $fn ) )
240             && ( $fn =~ /(.+)\.pm$/ ) )
241             {
242 3         21 $FoundSchemes{$1}++;
243             }
244             }
245             }
246             }
247              
248             sub _readonly_error {
249 4     4   188 croak "Cannot modify a read-only value";
250             }
251              
252             sub DESTROY {
253 13     13   6831 my $self = shift;
254 13         407 delete $self->{_schemes};
255 13         821 delete $self->{_iterator};
256             }
257              
258       0     sub UNTIE { # stub to avoid AUTOLOAD
259             }
260              
261             BEGIN {
262 7     7   67 no strict 'refs';
  7         20  
  7         528  
263 7     7   31 *STORE = \&_readonly_error;
264 7         20 *DELETE = \&_readonly_error;
265 7         16 *CLEAR = \&_readonly_error; # causes problems with 'undef'
266              
267 7         4370 *new = \&TIEHASH;
268             }
269              
270             # Convert 6-digit hexidecimal code (used for HTML etc.) to an array of
271             # RGB values
272              
273             sub hex2tuple {
274 1390     1390 1 12378 my $rgb = CORE::hex(shift);
275 1390         2370 my ( $red, $green, $blue );
276 1390         1931 $blue = ( $rgb & 0x0000ff );
277 1390         1856 $green = ( $rgb & 0x00ff00 ) >> 8;
278 1390         1749 $red = ( $rgb & 0xff0000 ) >> 16;
279 1390         2852 return ( $red, $green, $blue );
280             }
281              
282             # Convert list of RGB values to 6-digit hexidecimal code (used for HTML, etc.)
283              
284             sub tuple2hex {
285 1378     1378 1 4740 my ( $red, $green, $blue ) = @_;
286 1378         2977 my $rgb = sprintf "%.2x%.2x%.2x", $red, $green, $blue;
287 1378         4224 return $rgb;
288             }
289              
290             sub all_schemes {
291 1 50   1 1 512 unless (%FoundSchemes) {
292              
293 1         3 _load( "DirHandle", "File::Spec" );
294              
295 1         16 foreach my $dir (@INC) {
296 10         203 _find_schemes(
297             File::Spec->catdir( $dir, split( /::/, __PACKAGE__ ) ) );
298             }
299             }
300 1         11 return ( keys %FoundSchemes );
301             }
302              
303             sub _load_scheme_from_file {
304 4     4   7 my $self = shift;
305 4         7 my $file = shift;
306              
307 4 100       9 unless ( ref $file ) {
308 1 50       22 unless ( -r $file ) {
309 0         0 croak "Cannot load scheme from file: \'$file\'";
310             }
311 1         4 _load("IO::File");
312             }
313              
314 4 100       34 my $fh = ref($file) ? $file : ( IO::File->new );
315 4 100       42 unless ( ref $file ) {
316 1 50       45 open( $fh, $file )
317             || croak "Cannot open file: \'$file\'";
318             }
319              
320 4         10 my $scheme = {};
321              
322 4         68 while ( my $line = <$fh> ) {
323 40         79 chomp($line);
324 40         113 $line =~ s/[\!\#].*$//;
325 40 100       110 if ( $line ne "" ) {
326 24         56 my $name = lc( substr( $line, 12 ) );
327 24         103 $name =~ s/[\W]//g; # remove anything that isn't a letter or number
328              
329 24 50       57 croak "Missing color name",
330             unless ( $name ne "" );
331              
332             # TODO? Should we add an option to warn if overlapping names
333             # are defined? This seems to be too common to be useful.
334              
335             # unless (exists $scheme->{$name}) {
336              
337 24         51 $scheme->{$name} = 0;
338 24         45 foreach ( 0, 4, 8 ) {
339 72         130 $scheme->{$name} <<= 8;
340 72         2580 $scheme->{$name} |= ( eval substr( $line, $_, 3 ) );
341             }
342              
343             # }
344             }
345             }
346 4         22 $self->load_scheme($scheme);
347              
348 4 100       17 unless ( ref $file ) {
349 1         15 close $fh;
350             }
351             }
352              
353             sub hex {
354 15     15 1 4647 my ($self, $name, $prefix) = @_;
355 15         34 my $rgb = $self->FETCH($name);
356              
357 15 100       58 return '' unless defined $rgb;
358              
359 12 100       62 return $rgb unless defined $prefix;
360              
361 2         12 return $prefix . $rgb;
362             }
363              
364             sub rgb {
365 15     15 1 2672 my ($self, $name, $separator) = @_;
366 15         33 my $rgb = $self->FETCH($name);
367 15 100       44 my @rgb = (defined $rgb) ? hex2tuple( $rgb ) : ();
368 15 100 100     166 return wantarray ? @rgb : join($separator || ',', @rgb);
369             }
370              
371             1;
372              
373             __END__