File Coverage

blib/lib/Image/TextMode/Reader/ANSI.pm
Criterion Covered Total %
statement 159 181 87.8
branch 90 114 78.9
condition 20 32 62.5
subroutine 19 20 95.0
pod 16 16 100.0
total 304 363 83.7


line stmt bran cond sub pod time code
1             package Image::TextMode::Reader::ANSI;
2              
3 8     8   4183 use Moo;
  8         13  
  8         43  
4 8     8   2147 use Types::Standard qw( Int HashRef Bool Object );
  8         12  
  8         53  
5 8     8   87325 use charnames ':full';
  8         205646  
  8         44  
6              
7             extends 'Image::TextMode::Reader';
8              
9             # State definitions
10             my $S_TXT = 0;
11             my $S_CHK_B = 1;
12             my $S_WAIT_LTR = 2;
13             my $S_END = 3;
14              
15             has 'tabstop' => ( is => 'rw', isa => Int, default => 8 );
16              
17             has 'save_x' => ( is => 'rw', isa => Int, default => 0 );
18              
19             has 'save_y' => ( is => 'rw', isa => Int, default => 0 );
20              
21             has 'x' => ( is => 'rw', isa => Int, default => 0 );
22              
23             has 'y' => ( is => 'rw', isa => Int, default => 0 );
24              
25             has 'attr' => ( is => 'rw', isa => Int, default => 7 );
26              
27             has 'rgbattr' => ( is => 'rw', isa => HashRef, default => sub { { fg => [ 0xaa, 0xaa, 0xaa ], bg => [ 0, 0, 0 ] } } );
28              
29             has 'is_truecolor' => ( is => 'rw', isa => Bool, default => 0 );
30              
31             has 'state' => ( is => 'rw', isa => Int, default => $S_TXT );
32              
33             has 'image' => ( is => 'rw', isa => Object );
34              
35             has 'linewrap' => ( is => 'rw', isa => Int, default => 80 );
36              
37             sub _read {
38 23     23   44 my ( $self, $image, $fh, $options ) = @_;
39              
40 23         309 $self->image( $image );
41 23 100       4532 if ( $options->{ width } ) {
42 1         21 $self->linewrap( $options->{ width } );
43             }
44              
45 23 100       532 if ( $image->has_sauce ) {
46 1         52 $image->render_options->{ blink_mode } = $image->sauce->flags_id ^ 1;
47             }
48              
49 23         1528 seek( $fh, 0, 0 );
50              
51             # make sure we reset the state of the parser
52 23         346 $self->state( $S_TXT );
53              
54 23         4698 my ( $argbuf, $ch );
55 23         1243597 while ( read( $fh, $ch, 1 ) ) {
56 27637         621013 my $state = $self->state;
57 27637 50       127976 last if tell( $fh ) > $options->{ filesize };
58 27637 100       35150 if ( $state == $S_TXT ) {
    100          
    100          
    50          
59 26455 100       79687 if ( $ch eq "\N{SUBSTITUTE}" ) {
    100          
    100          
    100          
    100          
60 13         206 $self->state( $S_END );
61             }
62             elsif ( $ch eq "\N{ESCAPE}" ) {
63 182         3345 $self->state( $S_CHK_B );
64             }
65             elsif ( $ch eq "\n" ) {
66 13018         16916 $self->new_line;
67             }
68             elsif ( $ch eq "\r" ) {
69              
70             # do nothing
71             }
72             elsif ( $ch eq "\t" ) {
73 2         3 $self->tab;
74             }
75             else {
76 225         558 $self->store( $ch );
77             }
78             }
79             elsif ( $state == $S_CHK_B ) {
80 182 50       313 if ( $ch ne '[' ) {
81 0         0 $self->store( chr( 27 ) );
82 0         0 $self->store( $ch );
83 0         0 $self->state( $S_TXT );
84             }
85             else {
86 182         3029 $self->state( $S_WAIT_LTR );
87             }
88             }
89             elsif ( $state == $S_WAIT_LTR ) {
90 988 100       1999 if ( $ch =~ /[a-zA-Z]/s ) {
91 182         276 $argbuf =~ s{\s}{}sg; # eliminate whitespace from args
92 182         609 my @args = split( /;/s, $argbuf );
93              
94 182 100 66     872 if ( $ch eq 'm' ) {
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    50          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
95 148         389 $self->set_attributes( @args );
96             }
97             elsif ( $ch eq 'H' or $ch eq 'f' ) {
98 10         40 $self->set_position( @args );
99             }
100             elsif ( $ch eq 'A' ) {
101 2         6 $self->move_up( @args );
102             }
103             elsif ( $ch eq 'B' ) {
104 2         5 $self->move_down( @args );
105             }
106             elsif ( $ch eq 'C' ) {
107 2         4 $self->move_right( @args );
108             }
109             elsif ( $ch eq 'D' ) {
110 2         5 $self->move_left( @args );
111             }
112             elsif ( $ch eq 'E' ) {
113 0         0 $self->move_down( @args );
114 0         0 $self->x( 0 );
115             }
116             elsif ( $ch eq 'F' ) {
117 1         6 $self->move_up( @args );
118 1         55 $self->x( 0 );
119             }
120             elsif ( $ch eq 'G' ) {
121 0   0     0 $self->x( ( $args[ 0 ] || 1 ) - 1 );
122             }
123             elsif ( $ch eq 'h' ) {
124 1         7 $self->feature_on( $args[ 0 ] );
125             }
126             elsif ( $ch eq 'l' ) {
127 0         0 $self->feature_off( $args[ 0 ] );
128             }
129             elsif ( $ch eq 's' ) {
130 1         4 $self->save_position( @args );
131             }
132             elsif ( $ch eq 't' ) {
133 2         11 $self->rgb( @args );
134             }
135             elsif ( $ch eq 'u' ) {
136 1         4 $self->restore_position( @args );
137             }
138             elsif ( $ch eq 'J' ) {
139 7         41 $self->clear_screen( @args );
140             }
141             elsif ( $ch eq 'K' ) {
142 3         13 $self->clear_line( @args );
143             }
144              
145 182         6886 $argbuf = '';
146 182         3512 $self->state( $S_TXT );
147             }
148             else {
149 806         2140 $argbuf .= $ch;
150             }
151             }
152             elsif ( $state == $S_END ) {
153 12         36 last;
154             }
155             else {
156 0         0 $self->state( $S_TXT );
157             }
158             }
159              
160 23         1497 return $image;
161             }
162              
163             sub set_position {
164 10     10 1 20 my ( $self, $y, $x ) = @_;
165 10   100     36 $y = ( $y || 1 ) - 1;
166 10   100     29 $x = ( $x || 1 ) - 1;
167              
168 10 50       25 $y = 0 if $y < 0;
169 10 50       24 $x = 0 if $x < 0;
170              
171 10         159 $self->x( $x );
172 10         1315 $self->y( $y );
173             }
174              
175             sub set_attributes {
176 148     148 1 287 my ( $self, @args ) = @_;
177              
178 148         2493 my $attr = $self->attr;
179 148         7633 my $rgba = $self->rgbattr;
180 148         7448 my $pal = $self->image->palette->colors;
181              
182 148         14705 foreach ( @args ) {
183 339 100 33     3084 if ( $_ == 0 ) {
    100 33        
    50 66        
    100 33        
    50          
    50          
    100          
    50          
184 80         113 $attr = 7;
185 80         172 $rgba->{ fg } = $pal->[ 7 ];
186 80         215 $rgba->{ bg } = $pal->[ 0 ];
187             }
188             elsif ( $_ == 1 ) {
189 27         53 $attr |= 8;
190 27         95 $rgba->{ fg } = $pal->[ ( $attr & 15 ) ];
191             }
192             elsif ( $_ == 2 || $_ == 22 ) {
193 0         0 $attr &= 247;
194 0         0 $rgba->{ fg } = $pal->[ ( $attr & 15 ) ];
195             }
196             elsif ( $_ == 5 ) {
197 23         42 $attr |= 128;
198 23         83 $rgba->{ bg } = $pal->[ ( $attr & 240 ) >> 4 ];
199             }
200             elsif ( $_ == 7 || $_ == 27 ) {
201 0         0 my $oldfg = $attr & 15;
202 0         0 my $oldbg = ( $attr & 240 ) >> 4;
203 0         0 $attr = $oldbg | ( $oldfg << 4 );
204            
205 0         0 $rgba->{ fg } = $pal->[ ( $attr & 15 ) ];
206 0         0 $rgba->{ bg } = $pal->[ ( $attr & 240 ) >> 4 ];
207             }
208             elsif ( $_ == 25 ) {
209 0         0 $attr &= 127;
210 0         0 $rgba->{ bg } = $pal->[ ( $attr & 240 ) >> 4 ];
211             }
212             elsif ( $_ >= 30 and $_ <= 37 ) {
213 115         125 $attr &= 248;
214 115         172 $attr |= ( $_ - 30 );
215 115         242 $rgba->{ fg } = $pal->[ ( $attr & 15 ) ];
216             }
217             elsif ( $_ >= 40 and $_ <= 47 ) {
218 94         97 $attr &= 143;
219 94         100 $attr |= ( ( $_ - 40 ) << 4 );
220 94         310 $rgba->{ bg } = $pal->[ ( $attr & 240 ) >> 4 ];
221             }
222             }
223              
224 148         2937 $self->attr( $attr );
225             }
226              
227             sub move_up {
228 3     3 1 5 my $self = shift;
229 3   100     54 my $y = $self->y - ( shift || 1 );
230 3 50       29 $y = 0 if $y < 0;
231 3         163 $self->y( $y );
232             }
233              
234             sub move_down {
235 2     2 1 3 my $self = shift;
236 2   100     7 my $y = shift || 1;
237              
238 2         40 $self->y( $self->y + $y );
239             }
240              
241             sub move_right {
242 2     2 1 3 my $self = shift;
243 2   100     31 my $x = $self->x + ( shift || 1 );
244              
245             # check $x against $self->linewrap?
246              
247 2         41 $self->x( $x );
248             }
249              
250             sub move_left {
251 2     2 1 3 my $self = shift;
252 2   100     30 my $x = $self->x - ( shift || 1 );
253              
254 2 50       16 $x = 0 if $x < 0;
255              
256 2         30 $self->x( $x );
257             }
258              
259             sub save_position {
260 1     1 1 2 my $self = shift;
261              
262 1         61 $self->save_x( $self->x );
263 1         708 $self->save_y( $self->y );
264             }
265              
266             sub restore_position {
267 1     1 1 2 my $self = shift;
268              
269 1         17 $self->x( $self->save_x );
270 1         45 $self->y( $self->save_y );
271             }
272              
273             sub clear_line {
274 3     3 1 5 my $self = shift;
275 3         4 my $arg = shift;
276              
277 3 100       18 if ( !$arg ) { # clear to end of line
    100          
    50          
278 1         34 $self->image->clear_line( $self->y, [ $self->x, -1 ] );
279             }
280             elsif ( $arg == 1 ) { # clear to start of line
281 1         18 $self->image->clear_line( $self->y, [ 0, $self->x ] );
282             }
283             elsif ( $arg == 2 ) { #clear whole line
284 1         21 $self->image->clear_line( $self->y );
285             }
286             }
287              
288             sub clear_screen {
289 7     7 1 14 my $self = shift;
290 7         15 my $arg = shift;
291              
292 7 100       54 if( !$arg ) { # clear to end of screen, including cursor
    100          
    50          
293 1         35 my $next = $self->y + 1;
294 1         26 $self->image->delete_line( $next ) for 1..$self->image->height - $next + 1;
295 1         32 $self->image->clear_line( $self->y, [ $self->x, -1 ] );
296             }
297             elsif( $arg == 1 ) { # clear to start of screen, including cursor
298 1         27 $self->image->clear_line( $_ ) for 0..$self->y - 1;
299 1         32 $self->image->clear_line( $self->y, [ 0, $self->x ] );
300             }
301             elsif( $arg == 2 ) { # clear whole screen
302 5         136 $self->image->clear_screen;
303 5         239 $self->x( 0 );
304 5         239 $self->y( 0 );
305             }
306             }
307              
308             sub rgb {
309 2     2 1 5 my $self = shift;
310 2         5 my $mode = shift;
311 2         5 my @rgb = @_;
312              
313 2         55 $self->image->render_options->{ truecolor } = 1;
314 2         839 $self->is_truecolor( 1 );
315              
316 2 100       76 $self->rgbattr->{ $mode == 0 ? 'bg' : 'fg' } = [ @rgb ];
317             }
318              
319             sub feature_on {
320 1     1 1 4 my $self = shift;
321 1         3 my $arg = shift;
322              
323 1 50       6 if( $arg eq '?33' ) {
324 1         36 $self->image->render_options->{ blink_mode } = 0;
325             }
326             }
327              
328             sub feature_off {
329 0     0 1 0 my $self = shift;
330 0         0 my $arg = shift;
331              
332 0 0       0 if( $arg eq '?33' ) {
333 0         0 $self->image->render_options->{ blink_mode } = 1;
334             }
335             }
336              
337             sub new_line {
338 13020     13020 1 10876 my $self = shift;
339              
340 13020         187952 $self->y( $self->y + 1 );
341 13020         394430 $self->x( 0 );
342             }
343              
344             sub tab {
345 2     2 1 3 my $self = shift;
346 2         17 my $count = ( $self->x + 1 ) % $self->tabstop;
347 2 50       1013 if ( $count ) {
348 2         31 $count = $self->tabstop - $count;
349 2         11 for ( 1 .. $count ) {
350 14         734 $self->store( ' ' );
351             }
352             }
353             }
354              
355             sub store {
356 239     239 1 294 my $self = shift;
357 239         281 my $char = shift;
358 239         239 my $x = shift;
359 239         247 my $y = shift;
360 239         214 my $attr = shift;
361              
362 239         5090 my $pal = $self->image->palette->colors;
363              
364 239 50       16617 my %colors = ( attr => defined $attr ? $attr : $self->attr );
365 239 100       6304 if( $self->is_truecolor ) {
366 3         18 delete $colors{ attr };
367 3 50       47 $attr = defined $attr ? $attr : $self->rgbattr;
368 3         12 push @{ $pal }, $attr->{ fg };
  3         9  
369 3         3 $colors{ fg } = scalar @{ $pal } - 1;
  3         7  
370 3         3 push @{ $pal }, $attr->{ bg };
  3         4  
371 3         4 $colors{ bg } = scalar @{ $pal } - 1;
  3         7  
372             }
373              
374 239 50 33     6810 if ( defined $x and defined $y ) {
375 0         0 $self->image->putpixel( { char => $char, %colors }, $x, $y );
376             }
377             else {
378 239         4728 $self->image->putpixel( { char => $char, %colors },
379             $self->x, $self->y );
380 239         17024 $self->x( $self->x + 1 );
381             }
382              
383 239 100       10709 if ( $self->x >= $self->linewrap ) {
384 2         66 $self->new_line;
385             }
386             }
387              
388             =head1 NAME
389              
390             Image::TextMode::Reader::ANSI - Reads ANSI files
391              
392             =head1 DESCRIPTION
393              
394             Provides reading capabilities for the ANSI format.
395              
396             =head1 ACCESSORS
397              
398             =over 4
399              
400             =item * tabstop - every Nth character will be a tab stop location (default: 8)
401              
402             =item * save_x - saved x position (default: 0)
403              
404             =item * save_y - saved y position (default: 0)
405              
406             =item * x - current x (default: 0)
407              
408             =item * y - current y (default: 0)
409              
410             =item * attr - current attribute info (default: 7, gray on black)
411              
412             =item * state - state of the parser (default: C<$S_TXT>)
413              
414             =item * image - the image we're parsing into
415              
416             =item * linewrap - max width before we wrap to the next line (default: 80)
417              
418             =back
419              
420             =head1 METHODS
421              
422             =head2 set_position( [$x, $y] )
423              
424             Moves the cursor to C<$x, $y>.
425              
426             =head2 set_attributes( @args )
427              
428             Sets the default attribute information (fg and bg).
429              
430             =head2 move_up( $y )
431              
432             Moves the cursor up C<$y> lines.
433              
434             =head2 move_down( $y )
435              
436             Moves the cursor down C<$y> lines.
437              
438             =head2 move_left( $x )
439              
440             Moves the cursor left C<$x> columns.
441              
442             =head2 move_right( $x )
443              
444             Moves the cursor right C<$x> columns.
445              
446             =head2 save_position( )
447              
448             Saves the current cursor position.
449              
450             =head2 restore_position( )
451              
452             Restores the saved cursor position.
453              
454             =head2 clear_screen( )
455              
456             Clears all data on the canvas.
457              
458             =head2 clear_line( $y )
459              
460             Clears the line at C<$y>.
461              
462             =head2 rgb( $mode, $r, $g, $b )
463              
464             Set the attribute to RGB color. Also, sets image to true-color mode.
465              
466             =head2 feature_on( $code )
467              
468             Enables a feature.
469              
470             =head2 feature_off( $code )
471              
472             Disables a feature.
473              
474             =head2 new_line( )
475              
476             Simulates a C<\n> character.
477              
478             =head2 tab( )
479              
480             Simulates a C<\t> character.
481              
482             =head2 store( $char, $x, $y [, $attr] )
483              
484             Stores C<$char> at position C<$x, $y> with either the supplied attribute
485             or the current attribute setting.
486              
487             =head1 AUTHOR
488              
489             Brian Cassidy Ebricas@cpan.orgE
490              
491             =head1 COPYRIGHT AND LICENSE
492              
493             Copyright 2008-2014 by Brian Cassidy
494              
495             This library is free software; you can redistribute it and/or modify
496             it under the same terms as Perl itself.
497              
498             =cut
499              
500             1;