File Coverage

blib/lib/ANSI/Unicode.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package ANSI::Unicode;
2              
3 1     1   25775 use 5.008_005;
  1         4  
  1         75  
4             our $VERSION = '0.03';
5              
6 1     1   1809 use Moose;
  0            
  0            
7              
8             use Encode qw (from_to encode _utf8_on _utf8_off);
9             use Data::Dumper;
10              
11             has 'cols' => (
12             is => 'rw',
13             isa => 'Int',
14             default => 80,
15             );
16              
17             has 'rows' => (
18             is => 'rw',
19             isa => 'Int',
20             default => 0,
21             );
22              
23             has 'charmap' => (
24             is => 'rw',
25             isa => 'ArrayRef',
26             );
27             has 'colormap' => (
28             is => 'rw',
29             isa => 'ArrayRef',
30             );
31              
32             has 'no_color' => (
33             is => 'rw',
34             isa => 'Bool',
35             default => 0,
36             );
37              
38             has 'format' => (
39             is => 'rw',
40             isa => 'Str',
41             default => 'irc',
42             );
43              
44             has 'input_filename' => (
45             is => 'rw',
46             isa => 'Str',
47             required => 1,
48             );
49              
50             my $fontsize = 13; # px
51             my $esc = "\x1b";
52              
53             *color2mirc_bg = \&color2mirc_fg;
54              
55             my %ans2mircmap = (
56             30 => 1, # black
57             31 => 4, # red
58             32 => 9, # green
59             33 => 8, # yellow
60             34 => 2, # blue
61             35 => 13, # pink (should be purple?)
62             36 => 11, # cyan
63             37 => 0, # white
64             39 => 0, # white
65             );
66              
67             # generate background colors
68             foreach my $k (keys %ans2mircmap) {
69             $ans2mircmap{$k + 10} = $ans2mircmap{$k};
70             }
71             $ans2mircmap{49} = 1; # default is black not white for background color
72              
73             my %mirc2colormap = (
74             0 => 'white',
75             1 => 'black',
76             2 => '#00c',
77             3 => 'green',
78             4 => '#b00', # red
79             5 => 'brown',
80             6 => 'purple',
81             7 => 'orange',
82             8 => '#8F8F00', # dark yellow
83             9 => '#33FF33', # ltgreen
84             10 => 'teal',
85             11 => 'cyan',
86             12 => '#3333FF', # ltblue,
87             13 => '#FFA0AB', # pink
88             14 => 'grey',
89             15 => 'ltgrey',
90              
91             # extra mappings for high intensity colors to mirc
92             6 => 'yellow', # dark yellow -> orange
93             );
94              
95             # normal -> high intensity colors (for ANSI 'bold')
96             my %color2hi = (
97             '#8F8F00' => 'yellow', # dkyellow
98             'black' => '#777', # grey
99             'white' => '#eee', # this is sort of a 'wtf'
100             '#b00' => '#f00', # red
101             '#33FF33' => '#7F7', # ltgreen
102             'ltgrey' => '#888',
103             '#00c' => '#33F', # blue
104             'cyan' => '#4ef',
105             '#FFA0AB' => '#FCB',
106             );
107              
108             my %color2mircmap;
109             @color2mircmap{values %mirc2colormap} = keys %mirc2colormap;
110              
111             sub convert {
112             my ($self, $in) = @_;
113             my $mirc_last_fg = '';
114             my @map = ();
115             my @colormap = ();
116             my $row = 0;
117             my $col = 0;
118             my $linewrap;
119              
120             # filter out stuff we don't care about
121             $linewrap ||= $in =~ s/$esc\[.?7h//g; # enable linewrap
122              
123             # go through each character
124             my $idx = 0;
125             my $cur = $in;
126             while (length($cur)) {
127             last if $idx >= length($cur);
128              
129             my $c = substr($in, $idx, 1);
130             $idx++;
131              
132             if ($c eq $esc) {
133             # escape sequence, oh noes!
134             my $seq = substr($in, $idx);
135             # warn "seq: $seq";
136             if ($seq =~ s/^\[(\d+)?C//) {
137             # move forward
138             $col += $1 || 1;
139             } elsif ($seq =~ s/^\[(\d+)?D//) {
140             # move back
141             if ($1 && $1 > 254) {
142             $col = 0;
143             } else {
144             my $back = $1 || 1;
145             if ($col - $back < 0) {
146             warn "tried to set negative col: $back";
147             } else {
148             $col -= $back;
149             }
150             }
151             } elsif ($seq =~ s/^\[s//) {
152             # save pos
153             } elsif ($seq =~ s/^\[u//) {
154             # load pos
155             } elsif ($seq =~ s/^\[(\d+)?A//) {
156             # move up
157             my $up = $1 || 1;
158             if ($row - $up < 0) {
159             warn "tried to set negative row: $up";
160             } else {
161             $row -= $up;
162             }
163             } elsif ($seq =~ s/^\[(\d+)?B//) {
164             # move down
165             $row += $1 || 1;
166             } elsif ($seq =~ s/^\[(\d+);(\d+)H//) {
167             # set position
168             $row = $1;
169             $col = $2;
170             } elsif ($seq =~ s/^\[(\d+)m//) {
171             if ($1 == 0) {
172             # reset
173             $colormap[$row][$col] = {fgcolor => 'white', bgcolor => 'black'};
174             } elsif ($1 < 30) {
175             # ignore font/color attribute for now
176             } elsif ($1 >= 30 && $1 < 40) {
177             $colormap[$row][$col] = {fgcolor => ans2color($1)};
178             } elsif ($1 >= 40 && $1 < 50) {
179             $colormap[$row][$col] = {bgcolor => ans2color($1)};
180             } else {
181             print STDERR "Unknown ANSI color code: $1\n";
182             }
183             } elsif ($seq =~ s/^\[(\d*);(\d*);?(\d*)m//) {
184             my $color_info = {};
185             my @attrs = ($1, $2, $3);
186             my $force;
187             while (@attrs) {
188             my $attr = shift @attrs;
189             next if ! defined $attr || $attr eq '';
190              
191             if ($attr == 0) {
192             # reset
193             $color_info = {fgcolor => 'white', bgcolor => 'black'};
194             } elsif ($attr < 30) {
195             if ($attr == 1) {
196             # bold, but seems to mean set the fg color to ltgrey if fg and bg are white
197             #unless (grep { ans2color($_) ne 'black'
198             # && ans2color($_) ne 'white' } @attrs) {
199             #$color_info->{fgcolor} = 'ltgrey';
200             #$color_info->{bgcolor} = 'white';
201             #$force = 1;
202             #}
203             #$color_info->{bgcolor} = 'black';
204             $color_info->{bold} = 1;
205             } else {
206             #print STDERR "Unhandled attribute $attr\n";
207             }
208             # other color/text attribute. ignore for now.
209             } elsif ($attr < 40) {
210             # fg
211             # if ($color_info->{bold})
212             $color_info->{fgcolor} = ans2color($attr) unless $force;
213             } elsif ($attr < 50) {
214             #bg
215             $color_info->{bgcolor} = ans2color($attr) unless $force;
216             } elsif (! $force) {
217             print STDERR "Unrecognized ANSI color code: $attr\n";
218             }
219             }
220              
221             # don't allow white on white text
222             if ($color_info->{fgcolor} && $color_info->{bgcolor}) {
223             $color_info->{fgcolor} = 'ltgrey'
224             if $color_info->{fgcolor} eq $color_info->{bgcolor};
225             }
226              
227             $colormap[$row][$col] = $color_info;
228             } elsif ($seq =~ /\[2J/) {
229             # erase display and reset cursor... okay
230             $seq = '';
231             } else {
232             print STDERR "Unrecognized ANSI escape sequence, chunk='" .
233             substr($seq, 0, 7) . "'\n";
234             }
235              
236             # change the rest of the current sequence past $idx to $seq
237             my $seqlen = length($in) - length($seq) - $idx;
238             $idx += $seqlen;
239             # substr($cur, $idx + $seqlen) = $seq;
240             } elsif ($c eq "\n") {
241             $row++;
242             } elsif ($c eq "\r") {
243             $col = 0;
244             } else {
245             # otherwise it's a normal char
246             cp437_to_unicode(\$c) if ord($c) > 127;
247             $map[$row][$col] = $c;
248             $col++;
249             }
250              
251             if ($col >= $self->cols) {
252             # linewrap
253             $col = $col % $self->cols;
254             $row++;
255             }
256             }
257              
258             $self->rows($row);
259             $self->charmap(\@map);
260             $self->colormap(\@colormap);
261              
262             my $out;
263             my $format = $self->format;
264              
265             if ($format eq 'html') {
266             $out = $self->html_output;
267             } elsif ($format eq 'irc') {
268             # default
269             $out = $self->irc_output;
270             } else {
271             die "Unknown format $format";
272             }
273              
274             return $out;
275             }
276              
277             sub html_output {
278             my ($self) = @_;
279              
280             my $ret = '';
281             $ret .= qq {<table style="font-family: 'Courier New'; font-size: ${fontsize}px;" cellspacing="0" cellpadding="0">} . "\n";
282              
283             my @map = @{ $self->charmap };
284             my @colormap = @{ $self->colormap };
285             my $last_style = '';
286             my ($fgcolor, $bgcolor);
287             my $color_info = {fgcolor => 'white', bgcolor => 'black'};
288              
289             for (my $row = 0; $row <= $self->rows; $row++) {
290             $ret .= '<tr bgcolor="black">';
291              
292             for (my $col = 0; $col < $self->cols; $col++) {
293             my $c = $map[$row][$col];
294              
295             if ($colormap[$row][$col]) {
296             foreach my $attr (qw/ fgcolor bgcolor bold /) {
297             next unless my $newattr = $colormap[$row][$col]->{$attr};
298             $color_info->{$attr} = $newattr;
299             }
300             }
301              
302             $fgcolor = $color_info->{fgcolor} if $color_info->{fgcolor};
303             $bgcolor = $color_info->{bgcolor} if $color_info->{bgcolor};
304              
305             if ($color_info->{bold}) {
306             # bold really doesn't mean bold, it means use the high-intensity version of the color
307             # warn "bold: $fgcolor";
308             $fgcolor = color_hi($fgcolor);
309             }
310              
311             my $char_uni_html = '';
312              
313             my ($td_fgcolor, $td_bgcolor);
314              
315             # look up $c's unicode value
316             if (! defined $c) {
317             # no char, make this a blank cell
318             $bgcolor = '#000';
319             $char_uni_html = '&nbsp;';
320             } elsif ($c eq ' ') {
321             # turn space into nbsp
322             $char_uni_html = '&nbsp;';
323             } else {
324             # convert char to unicode
325             # cp437_to_unicode(\$c);
326             _utf8_on($c);
327             # _utf8_off($c);
328             # warn "char: $c";
329             $char_uni_html = $c; #'&#' . ord($c) . ';';
330             # warn "ord: " . ord($c);
331             }
332              
333             $td_fgcolor ||= qq{ style="color: $fgcolor"};
334             $td_bgcolor ||= qq{ bgcolor="$bgcolor"};
335              
336             $td_bgcolor = '' if $bgcolor eq '#000' || $bgcolor eq 'black';
337              
338             $ret .= "<td$td_bgcolor$td_fgcolor>$char_uni_html</td>";
339             }
340              
341             $ret .= "</tr>\n";
342             }
343              
344             $ret .= "</table>\n";
345             return $ret;
346             }
347              
348             sub irc_output {
349             my ($self, %map) = @_;
350              
351             my @map = @{ $self->charmap };
352             my @colormap = @{ $self->colormap };
353             my $lastcolor;
354             my $ret;
355              
356             $ret .= colorinfo2mirc({fgcolor => "white", bgcolor => "black"});
357             my $color_info;
358              
359             for (my $row = 0; $row <= $self->rows; $row++) {
360             my $mirc_color;
361             my $last_color;
362              
363             for (my $col = 0; $col < $self->cols; $col++) {
364             if ($colormap[$row][$col]) {
365             foreach my $attr (qw/ fgcolor bgcolor bold /) {
366             my $newattr = $colormap[$row][$col]->{$attr};
367             next unless defined $newattr;
368             $color_info->{$attr} = $newattr;
369             }
370             }
371              
372             my $c = $map[$row][$col];
373              
374             if (defined $c) {
375             $mirc_color = colorinfo2mirc($color_info) || '';
376              
377             # print out new color code if we have a new color
378             $ret .= "$mirc_color"
379             if ($mirc_color && ! $last_color) || ($last_color && $mirc_color && $mirc_color ne $last_color);
380              
381             $last_color = $mirc_color;
382              
383             # output char
384             $ret .= $c;
385             } else {
386             $ret .= " ";
387             }
388             }
389              
390             $ret .= "\n";
391              
392             # new line, keep last color
393             $ret .= $mirc_color if $mirc_color;
394             }
395              
396             # this might not be right
397             _utf8_off($ret);
398              
399             return $ret;
400             }
401              
402             # takes strref
403             sub cp437_to_unicode {
404             my $strref = shift;
405             from_to($$strref, "IBM437", "utf8");
406             return;
407             #_utf8_on($$strref);
408             my $mapped = Encode::encode_utf8($$strref);
409             $strref = \$mapped;
410              
411             # fix perl's gay mapping
412             $$strref =~ s/\x{004}/\x{2666}/g;
413             }
414              
415             # returns the high-intensity version of this color, if available
416             sub color_hi {
417             my $color = shift;
418             my $light = $color2hi{$color};
419             unless ($light) {
420             warn "Failed to find high-intensity version of $color";
421             }
422             return $light || $color;
423             }
424              
425             sub color2mirc_fg {
426             my $color = shift;
427             return $color2mircmap{$color};
428             }
429              
430             sub colorinfo2mirc {
431             my $color = shift;
432              
433             my $fgcolor = $color->{fgcolor};
434             my $bgcolor = $color->{bgcolor};
435              
436             # return '' if $termout;
437             return '' unless $fgcolor || $bgcolor;
438              
439             my $fg = $fgcolor ? color2mirc_fg($fgcolor) : '';
440             my $bg = $bgcolor ? color2mirc_bg($bgcolor) : '';
441              
442             $fg = color2mirc_fg(color_hi($fgcolor)) if $fgcolor && $color->{bold};
443              
444             # return "\033[$fgcolor;$bgcolor;m" if $self->termout;
445              
446             if ($bg) {
447             $fg ||= 0;
448             return "\003$fg,$bg";
449             }
450              
451             return "\003$fg";
452             }
453              
454             sub ans2color {
455             my $ans = shift;
456             return '' unless $ans;
457             my $mirc_color = $ans2mircmap{$ans};
458             return '' unless defined $mirc_color;
459             return $mirc2colormap{$mirc_color};
460             }
461              
462             1;
463             __END__
464              
465             =encoding utf-8
466              
467             =head1 NAME
468              
469             ANSI::Unicode - ANSI to IRC and HTML converter
470              
471             =head1 DESCRIPTION
472              
473             Convert old-school .ANS files from the codepage 437 encoding to unicode.
474              
475             Outputs colorized unicode as either HTML or IRC-compatible format.
476              
477             =head1 AUTHOR
478              
479             Mischa S. E<lt>revmischa@cpan.orgE<gt>
480              
481             =head1 COPYRIGHT
482              
483             Copyright 2013- Mischa Spiegelmock
484              
485             =head1 LICENSE
486              
487             This library is free software; you can redistribute it and/or modify
488             it under the same terms as Perl itself.
489              
490             =head1 SEE ALSO
491              
492             =cut