File: Win32Console.pm

package info (click to toggle)
libstring-tagged-terminal-perl 0.08-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 172 kB
  • sloc: perl: 653; makefile: 13
file content (161 lines) | stat: -rw-r--r-- 4,372 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2017-2018 -- leonerd@leonerd.org.uk

package String::Tagged::Terminal::Win32Console 0.08;

use v5.14;
use warnings;

use Win32::Console;
use List::Util qw( max );

=head1 NAME

C<String::Tagged::Terminal::Win32Console> - Windows-specific code for L<String::Tagged::Terminal>

=head1 SYNOPSIS

   # No user serviceable parts inside
   use String::Tagged::Terminal;

=head1 DESCRIPTION

This module provides support for L<String::Tagged::Terminal> to print to the
console on C<MSWin32>. It is not intended to be used directly.

=cut

use constant {
   ATTR_BLUE          => 0x0001,
   ATTR_GREEN         => 0x0002,
   ATTR_RED           => 0x0004,
   ATTR_INTENSITY     => 0x0008,
   ATTR_REVERSE_VIDEO => 0x4000, # Windows 10 onwards
   ATTR_UNDERSCORE    => 0x8000, # Windows 10 onwards
};

# We can only ever allocate a single console on Windows
our $WIN32_CONSOLE;

my %color_to_attr; # a cache

sub print_to_console
{
   my $self = shift;
   my ( $fh, %opts ) = @_;

   # Convert filenos to native Win32 file handles, this should also try
   # Win32API::File::FdGetOsFHandle( $fh );
   my $fileno = {
       1 => Win32::Console::STD_OUTPUT_HANDLE(),
       2 => Win32::Console::STD_ERROR_HANDLE(),
   }->{ $fh->fileno } || $fh->fileno;

   my %output_options = (
      ( $opts{no_color} ? ( except => [qw( fgindex bgindex )] ) : () ),
      only => [qw( fgindex bgindex bold under reverse )], # only process what we can handle
   );

   if( $fileno < 0 ) {
      # This looks like a Perl-internal FH, let's not output any formatting
      $fh->print( $self->build_terminal( %opts ) );
   }
   else {
      my $console = $opts{console} || do { $WIN32_CONSOLE ||= Win32::Console->new( $fileno ); };
      my $saved = $console->Attr();
      my $attr = $saved;

      $self->iter_substr_nooverlap( sub {
         my ( $s, %tags ) = @_;

         # Simple boolean attributes first
         foreach (
            # bold is handled at the end
            [ under     =>  ATTR_UNDERSCORE ], # Rendering is flakey under Windows 10
            # Windows console doesn't support italic, strike, blink
            [ reverse   =>  ATTR_REVERSE_VIDEO ],
         ) {
            my ( $tag, $on ) = @$_;
            $attr &= ~$on;

            $attr |= $on if $tags{$tag};
         }

         # Colour index attributes
         foreach (
            [ fgindex => 0, ],
            [ bgindex => 4, ],
         ) {
            my ( $tag, $shift ) = @$_;
            my $mask = 0x000F << $shift;
            $attr &= ~$mask;

            if( defined $tags{$tag} ) {
               my $idx = $tags{$tag};
               $attr |= ( $color_to_attr{$idx} //= _color_to_attr( $idx ) ) << $shift;
            }
            else {
               # Restore to previous
               $attr |= $saved & $mask;
            }
         }

         $attr |= ATTR_INTENSITY if $tags{bold};

         $console->Attr($attr);
         $console->Write($s);
      }, %output_options );

      $console->Attr( $saved );
   }
}

sub _color_to_attr
{
   my ( $idx ) = @_;

   my $attr = 0;

   if( $idx >= 16 ) {
      # Attempt to convert xterm256 range into RGB+I
      require Convert::Color;
      my $color = Convert::Color->new( "xterm:$idx" )->as_rgb;

      my ( $red, $green, $blue ) = $color->rgb;
      my $max = max( $red, $green, $blue );

      $attr |= ATTR_RED   if $red   > 0.5;
      $attr |= ATTR_GREEN if $green > 0.5;
      $attr |= ATTR_BLUE  if $blue  > 0.5;
      $attr |= ATTR_INTENSITY if $max > 0.75;
      $attr = ATTR_INTENSITY if $attr == 0 and
         $red == $green and $red == $blue and $max > 0.25; # dark grey
   }
   else {
      # The bits are swapped between ANSI and Win32 console
      $attr |= ATTR_RED   if $idx & 1;
      $attr |= ATTR_GREEN if $idx & 2;
      $attr |= ATTR_BLUE  if $idx & 4;
      $attr |= ATTR_INTENSITY if $idx & 8;
   }
   return $attr;
}

=head1 COMPATIBILITY NOTES

On Windows before Windows 10, only C<fgindex>, C<bgindex> and C<bold> are supported.

Starting with Windows 10, also C<under> and C<reverse> are supported.

On Windows, only a single output console is supported.

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>,
Max Maischein <corion@corion.net>

=cut

0x55AA;