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   4586 use Moo;
  8         14  
  8         50  
4 8     8   2190 use Types::Standard qw( Int HashRef Bool Object );
  8         9  
  8         64  
5 8     8   10052 use charnames ':full';
  8         205758  
  8         46  
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   49 my ( $self, $image, $fh, $options ) = @_;
39              
40 23         287 $self->image( $image );
41 23 100       4831 if ( $options->{ width } ) {
42 1         17 $self->linewrap( $options->{ width } );
43             }
44              
45 23 100       507 if ( $image->has_sauce ) {
46 1         47 $image->render_options->{ blink_mode } = ($image->sauce->flags_id & 1) ^ 1;
47             }
48              
49 23         1410 seek( $fh, 0, 0 );
50              
51             # make sure we reset the state of the parser
52 23         309 $self->state( $S_TXT );
53              
54 23         4801 my ( $argbuf, $ch );
55 23         321 while ( read( $fh, $ch, 1 ) ) {
56 27637         590478 my $state = $self->state;
57 27637 50       120545 last if tell( $fh ) > $options->{ filesize };
58 27637 100       34023 if ( $state == $S_TXT ) {
    100          
    100          
    50          
59 26455 100       76926 if ( $ch eq "\N{SUBSTITUTE}" ) {
    100          
    100          
    100          
    100          
60 13         199 $self->state( $S_END );
61             }
62             elsif ( $ch eq "\N{ESCAPE}" ) {
63 182         2695 $self->state( $S_CHK_B );
64             }
65             elsif ( $ch eq "\n" ) {
66 13018         17101 $self->new_line;
67             }
68             elsif ( $ch eq "\r" ) {
69              
70             # do nothing
71             }
72             elsif ( $ch eq "\t" ) {
73 2         6 $self->tab;
74             }
75             else {
76 225         394 $self->store( $ch );
77             }
78             }
79             elsif ( $state == $S_CHK_B ) {
80 182 50       294 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         2775 $self->state( $S_WAIT_LTR );
87             }
88             }
89             elsif ( $state == $S_WAIT_LTR ) {
90 988 100       1558 if ( $ch =~ /[a-zA-Z]/s ) {
91 182         294 $argbuf =~ s{\s}{}sg; # eliminate whitespace from args
92 182         529 my @args = split( /;/s, $argbuf );
93              
94 182 100 66     690 if ( $ch eq 'm' ) {
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    50          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
95 148         347 $self->set_attributes( @args );
96             }
97             elsif ( $ch eq 'H' or $ch eq 'f' ) {
98 10         41 $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         7 $self->move_down( @args );
105             }
106             elsif ( $ch eq 'C' ) {
107 2         6 $self->move_right( @args );
108             }
109             elsif ( $ch eq 'D' ) {
110 2         6 $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         5 $self->move_up( @args );
118 1         29 $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         3 $self->save_position( @args );
131             }
132             elsif ( $ch eq 't' ) {
133 2         9 $self->rgb( @args );
134             }
135             elsif ( $ch eq 'u' ) {
136 1         4 $self->restore_position( @args );
137             }
138             elsif ( $ch eq 'J' ) {
139 7         30 $self->clear_screen( @args );
140             }
141             elsif ( $ch eq 'K' ) {
142 3         9 $self->clear_line( @args );
143             }
144              
145 182         5706 $argbuf = '';
146 182         2745 $self->state( $S_TXT );
147             }
148             else {
149 806         1724 $argbuf .= $ch;
150             }
151             }
152             elsif ( $state == $S_END ) {
153 12         33 last;
154             }
155             else {
156 0         0 $self->state( $S_TXT );
157             }
158             }
159              
160 23         1159 return $image;
161             }
162              
163             sub set_position {
164 10     10 1 19 my ( $self, $y, $x ) = @_;
165 10   100     28 $y = ( $y || 1 ) - 1;
166 10   100     28 $x = ( $x || 1 ) - 1;
167              
168 10 50       25 $y = 0 if $y < 0;
169 10 50       32 $x = 0 if $x < 0;
170              
171 10         143 $self->x( $x );
172 10         1295 $self->y( $y );
173             }
174              
175             sub set_attributes {
176 148     148 1 244 my ( $self, @args ) = @_;
177              
178 148         2202 my $attr = $self->attr;
179 148         6470 my $rgba = $self->rgbattr;
180 148         6711 my $pal = $self->image->palette->colors;
181              
182 148         12747 foreach ( @args ) {
183 339 100 33     2616 if ( $_ == 0 ) {
    100 33        
    50 66        
    100 33        
    50          
    50          
    100          
    50          
184 80         99 $attr = 7;
185 80         161 $rgba->{ fg } = $pal->[ 7 ];
186 80         178 $rgba->{ bg } = $pal->[ 0 ];
187             }
188             elsif ( $_ == 1 ) {
189 27         46 $attr |= 8;
190 27         81 $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         37 $attr |= 128;
198 23         73 $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         118 $attr &= 248;
214 115         130 $attr |= ( $_ - 30 );
215 115         209 $rgba->{ fg } = $pal->[ ( $attr & 15 ) ];
216             }
217             elsif ( $_ >= 40 and $_ <= 47 ) {
218 94         91 $attr &= 143;
219 94         107 $attr |= ( ( $_ - 40 ) << 4 );
220 94         205 $rgba->{ bg } = $pal->[ ( $attr & 240 ) >> 4 ];
221             }
222             }
223              
224 148         2382 $self->attr( $attr );
225             }
226              
227             sub move_up {
228 3     3 1 5 my $self = shift;
229 3   100     48 my $y = $self->y - ( shift || 1 );
230 3 50       26 $y = 0 if $y < 0;
231 3         45 $self->y( $y );
232             }
233              
234             sub move_down {
235 2     2 1 4 my $self = shift;
236 2   100     8 my $y = shift || 1;
237              
238 2         35 $self->y( $self->y + $y );
239             }
240              
241             sub move_right {
242 2     2 1 1 my $self = shift;
243 2   100     32 my $x = $self->x + ( shift || 1 );
244              
245             # check $x against $self->linewrap?
246              
247 2         43 $self->x( $x );
248             }
249              
250             sub move_left {
251 2     2 1 4 my $self = shift;
252 2   100     31 my $x = $self->x - ( shift || 1 );
253              
254 2 50       18 $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         18 $self->save_x( $self->x );
263 1         706 $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 4 my $self = shift;
275 3         4 my $arg = shift;
276              
277 3 100       13 if ( !$arg ) { # clear to end of line
    100          
    50          
278 1         18 $self->image->clear_line( $self->y, [ $self->x, -1 ] );
279             }
280             elsif ( $arg == 1 ) { # clear to start of line
281 1         19 $self->image->clear_line( $self->y, [ 0, $self->x ] );
282             }
283             elsif ( $arg == 2 ) { #clear whole line
284 1         18 $self->image->clear_line( $self->y );
285             }
286             }
287              
288             sub clear_screen {
289 7     7 1 11 my $self = shift;
290 7         12 my $arg = shift;
291              
292 7 100       42 if( !$arg ) { # clear to end of screen, including cursor
    100          
    50          
293 1         17 my $next = $self->y + 1;
294 1         20 $self->image->delete_line( $next ) for 1..$self->image->height - $next + 1;
295 1         22 $self->image->clear_line( $self->y, [ $self->x, -1 ] );
296             }
297             elsif( $arg == 1 ) { # clear to start of screen, including cursor
298 1         18 $self->image->clear_line( $_ ) for 0..$self->y - 1;
299 1         24 $self->image->clear_line( $self->y, [ 0, $self->x ] );
300             }
301             elsif( $arg == 2 ) { # clear whole screen
302 5         90 $self->image->clear_screen;
303 5         181 $self->x( 0 );
304 5         167 $self->y( 0 );
305             }
306             }
307              
308             sub rgb {
309 2     2 1 5 my $self = shift;
310 2         2 my $mode = shift;
311 2         5 my @rgb = @_;
312              
313 2         42 $self->image->render_options->{ truecolor } = 1;
314 2         693 $self->is_truecolor( 1 );
315              
316 2 100       74 $self->rgbattr->{ $mode == 0 ? 'bg' : 'fg' } = [ @rgb ];
317             }
318              
319             sub feature_on {
320 1     1 1 3 my $self = shift;
321 1         2 my $arg = shift;
322              
323 1 50       6 if( $arg eq '?33' ) {
324 1         33 $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 10963 my $self = shift;
339              
340 13020         183471 $self->y( $self->y + 1 );
341 13020         377355 $self->x( 0 );
342             }
343              
344             sub tab {
345 2     2 1 3 my $self = shift;
346 2         18 my $count = ( $self->x + 1 ) % $self->tabstop;
347 2 50       1072 if ( $count ) {
348 2         31 $count = $self->tabstop - $count;
349 2         14 for ( 1 .. $count ) {
350 14         755 $self->store( ' ' );
351             }
352             }
353             }
354              
355             sub store {
356 239     239 1 240 my $self = shift;
357 239         241 my $char = shift;
358 239         213 my $x = shift;
359 239         193 my $y = shift;
360 239         170 my $attr = shift;
361              
362 239         3739 my $pal = $self->image->palette->colors;
363              
364 239 50       12615 my %colors = ( attr => defined $attr ? $attr : $self->attr );
365 239 100       4793 if( $self->is_truecolor ) {
366 3         19 delete $colors{ attr };
367 3 50       48 $attr = defined $attr ? $attr : $self->rgbattr;
368 3         14 push @{ $pal }, $attr->{ fg };
  3         8  
369 3         3 $colors{ fg } = scalar @{ $pal } - 1;
  3         8  
370 3         3 push @{ $pal }, $attr->{ bg };
  3         5  
371 3         2 $colors{ bg } = scalar @{ $pal } - 1;
  3         5  
372             }
373              
374 239 50 33     5741 if ( defined $x and defined $y ) {
375 0         0 $self->image->putpixel( { char => $char, %colors }, $x, $y );
376             }
377             else {
378 239         3467 $self->image->putpixel( { char => $char, %colors },
379             $self->x, $self->y );
380 239         12931 $self->x( $self->x + 1 );
381             }
382              
383 239 100       7540 if ( $self->x >= $self->linewrap ) {
384 2         43 $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-2015 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;