File Coverage

blib/lib/String/Tagged/Terminal/Win32Console.pm
Criterion Covered Total %
statement 49 59 83.0
branch 13 30 43.3
condition 3 18 16.6
subroutine 8 8 100.0
pod 0 1 0.0
total 73 116 62.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2017-2018 -- leonerd@leonerd.org.uk
5              
6             package String::Tagged::Terminal::Win32Console;
7              
8 1     1   6 use strict;
  1         1  
  1         25  
9 1     1   5 use warnings;
  1         2  
  1         20  
10 1     1   4 use Win32::Console;
  1         3  
  1         48  
11 1     1   6 use List::Util qw( max );
  1         2  
  1         138  
12              
13             our $VERSION = '0.04';
14              
15             =head1 NAME
16              
17             C - Windows-specific code for L
18              
19             =head1 SYNOPSIS
20              
21             # No user serviceable parts inside
22             use String::Tagged::Terminal;
23              
24             =head1 DESCRIPTION
25              
26             This module provides support for L to print to the
27             console on C. It is not intended to be used directly.
28              
29             =cut
30              
31             use constant {
32 1         564 ATTR_BLUE => 0x0001,
33             ATTR_GREEN => 0x0002,
34             ATTR_RED => 0x0004,
35             ATTR_INTENSITY => 0x0008,
36             ATTR_REVERSE_VIDEO => 0x4000, # Windows 10 onwards
37             ATTR_UNDERSCORE => 0x8000, # Windows 10 onwards
38 1     1   6 };
  1         2  
39              
40             # We can only ever allocate a single console on Windows
41             our $WIN32_CONSOLE;
42              
43             my %color_to_attr; # a cache
44              
45             sub print_to_console
46             {
47 3     3 0 5 my $self = shift;
48 3         7 my ( $fh, %opts ) = @_;
49              
50             # Convert filenos to native Win32 file handles, this should also try
51             # Win32API::File::FdGetOsFHandle( $fh );
52             my $fileno = {
53             1 => Win32::Console::STD_OUTPUT_HANDLE(),
54             2 => Win32::Console::STD_ERROR_HANDLE(),
55 3   33     17 }->{ $fh->fileno } || $fh->fileno;
56              
57             my %output_options = (
58 3 50       29 ( $opts{no_color} ? ( except => [qw( fgindex bgindex )] ) : () ),
59             only => [qw( fgindex bgindex bold under reverse )], # only process what we can handle
60             );
61              
62 3 50       7 if( $fileno < 0 ) {
63             # This looks like a Perl-internal FH, let's not output any formatting
64 0         0 $fh->print( $self->build_terminal( %opts ) );
65             }
66             else {
67 3   33     9 my $console = $opts{console} || do { $WIN32_CONSOLE ||= Win32::Console->new( $fileno ); };
68 3         13 my $saved = $console->Attr();
69 3         15 my $attr = $saved;
70              
71             $self->iter_substr_nooverlap( sub {
72 9     9   458 my ( $s, %tags ) = @_;
73              
74             # Simple boolean attributes first
75 9         20 foreach (
76             # bold is handled at the end
77             [ under => ATTR_UNDERSCORE ], # Rendering is flakey under Windows 10
78             # Windows console doesn't support italic, strike, blink
79             [ reverse => ATTR_REVERSE_VIDEO ],
80             ) {
81 18         28 my ( $tag, $on ) = @$_;
82 18         29 $attr &= ~$on;
83              
84 18 100       38 $attr |= $on if $tags{$tag};
85             }
86              
87             # Colour index attributes
88 9         21 foreach (
89             [ fgindex => 0, ],
90             [ bgindex => 4, ],
91             ) {
92 18         27 my ( $tag, $shift ) = @$_;
93 18         24 my $mask = 0x000F << $shift;
94 18         22 $attr &= ~$mask;
95              
96 18 100       29 if( defined $tags{$tag} ) {
97 1         2 my $idx = $tags{$tag};
98 1   33     5 $attr |= ( $color_to_attr{$idx} //= _color_to_attr( $idx ) ) << $shift;
99             }
100             else {
101             # Restore to previous
102 17         24 $attr |= $saved & $mask;
103             }
104             }
105              
106 9 100       23 $attr |= ATTR_INTENSITY if $tags{bold};
107              
108 9         22 $console->Attr($attr);
109 9         53 $console->Write($s);
110 3         23 }, %output_options );
111              
112 3         49 $console->Attr( $saved );
113             }
114             }
115              
116             sub _color_to_attr
117             {
118 1     1   3 my ( $idx ) = @_;
119              
120 1         2 my $attr = 0;
121              
122 1 50       3 if( $idx >= 16 ) {
123             # Attempt to convert xterm256 range into RGB+I
124 0         0 require Convert::Color;
125 0         0 my $color = Convert::Color->new( "xterm:$idx" )->as_rgb;
126              
127 0         0 my ( $red, $green, $blue ) = $color->rgb;
128 0         0 my $max = max( $red, $green, $blue );
129              
130 0 0       0 $attr |= ATTR_RED if $red > 0.5;
131 0 0       0 $attr |= ATTR_GREEN if $green > 0.5;
132 0 0       0 $attr |= ATTR_BLUE if $blue > 0.5;
133 0 0       0 $attr |= ATTR_INTENSITY if $max > 0.75;
134 0 0 0     0 $attr = ATTR_INTENSITY if $attr == 0 and
      0        
      0        
135             $red == $green and $red == $blue and $max > 0.25; # dark grey
136             }
137             else {
138             # The bits are swapped between ANSI and Win32 console
139 1 50       3 $attr |= ATTR_RED if $idx & 1;
140 1 50       11 $attr |= ATTR_GREEN if $idx & 2;
141 1 50       3 $attr |= ATTR_BLUE if $idx & 4;
142 1 50       3 $attr |= ATTR_INTENSITY if $idx & 8;
143             }
144 1         4 return $attr;
145             }
146              
147             =head1 COMPATIBILITY NOTES
148              
149             On Windows before Windows 10, only C, C and C are supported.
150              
151             Starting with Windows 10, also C and C are supported.
152              
153             On Windows, only a single output console is supported.
154              
155             =head1 AUTHOR
156              
157             Paul Evans ,
158             Max Maischein
159              
160             =cut
161              
162             0x55AA;