#!/usr/bin/env perl

#######################################################################
#            _   _  _____ _____   ______                     _        #
#      ╱╲   | ╲ | |╱ ____|_   _| |  ____|                   | |       #
#     ╱  ╲  |  ╲| | (___   | |   | |__   _ __   ___ ___   __| | ___   #
#    ╱ ╱╲ ╲ | . ` |╲___ ╲  | |   |  __| | '_ ╲ ╱ __╱ _ \ / _` |╱ _ ╲  #
#   ╱ ____ ╲| |╲  |____) |_| |_  | |____| | | | (_| (_) | (_| |  __╱  #
#  ╱_╱    ╲_╲_| ╲_|_____╱|_____| |______|_| |_|╲___╲___╱ ╲__,_|╲___|  #
#######################################################################
#                     Written By Richard Kelsch                       #
#                  © Copyright 2025 Richard Kelsch                    #
#                        All Rights Reserved                          #
#######################################################################

use strict;
use utf8; # Required
use charnames();
use constant {
    TRUE  => 1,
    FALSE => 0,
    YES   => 1,
    NO    => 0,
};

use Term::ANSIScreen qw( :cursor :screen );
use Term::ANSIColor;
use Time::HiRes qw( sleep );
use Term::ANSIEncode;
use Getopt::Long;
use List::Util qw(max);

# use Data::Dumper::Simple;$Data::Dumper::Terse=TRUE;$Data::Dumper::Indent=TRUE;$Data::Dumper::Useqq=TRUE;$Data::Dumper::Deparse=TRUE;$Data::Dumper::Quotekeys=TRUE;$Data::Dumper::Trailingcomma=TRUE;$Data::Dumper::Sortkeys=TRUE;$Data::Dumper::Purity=TRUE;$Data::Dumper::Deparse=TRUE;

# Since UTF-8 is the norm, it's enabled for all needed handles
binmode(STDERR, ":encoding(UTF-8)");
binmode(STDOUT, ":encoding(UTF-8)");
binmode(STDIN,  ":encoding(UTF-8)");

our $VERSION = $Term::ANSIEncode::VERSION;    # Pull in the version from Term::ANSIEncode


my $version   = FALSE;
my $help      = FALSE;
my $tokens    = FALSE;
my $rawtokens = FALSE;
my $symbols   = FALSE;
my $unicode   = FALSE;
my $colors    = FALSE;
my $Dump      = FALSE;
my $c16       = TRUE;
my $c256      = FALSE;
my $crgb      = FALSE;
my $frames    = FALSE;
my $rules     = FALSE;
my $ansi_modes = FALSE;

GetOptions(
	'a|ansi-modes'       => \$ansi_modes,
    'version'            => \$version,
    'help'               => \$help,
    'tokens'             => \$tokens,
    'rawtokens'          => \$rawtokens,
    'colors'             => \$colors,
    'symbols'            => \$symbols,
    'dump'               => \$Dump,
    'unicode'            => \$unicode,
	'frames'             => \$frames,
	'h|horizontal-rules' => \$rules,
);

if (exists($ENV{'COLORTERM'}) && $ENV{'COLORTERM'} eq 'truecolor') {
    $crgb = TRUE;
}
if (exists($ENV{'TERM'}) && $ENV{'TERM'} =~ /256/) {
    $c256 = TRUE;
}

my $header = '[% CLS %]For Best results, make sure your terminal type supports 256 (or more)
colors like "xterm-256color".  You should be using the "Awesome" fonts for
access to all Unicode characters and symbols:

    http://github.com/gabrielelana/awesome-terminal-fonts
';

###

my $sample_horizontal_rules = <<'RULES';

Horizontal rules

[% HORIZONTAL RULE PINK %][% BLACK %][% B_PINK %] PINK[% RESET %]
[% HORIZONTAL RULE GREEN %][% BLACK %][% B_GREEN %] GREEN[% RESET %]
[% HORIZONTAL RULE ORANGE %][% BLACK %][% B_ORANGE %] ORANGE[% RESET %]
[% HORIZONTAL RULE MAGENTA %][% BLACK %][% B_MAGENTA %] MAGENTA[% RESET %]
[% HORIZONTAL RULE CYAN %][% BLACK %][% B_CYAN %] CYAN[% RESET %]
[% HORIZONTAL RULE BLUE %][% WHITE %][% B_BLUE %] BLUE[% RESET %]
[% HORIZONTAL RULE RED %][% WHITE %][% B_RED %] RED[% RESET %]
[% HORIZONTAL RULE YELLOW %][% BLACK %][% B_YELLOW %] YELLOW[% RESET %]
[% HORIZONTAL RULE NAVY %][% WHITE %][% B_NAVY %] NAVY[% RESET %]
[% HORIZONTAL RULE ROYAL BLUE %][% WHITE %][% B_ROYAL BLUE %] ROYAL BLUE[% RESET %]

RULES

my $sample_frames = <<'FRAMES';

[% BOX BRIGHT YELLOW,1,7,17,5,DOUBLE        %]This is a text box with a DOUBLE frame[% ENDBOX %]
[% BOX BRIGHT GREEN,18,7,21,4,THIN          %]This is a text box with a THIN frame[% ENDBOX %]
[% BOX BRIGHT RED,39,7,16,5,THICK           %]This is a text box with a THICK frame[% ENDBOX %]
[% BOX BRIGHT BLUE,56,7,16,5,CIRCLE         %]This is a text box with a CIRCLE frame[% ENDBOX %]
[% BOX PINK,1,12,20,5,ROUNDED               %]This is a text box with a ROUNDED frame[% ENDBOX %]
[% BOX ORANGE,18,17,16,5,BLOCK              %]This is a text box with a BLOCK frame[% ENDBOX %]
[% BOX BRIGHT BLUE,1,17,16,5,WEDGE          %]This is a text box with a WEDGE frame[% ENDBOX %]
[% BOX MAGENTA,53,16,14,6,DOTS              %]This is a text box with a DOTS frame[% ENDBOX %]
[% BOX CYAN,22,12,17,5,DIAMOND              %]This is a text box with a DIAMOND frame[% ENDBOX %]
[% BOX WHITE,41,12,22,4,STAR                %]This is a text box with a STAR frame[% ENDBOX %]
[% BOX RED,35,17,17,5,SQUARE                %]This is a text box with a SQUARE frame[% ENDBOX %]
[% BOX BRIGHT WHITE,1,22,17,5,DITHERED      %]This is a text box with a DITHERED frame[% ENDBOX %]
[% BOX BRIGHT MAGENTA,19,22,17,5,HEARTS     %]This is a text box with a HEARTS frame[% ENDBOX %]
[% BOX SADDLE BROWN,37,22,17,5,CHRISTIAN    %]This is a text box with a CHRISTIAN frame[% ENDBOX %]
[% BOX ROYAL BLUE,55,22,17,5,ARROWS         %]This is a text box with an ARROWS frame[% ENDBOX %]
[% BOX FOREST GREEN,1,27,22,5,PARALLELOGRAM %]This is a text box with a PARALLELOGRAM frame[% ENDBOX %]
[% BOX CRIMSON,24,27,17,5,BIG WEDGE         %]This is a text box with a BIG WEDGE frame[% ENDBOX %]
[% BOX SALMON,42,27,17,5,BIG ARROWS         %]This is a text box with a BIG ARROWS frame[% ENDBOX %]
[% BOX YELLOW,60,27,16,5,NOTES              %]This is a text box with a NOTES frame[% ENDBOX %]

FRAMES

my $text = <<'VERSION';
[% CLS %][% YELLOW %]╔═════════════════════════════════════════════════════════════════════════════╗[% RESET %]
[% YELLOW %]║[% B_BLACK %][% RED %]               [% BRIGHT YELLOW %] _   _ [% GREEN %] _____ [% BRIGHT BLUE %]_____  [% BRIGHT WHITE %] ______                     _            [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% B_BLACK %][% RED %]          ╱╲   [% BRIGHT YELLOW %]│ ╲ │ │[% GREEN %]╱ ____│[% BRIGHT BLUE %]_   _│ [% BRIGHT WHITE %]│  ____│                   │ │           [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% B_BLACK %][% RED %]         ╱  ╲  [% BRIGHT YELLOW %]│  ╲│ │[% GREEN %] (___  [% BRIGHT BLUE %] │ │   [% BRIGHT WHITE %]│ │__   _ __   ___ ___   __│ │ ___       [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% B_BLACK %][% RED %]        ╱ ╱╲ ╲ [% BRIGHT YELLOW %]│ . ` │[% GREEN %]╲___ ╲ [% BRIGHT BLUE %] │ │   [% BRIGHT WHITE %]│  __│ │ '_ ╲ ╱ __╱ _ ╲ ╱ _` │╱ _ ╲      [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% B_BLACK %][% RED %]       ╱ ____ ╲[% BRIGHT YELLOW %]│ │╲  │[% GREEN %]____) │[% BRIGHT BLUE %]_│ │_  [% BRIGHT WHITE %]│ │____│ │ │ │ (_│ (_) │ (_│ │  __╱      [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% B_BLACK %][% RED %]      ╱_╱    ╲_╲[% BRIGHT YELLOW %]_│ ╲_│[% GREEN %]_____╱[% BRIGHT BLUE %]│_____│ [% BRIGHT WHITE %]│______│_│ │_│╲___╲___╱ ╲__,_│╲___│      [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% B_BLACK %]                                                                             [% YELLOW %]║[% B_BLACK %]
[% YELLOW %]╠═════════════════════════════════════════════════════════════════════════════╣[% RESET %]
[% YELLOW %]║[% RESET %][% B_COLOR 17 %]                         Written By [% BRIGHT YELLOW %]Richard Kelsch[% RESET %][% B_COLOR 17 %]                           [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% RESET %][% B_COLOR 17 %]                       Copyright ©[% GREEN %]2025 [% BRIGHT YELLOW %]Richard Kelsch[% RESET %][% B_COLOR 17 %]                        [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% RESET %][% B_COLOR 17 %]                            All Rights Reserved                              [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% RESET %][% B_COLOR 17 %]                         Perl Artistic License 2.0                           [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% RESET %][% B_COLOR 17 %]                               Version [% GREEN %]XXXX[% RESET %][% B_COLOR 17 %]                                  [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]║[% RESET %][% B_COLOR 17 %]              GitHub:  https://github.com/richcsst/ansi-encode               [% RESET %][% YELLOW %]║[% RESET %]
[% YELLOW %]╚═════════════════════════════════════════════════════════════════════════════╝[% RESET %]
VERSION
###
$text =~ s/XXXX/$VERSION/gs;

my $ansi = Term::ANSIEncode->new();

my @list = (
    0x20 .. 0x7F,
    0xA0 .. 0xFF,
    0x2010 .. 0x205F,
    0x2070 .. 0x242F,
    0x2440 .. 0x244F,
    0x2460 .. 0x29FF,
    0x1F300 .. 0x1F8BF,
    0x1F900 .. 0x1FBBF,
    0x1FBC0 .. 0x1FBCF,
    0x1FBF0 .. $ansi->{'finish'}
);

###
my $small = <<'SMALL';

[% CLS %]╔═════════════════════════════════════════════════════════════════════════╗
║[% B_BLACK %]                     [% RED %]┏━┓[% BRIGHT YELLOW %]┏┓╻[% GREEN %]┏━┓[% BRIGHT BLUE %]╻   [% BRIGHT WHITE %]┏━╸┏┓╻┏━╸┏━┓╺┳┓┏━╸                     [% RESET %]║
║[% B_BLACK %]                     [% RED %]┣━┫[% BRIGHT YELLOW %]┃┗┫[% GREEN %]┗━┓[% BRIGHT BLUE %]┃   [% BRIGHT WHITE %]┣╸ ┃┗┫┃  ┃ ┃ ┃┃┣╸                      [% RESET %]║
║[% B_BLACK %]                     [% RED %]╹ ╹[% BRIGHT YELLOW %]╹ ╹[% GREEN %]┗━┛[% BRIGHT BLUE %]╹   [% BRIGHT WHITE %]┗━╸╹ ╹┗━╸┗━┛╺┻┛┗━╸                     [% RESET %]║
╠═════════════════════════════════════════════════════════════════════════╣
║[% B_COLOR 52 %][% BRIGHT YELLOW %] DESCRIPTION                                                             [% RESET %]║
║     Markup text to ANSI encoder.                                        ║
╟─────────────────────────────────────────────────────────────────────────╢
║[% B_COLOR 52 %][% BRIGHT YELLOW %] USAGE                                                                   [% RESET %]║
║     [% CYAN %]ansi-encode[% RESET %] [options] [text file]                                   ║
╟─────────────────────────────────────────────────────────────────────────╢
║[% B_COLOR 52 %][% BRIGHT YELLOW %] OPTIONS                                                                 [% RESET %]║
║     -[% BRIGHT BLUE %]a[% RESET %] or --[% BRIGHT BLUE %]ansi-modes[% RESET %]                                                  ║
║         Show supported ANSI modes                                       ║
║                                                                         ║
║     -[% BRIGHT BLUE %]c[% RESET %] or --[% BRIGHT BLUE %]colors[% RESET %]                                                      ║
║         Show available colors                                           ║
║                                                                         ║
║     -[% BRIGHT BLUE %]d[% RESET %] or --[% BRIGHT BLUE %]dump[% RESET %] [search]                                               ║
║         Dump available sysmbols                                         ║
║                                                                         ║
║     -[% BRIGHT BLUE %]f[% RESET %] or --[% BRIGHT BLUE %]frames[% RESET %]                                                      ║
║         Show sample frame types                                         ║
║                                                                         ║
║     --[% BRIGHT BLUE %]help[% RESET %]                                                              ║
║         Usage information                                               ║
║                                                                         ║
║     -[% BRIGHT BLUE %]h[% RESET %] or --[% BRIGHT BLUE %]horizontal-rules[% RESET %]                                            ║
║         Show sample horizontal rules                                    ║
║                                                                         ║
║     -[% BRIGHT BLUE %]r[% RESET %] or --[% BRIGHT BLUE %]rawtokens[% RESET %]                                                   ║
║         Raw dump of available tokens.                                   ║
║                                                                         ║
║     -[% BRIGHT BLUE %]s[% RESET %] or --[% BRIGHT BLUE %]symbols[% RESET %] [search]                                            ║
║         Show available symbols and character tokens by name             ║
║                                                                         ║
║     -[% BRIGHT BLUE %]t[% RESET %] or --[% BRIGHT BLUE %]tokens[% RESET %]  [-[% BRIGHT BLUE %]e[% RESET %]]                                                ║
║         Show most used tokens                                           ║
║                                                                         ║
║     -[% BRIGHT BLUE %]u[% RESET %] or --[% BRIGHT BLUE %]unicode[% RESET %] [-[% BRIGHT BLUE %]e[% RESET %]] [search]                                       ║
║         Show available symbols and character tokens by unicode          ║
║                                                                         ║
║     -[% BRIGHT BLUE %]v[% RESET %] or --[% BRIGHT BLUE %]version[% RESET %]                                                     ║
║         Shows version and licensing info                                ║
╚═════════════════════════════════════════════════════════════════════════╝
SMALL

my $to = <<'TOKENS';

[% CLS %]Tokens have to be encapsulated inside [ % TOKEN % ] (the TOKEN must be
surrounded by at least one space on each side).  Colors beyond the standard 16
will require a terminal that supports 256 colors, up to 16 million colors.  It
is also highly recommended that your terminal supports UTF-8 for advanced
character/symbol support.  Some terminals may not support some features.

NOTE:  Use "less -r" to view ANSI in "less"

╭────────────────────────────────────────────────────────────────────────────────────────────────╮
TOKENS
###

$to .= '│' . colored(['bright_red'],      '                ::::::::::::   ...      :::  .   .,:::::::::.    :::. .::::::.                  ') . "│\n";
$to .= '│' . colored(['bright_magenta'], q{                ;;;;;;;;''''.;;;;;;;.   ;;; .;;,.;;;;''''`;;;;,  `;;;;;;`    `                  }) . "│\n";
$to .= '│' . colored(['bright_cyan'],    q{                     [[    ,[[     \[[, [[[[[/'   [[cccc   [[[[[. '[['[==/[[[[,                 }) . "│\n";
$to .= '│' . colored(['bright_blue'],    q{                     $$    $$$,     $$$_$$$$,     $$""""   $$$ "Y$c$$  '''    $                 }) . "│\n";
$to .= '│' . colored(['bright_green'],   q{                     88,   "888,_ _,88P"888"88o,  888oo,__ 888    Y88 88b    dP                 }) . "│\n";
$to .= '│' . colored(['bright_yellow'],  q{                     MMM     "YMMMMMP"  MMM "MMP" """"YUMMMMMM     YM  "YMmMY"                  }) . "│\n";

{
    foreach my $code ('special','clear','cursor','attributes','foreground ANSI 16', 'foreground ANSI 256', 'foreground ANSI TrueColor','background ANSI 16', 'background ANSI 256', 'background ANSI TrueColor','horizontal rules') {
		next if (($code =~ /256/ && ! $c256) || ($code =~ /TrueColor/ && ! $crgb));
        if ($code eq 'special') {
            $to .= '╞══ ' . colored(['bold','bright_white'],uc($code)) . ' ' . '═' x (36 - (2 + length($code))) . '═╤════════════════════════════════════════════════════════╡' . "\n";
        } else {
            $to .= '╞══ ' . colored(['bold','bright_white'],uc($code)) . ' ' . '═' x (36 - (2 + length($code))) . '═╪════════════════════════════════════════════════════════╡' . "\n";
        }
        if ($code eq 'horizontal rules') {
            $to .= '│' . sprintf('%-38s', 'HORIZONTAL RULE color') . ' │ ' . sprintf('%-54s','Horizontal rule the width of the screen in the') . ' │' . "\n";
            $to .= '│                                       │ ' . sprintf('%-54s','specified color') . ' │' . "\n";
		} elsif ($code =~ /^(foreground|background)/) {
			my $ncode = substr($code, 0, 10);
            my @names = grep(!/^(DEFAULT|B_DEFAULT|COLOR|GRAY|B_COLOR|B_GRAY)/,(sort(keys %{$ansi->{'ansi_meta'}->{$ncode}})));
			if ($code eq 'foreground ANSI 16') {
				unshift(@names,'DEFAULT');
			} elsif ($code eq 'background ANSI 16') {
				unshift(@names, 'B_DEFAULT');
			}
			foreach my $count (16 .. 231) {
				if ($code eq 'foreground ANSI 256') {
					push(@names,"COLOR $count");
				} elsif ($code eq 'background ANSI 256') {
					push(@names,"B_COLOR $count");
				}
			}
			foreach my $count (0 .. 23) {
				if ($code eq 'foreground ANSI 256') {
					push(@names,"GRAY $count");
				} elsif ($code eq 'background ANSI 256') {
					push(@names,"B_GRAY $count");
				}
			}
            while(scalar(@names)) {
                my $name = shift(@names);
				my $prefix = substr($ansi->{'ansi_meta'}->{$ncode}->{$name}->{'out'},2);
				my $type = type($prefix);
				if (($c16 && $type eq 'ANSI16' && $code =~ /ANSI 16/) || ($c256 && $type eq 'ANSI256' && $code =~ /ANSI 256/) || ($crgb && $type eq 'ANSITRUECOLOR' && $code =~ /TrueColor/)) {
					$to .= '│' . sprintf('%-39s',$name) . '[% RESET %]│ ' . sprintf('%-54s',$ansi->ansi_description($ncode,$name)) . ' │' . "\n";
				}
            }
			if ($code eq 'foreground ANSI TrueColor') {
				$to .= '│' . sprintf('%-38s', ' RGB red,green,blue') . ' │ ' . sprintf('%-54s','Set foreground color with Red, Green and Blue values') . ' │' . "\n";
			} elsif ($code eq 'background ANSI TrueColor') {
				$to .= '│' . sprintf('%-38s', ' B_RGB red,green,blue') . ' │ ' . sprintf('%-54s','Set background color with Red, Green and Blue values') . ' │' . "\n";
			}
		} elsif ($code eq 'special') {
            my @names = (sort(keys %{$ansi->{'ansi_meta'}->{$code}}));
            while(scalar(@names)) {
                my $name = shift(@names);
				$to .= '│' . sprintf('%-38s',$name) . ' │ ' . sprintf('%-54s',$ansi->ansi_description($code,$name)) . ' │' . "\n";
			}
			$to .= '│' . sprintf('%-38s','BOX color,column,row,width,height,type') . ' │ ' . sprintf('%-54s', 'Shows framed text box in the selected frame type and') . ' │' . "\n";
			$to .= '│' . sprintf('%-38s',' ') . ' │ ' . sprintf('%-54s', 'color.  Text goes between the BOX and ENDBOX token') . ' │' . "\n";
			$to .= '│' . sprintf('%-38s','    types:') . ' │ ' . sprintf('%-54s', 'See the "frames" option') . ' │' . "\n";
			foreach my $types (sort('DOUBLE', 'THIN', 'THICK', 'CIRCLE', 'ROUNDED', 'BLOCK', 'WEDGE', 'DOTS', 'DIAMOND', 'STAR', 'SQUARE', 'DITHERED', 'HEARTS', 'CHRISTIAN', 'ARROWS', 'PARALLELOGRAM', 'BIG WEDGE', 'BIG ARROWS', 'NOTES')) {
				$to .= '│' . sprintf('%38s',$types) . ' │ ' . sprintf('%-54s', ' ') . ' │' . "\n";
			}
			$to .= '│' . sprintf('%-38s','ENDBOX') . ' │ ' . sprintf('%-54s', 'Ends the BOX token function') . ' │' . "\n";
		} elsif ($code eq 'cursor') {
            my @names = (sort(keys %{$ansi->{'ansi_meta'}->{$code}}));
            while(scalar(@names)) {
                my $name = shift(@names);
				$to .= '│' . sprintf('%-38s',$name) . ' │ ' . sprintf('%-54s',$ansi->ansi_description($code,$name)) . ' │' . "\n";
			}
			$to .= '│' . sprintf('%-38s','LOCATE column,row') . ' │ ' . sprintf('%-54s','Sets the cursor location') . ' │' . "\n";
			$to .= '│' . sprintf('%-38s','SCROLL UP count') . ' │ ' . sprintf('%-54s','Scrolls the screen up by "count" lines') . ' │' . "\n";
			$to .= '│' . sprintf('%-38s','SCROLL DOWN count') . ' │ ' . sprintf('%-54s','Scrolls the screen down by "count" lines') . ' │' . "\n";
        } else {
            my @names = (sort(keys %{$ansi->{'ansi_meta'}->{$code}}));
            while(scalar(@names)) {
                my $name = shift(@names);
				$to .= '│' . sprintf('%-38s',$name) . ' │ ' . sprintf('%-54s',$ansi->ansi_description($code,$name)) . ' │' . "\n";
			}
        }
    }
}
  
$to .= '╰───────────────────────────────────────┴────────────────────────────────────────────────────────╯' . "\n";
###

{ # Colors
    my $new;
    foreach my $code (qw(attributes foreground background)) {
        if ($code eq 'background') {
            my $new;
            foreach my $name (keys %{$ansi->{'ansi_meta'}->{$code}}) {
                if ($name =~ /^(B_GRAY 0|B_GRAY 1|B_GRAY 2|B_GRAY 3|B_GRAY 4|B_COLOR 16|B_COLOR 17|B_COLOR 18|B_COLOR 19|B_COLOR 20|B_COLOR 21|B_COLOR 22|B_COLOR 23|B_HALAYA UBE|B_INTERNATIONAL KLEIN|B_UA BLUE|B_NAVY BLUE|B_BISTRE|B_BRITISH RACING GREEN|B_BULGARIAN ROSE|B_DUKE BLUE|B_ZAFFRE|B_CALIFORNIA GOLD|B_CHARCOAL|B_COOL BLACK|B_DARK GREEN|B_DARK JUNGLE GREEN|B_DARK MIDNIGHT BLUE|B_DARK SIENNA|B_DARK SCARLET|B_MAJESTY|B_MEDIUM JUNGLE GREEN|B_MYRTLE|B_ONYX|B_OXFORD BLUE|B_PERSIAN INDIGO|B_PHTHALO BLUE|B_PHTHALO GREEN|B_PRUSSIAN BLUE|B_SAINT PATRICK BLUE|B_SEAL BROWN|B_SMOKY BLACK|B_ULTRAMARINE|B_UP FOREST GREEN|B_TAUPE|B_WARM BLACK|B_ZINNWALDITE BROWN|B_BLACK|B_NAVY|B_MIDNIGHT BLUE|B_MEDIUM BLUE|B_INDIGO|B_MAROON|B_DEFAULT|B_DARK BLUE|B_BLUE)$/) {
                    $new = '[% ' . $name . ' %] ' . $name;
                } else {
                    $new = '[% BLACK %][% ' . $name . ' %] ' . $name;
                }
                $to =~ s/│$name  /│$new /gs;
            }
        } else {
            foreach my $name (grep(!/^(HIDE|BLACK)$/, sort(keys %{$ansi->{'ansi_meta'}->{$code}}))) {
                my $new = $ansi->{'ansi_meta'}->{$code}->{$name}->{'out'} . $name . $ansi->{'ansi_meta'}->{'attributes'}->{'RESET'}->{'out'};
                $to =~ s/│$name  /│$new  /gs;
            }
        }
    }
    $new = '[% RESET %]' . colored(['bright_green'],'│');
    $to =~ s/│/$new/gs;

    $new = colored(['bright_green'],'─');
    $to =~ s/─/$new/gs;

    $new = colored(['bright_green'],'┴');
    $to =~ s/┴/$new/gs;

    $new = colored(['bright_green'],'╭');
    $to =~ s/╭/$new/gs;

    $new = colored(['bright_green'],'╮');
    $to =~ s/╮/$new/gs;

    $new = colored(['bright_green'],'╰');
    $to =~ s/╰/$new/gs;

    $new = colored(['bright_green'],'╯');
    $to =~ s/╯/$new/gs;

    $new = colored(['bright_green'],'╞');
    $to =~ s/╞/$new/gs;

    $new = colored(['bright_green'],'═');
    $to =~ s/═/$new/gs;

    $new = colored(['bright_green'],'╤');
    $to =~ s/╤/$new/gs;

    $new = colored(['bright_green'],'╡');
    $to =~ s/╡/$new/gs;

    $new = colored(['bright_green'],'╪');
    $to =~ s/╪/$new/gs;

    $new = 'RULE [% FAINT %][% ITALIC %]color[% RESET %]';
    $to =~ s/RULE color/$new/s;

    $new = '[% FAINT %][% ITALIC %]red[% RESET %],[% FAINT %][% ITALIC %]green[% RESET %],[% FAINT %][% ITALIC %]blue[% RESET %]';
    $to =~ s/red,green,blue/$new/gs;
}

$| = 1;
if ($version) {
    $ansi->ansi_output($text);
} elsif ($ansi_modes) {
	my $out = "[% BRIGHT WHITE %]-------- [% BRIGHT YELLOW %]Modes supported [% BRIGHT WHITE %]----------[% RESET %]\n";
	if ($c16) {
		$out .= "        ANSI16:  [% GREEN %]Supported[% RESET %]\n";
	} else {
		$out .= "        ANSI16:  [% RED %]Not Supported[% RESET %]\n";
	}
    if ($c256) {
		$out .= "       ANSI256:  [% GREEN %]Supported[% RESET %]\n";
	} else {
		$out .= "       ANSI256:  [% RED %]Not Supported[% RESET %]\n";
	}
	if ($crgb) {
		$out .= "ANSI TrueColor:  [% GREEN %]Supported[% RESET %]\n";
	} else {
		$out .= "ANSI TrueColor:  [% RED %]Not Supported[% RESET %]\n";
	}
	$out .= "[% BRIGHT WHITE %]-----------------------------------[% RESET %]\n";
	$ansi->ansi_output($out);
} elsif ($help) {
    $ansi->ansi_output($small);
} elsif ($tokens) {
    $ansi->ansi_output($to);
} elsif ($frames) {
	$ansi->ansi_output("$header\n$sample_frames");
} elsif ($rules) {
	$ansi->ansi_output("$header\n$sample_horizontal_rules");
} elsif ($rawtokens) {
    print "\n╔", '═' x 131, "╗\n";
    $ansi->ansi_output('║[% B_BLACK %][% BRIGHT YELLOW %]' . ' ' x 46 . 'THE FOLLOWING ARE THE AVAILABLE TOKENS' . ' ' x 47 . "[% RESET %]║\n");
    print '╠', '═' x 32, '╦', '═' x 32, '╦', '═' x 32, '╦', '═' x 32, '╣', "\n";
    printf('║ %-31s║ %-31s║ %-31s║ %-31s║', 'COLOR 0 - COLOR 231', 'B_COLOR 0 - B_COLOR 231', 'GRAY 0 - GRAY 28', 'B_GRAY 0 - B_GRAY 28');
    print "\n║";
    my $width = 4;
    my $count = 1;
    foreach my $codes ('clear','cursor','attributes','foreground','background') {
        foreach my $token (sort(keys %{ $ansi->{'ansi_meta'}->{$codes} })) {
            next if ($token =~ /COLOR |GRAY /);
            printf(' %-31s', $token);
            $count++;
            if ($count > $width) {
                $count = 1;
                print "║\n║";
            } else {
                print '║';
            }
        } ## end foreach my $token (sort(keys...))
    }
    print "\r╚", '═' x 32, '╩', '═' x 32, '╩', '═' x 32, '╩', '═' x 32, '╝', "\n\n";
} elsif ($Dump) {
    my $temp = "\n\n";
    my @names;
    my $search = (scalar(@ARGV)) ? pop(@ARGV) : undef;
    if (defined($search) && $search ne '') {
        $ansi->ansi_output(charnames::string_vianame($search));
    } else {
        my $start = $ansi->{'start'};
        foreach my $code (@list) {
            my $name = charnames::viacode($code);
            $ansi->ansi_output(charnames::string_vianame($name) . ' ');
        }
    }
    print "\n";
} elsif ($unicode) {
    my $search = (scalar(@ARGV)) ? pop(@ARGV) : undef;
    $ansi->ansi_output("\nNOTE:  Not all terminals will support all characters\n" . '[% COLOR 52 %]╭─────────╮[% RESET %]' . "\n");
    if (defined($search)) {
        $ansi->ansi_output('[% COLOR 52 %]│ [% BRIGHT CYAN %]Unicode[% COLOR 52 %] │[% RESET %]' . "\n");
        $ansi->ansi_output('[% COLOR 52 %]├─────────┼─────[% RESET %]' . "\n");
        $ansi->ansi_output('[% COLOR 52 %]│[% BRIGHT WHITE %]' . sprintf('U+%05s',$search) . ' [% COLOR 52 %]│[% RESET %] ' . charnames::string_vianame(charnames::viacode(hex($search))));
        print "\n";
        $ansi->ansi_output('[% COLOR 52 %]╰─────────┴─────[% RESET %]' . "\n\n");
    } else {
        $ansi->ansi_output('[% COLOR 52 %]│ [% BRIGHT CYAN %]Unicode[% COLOR 52 %] │[% RESET %][% BRIGHT WHITE %] 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F[% RESET %]' . "\n");
        $ansi->ansi_output('[% COLOR 52 %]├─────────┼─────────────────────────────────────────────────────────────────[% RESET %]' . "\n");
        my $count = 0;
        foreach my $code (@list) {
            my $name = charnames::viacode($code);
            my $char = charnames::string_vianame($name);
            my $hcode = sprintf('U+%-5s',substr(sprintf('0x%05X',$code),2));
            if ($hcode ne '') {
                unless ($count) {
                    $ansi->ansi_output('[% COLOR 52 %]│[% BRIGHT WHITE %] ' . $hcode . ' [% COLOR 52 %]│[% RESET %] ' . $char);
                } else {
                    $ansi->ansi_output('  ' . $char);
                }
                $count++;
                if ($count > 15) {
                    $count = 0;
                    print "\n";
                    $ansi->ansi_output('[% COLOR 52 %]├─────────┼─────────────────────────────────────────────────────────────────[% RESET %]' . "\n") if ($code < 0x1FBFF);
                }
            } ## end if ($name ne '')
        }
        $ansi->ansi_output('[% COLOR 52 %]╰─────────┴─────────────────────────────────────────────────────────────────[% RESET %]' . "\n\n");
    }
} elsif ($symbols) {
    my @names;
    my $search = (scalar(@ARGV)) ? pop(@ARGV) : undef;
    my $size = 0;
    if (defined($search) && $search ne '') {
        if ($search =~ /^U\+/) {
            $search = substr($search,2);
            foreach my $code (@list) {
                my $ucode = sprintf('%X',$code);
                if ($ucode =~ /^$search/i) {
                    my $name = charnames::viacode($code);
                    push(@names, $name);
                    $size = max($size, length($name));
                }
            }
        } else {
            foreach my $code (@list) {
                my $name = charnames::viacode($code);
                if ($name =~ /$search/i) {
                    push(@names, $name);
                    $size = max($size, length($name));
                }
            }
        }
    } else {
        foreach my $code (@list) {
            my $name = charnames::viacode($code);
            push(@names, $name);
            $size = max($size, length($name));
        }
    }
    $ansi->ansi_output("\nNOTE:  Not all terminals will support all characters\n" . '[% COLOR 52 %]╭─────────┬─' . '─' x $size . '─╮[% RESET %]' . "\n");
    $ansi->ansi_output('[% COLOR 52 %]│[% B_BLACK %][% CYAN %] Unicode [% RESET %][% COLOR 52 %]│[% B_BLACK %]' . ' ' x ($size - 20) . '[% BRIGHT YELLOW %]Character Token Names [% RESET %]' . "[% COLOR 52 %]│[% RESET %]\n");
    $ansi->ansi_output('[% COLOR 52 %]├─────────┼─' . '─' x $size . "─┤[% RESET %]\n");
    while (scalar(@names)) {
        my $name = shift(@names);
        if ($name ne '') {
            if ($name =~ /^COMBINING/) {
                $ansi->ansi_output(sprintf('%s│ %s%-5X %s│%s %' . $size . 's %s│%s   %s', '[% COLOR 52 %]', '[% RESET %]U+', charnames::vianame($name), '[% COLOR 52 %]', '[% RESET %]', $name, '[% COLOR 52 %]', '[% RESET %]', charnames::string_vianame($name)) . "\n");
            } else {
                $ansi->ansi_output(sprintf('%s│ %s%-5X %s│%s %' . $size . 's %s│%s %s', '[% COLOR 52 %]', '[% RESET %]U+', charnames::vianame($name), '[% COLOR 52 %]', '[% RESET %]', $name, '[% COLOR 52 %]', '[% RESET %]', charnames::string_vianame($name)) . "\n");
            }
        } ## end if ($name ne '')
    } ## end while (scalar(@names))
    $ansi->ansi_output('[% COLOR 52 %]╰─────────┴─' . '─' x $size . '─╯[% RESET %]' . "\n\n");
} elsif ($colors) {
    my $grey   = '[% GRAY 8 %]';
    my $off    = '[% RESET %]';
    my $string = '';
    $string .= "\n" . '[% BRIGHT WHITE %] ANSI Colors and GRAY colors (requires a terminal with 256 color support for all colors)' . $off . "\n";
    $string .= $grey . '╭─────┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──╮' . $off . "\n";
    $string .= $grey . '│' . $off . 'COLOR';
    foreach my $i (0 .. 35) {
        $string .= $grey . '│' . $off . sprintf('%02d', $i);
    }
    $string .= $grey . "│\n├─────┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤$off\n";
    $string .= sprintf('%s│%s %4d%s│%s', $grey, $off, 0, $grey, $off);
    foreach my $i (0 .. 35) {
        if ($i <= 15) {
            $string .= chr(27) . '[48;5;' . $i . 'm  ' . $off . $grey . '│' . $off;
        } else {
            $string .= '  ' . $grey . '│' . $off;
        }
    } ## end foreach my $i (0 .. 35)
    foreach my $i (0 .. 6) {
        my $_i = ($i * 36) + 16;
        $string .= "\n" . $grey . '├─────┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤' . $off . "\n";
        if ($i == 6) {
            $string .= $grey . '│' . $off . 'GRAY ' . $grey . '│' . $off;
        } else {
            $string .= sprintf("%s│%s %4d%s│%s", $grey, $off, $_i, $grey, $off);
        }
        foreach my $j (0 .. 35) {
            $string .= chr(27) . '[48;5;' . ($_i + $j) . 'm  ' . chr(27) . '[m' . $grey . '│' . $off;
        }
    } ## end foreach my $i (0 .. 6)
    $string .= "\n" . $grey . '╰─────┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──╯' . $off . "\n\n";
    $string .= 'RGB and B_RGB take three values (0 - 255) for [% RED %]RED[% RESET %],[% GREEN %]GREEN[% RESET %],[% BLUE %]BLUE[% RESET %] for full 24 bit color' . "\n";
    $ansi->ansi_output($string);
    print 'For example:  [% RGB 255,255,255 %] WHITE [% RESET %] will show as ';
    $ansi->ansi_output('[% RGB 255,255,255 %] WHITE [% RESET %]' . "\n");
    print '              [% RGB 255,128,192 %] COLOR [% RESET %] will show as ';
    $ansi->ansi_output('[% RGB 255,128,192 %] COLOR [% RESET %]' . "\n");
} else {    # Output file to STDOUT
    my $file = $ARGV[0];
    if (defined($file) && -e $file) {
        my $size = -s $file;
        open(my $FILE, '<', $file);
        binmode($FILE, ":encoding(UTF-8)");
        read($FILE, my $text, $size);
        close($FILE);
        $ansi->ansi_output($text);
    } else {
        $ansi->ansi_output($small);
        $| = 1;
    }
} ## end else [ if ($version) ]

sub type {
    my $text = shift;

    my $type = 0;
    if ($text =~ /^\d+m/) {
        return('ANSI16');
    } elsif ($text =~ /^\d+\;\d\;\d+m/) {
        return('ANSI256');
    } elsif ($text =~ /^\d+\:\d\:\d+\:\d+\:\d+m/) {
        return('ANSITRUECOLOR');
    }
}

exit(0);

__END__

=pod

=encoding utf8

=head1 NAME

ANSI Encode

=head1 SYNOPSIS

A markup language to generate basic ANSI text.  A terminal that supports UTF-8 is highly recommended for graphics characters.

=head1 USAGE

 ansi_encode.pl [options] [file or search]

=head1 OPTIONS

Using no options expects a file name.

=over 4

=item --B<version> or -B<v>

Shows name, version information and brief licensing information.

=item --B<help> or -B<h>

Simple usage and options documentation

=item --B<tokens> or -B<y>

Shows the most used tokens available.  A token is encapsulated within [% and %] (with at lease one space on each side)

=item --B<rawtokens> or B<r>

Raw dump of useable tokens.

=item --B<symbols> or -B<s> [search]

Similar to "tokens", but instead shows special symbol character token names.

You may also add a search string to shorten the list.

IT IS HIGHLY SUGGESTED YOU USE A SEARCH STRING.  There are a lot of Unicode characters.  Each character has its own token.

=item --B<unicode> or -B<u> [search]

Similar to "tokens", but instead shows special symbol characters by unicode.

You may also add a search string to shorten the list.

IT IS HIGHLY SUGGESTED YOU USE A SEARCH STRING.

=item --B<dump> or -B<d> [search]

Does a raw dump of the symbols.

=back

=over 4

[% RED %]This is written in red[% RESET %]

B<RESET> changes output text to normal.

=back

=head1 TOKENS

=head2 GENERAL

=over 4

=item B<[% RETURN %]>

ASCII RETURN (13)

=item B<[% LINEFEED %]>

ASCII LINEFEED (10)

=item B<[% NEWLINE %]>

RETURN + LINEFEED (13 + 10)

=item B<[% CLS %]>

Places cursor at top left, screen cleared

=item B<[% CLEAR %]>

Clear screen only, cursor remains where it was

=item B<[% CLEAR LINE %]>

Clear to the end of line from current cursor position

=item B<[% CLEAR DOWN %]>

Clear down from current cursor position

=item B<[% CLEAR UP %]>

Clear up from current cursor position

=item B<[% RESET %]>

Reset all colors and attributes to their defaults

=back

=head2 CURSOR

=over 4

=item B<[% HOME %]>

Moves the cursor to the location 1,1 (Top left of screen)

=item B<[% UP %]> (Non-destructive)

Moves cursor up one step

=item B<[% DOWN %]> (Non-destructive)

Moves cursor down one step

=item B<[% RIGHT %]> (Non-destructive)

Moves cursor right one step

=item B<[% LEFT %]> (Non-destructive)

Moves cursor left one step

=item B<[% SAVE %]>

Save current cursor position (overwrites previous save, only one at a time)

=item B<[% RESTORE %]>

Place cursor at saved position

=item B<[% BOLD %]>

Bold text (not all terminals support this)

=item B<[% FAINT %]>

Faded text (not all terminals support this)

=item B<[% ITALIC %]>

Italicized text (not all terminals support this)

=item B<[% UNDERLINE %]>

Underlined text

=item B<[% SLOW BLINK %]>

Slow cursor blink

=item B<[% RAPID BLINK %]>

Rapid cursor blink (Some terminals just do a slow blink)

=item B<[% LOCATE column,row %]>

Set cursor position

=back

=head2 ATTRIBUTES

=over 4

=item B<[% INVERT %]>

Invert text (flip background and foreground attributes)

=item B<[% REVERSE %]>

Invert text (flip background and foreground attributes)

=item B<[% CROSSED OUT %]>

Crossed out.  Text with a line through the middle.

=item B<{% DEFAULT FONT %]>

Default font.  If the font was changed, then restore to the default font.

=back

=head2 FRAMES

=over 4

=item B<[% BOX color,x,y,width,height,type %]> text here B<[% ENDBOX %]>

Draw a frame around text.  There are many types to choose from.

=back

=head2 COLORS

=over 4

=item B<[% NORMAL %]>

Sets colors to default

=back

=head2 FOREGROUND

There are many more foreground colors available than the sixteen below.  However, the ones below should work on any color terminal.  Other colors may requite 256 and 16 million color support.  Most Linux X-Windows and Wayland terminal software should support the extra colors.  Some Windows terminal software should have "Term256" features.  You can used the "-t" option for all of the color tokens available or use the "RGB" token for access to 16 million colors.

=over 4

=item BLACK          = Black
=item RED            = Red
=item GREEN          = Green
=item YELLOW         = Yellow
=item BLUE           = Blue
=item MAGENTA        = Magenta
=item CYAN           = Cyan
=item WHITE          = White
=item DEFAULT        = Default foreground color
=item BRIGHT BLACK   = Bright black (dim grey)
=item BRIGHT RED     = Bright red
=item BRIGHT GREEN   = Lime
=item BRIGHT YELLOW  = Bright Yellow
=item BRIGHT BLUE    = Bright blue
=item BRIGHT MAGENTA = Bright magenta
=item BRIGHT CYAN    = Bright cyan
=item BRIGHT WHITE   = Bright white

=back

=head2 BACKGROUND

There are many more background colors available than the sixteen below.  However, the ones below should work on any color terminal.  Other colors may requite 256 and 16 million color support.  Most Linux X-Windows and Wayland terminal software should support the extra colors.  Some Windows terminal software should have "Term256" features.  You can used the "-t" option for all of the color tokens available or use the "B_RGB" token for access to 16 million colors.

=over 4

=item B_BLACK          = Black
=item B_RED            = Red
=item B_GREEN          = Green
=item B_YELLOW         = Yellow
=item B_BLUE           = Blue
=item B_MAGENTA        = Magenta
=item B_CYAN           = Cyan
=item B_WHITE          = White
=item B_DEFAULT        = Default background color
=item BRIGHT B_BLACK   = Bright black (grey)
=item BRIGHT B_RED     = Bright red
=item BRIGHT B_GREEN   = Lime
=item BRIGHT B_YELLOW  = Bright yellow
=item BRIGHT B_BLUE    = Bright blue
=item BRIGHT B_MAGENTA = Bright magenta
=item BRIGHT B_CYAN    = Bright cyan
=item BRIGHT B_WHITE   = Bright white

=back

=head2 HORIZONAL RULES

Makes a solid blank line, the full width of the screen with the selected color

For example, for a color of blue, use the following

  [% HORIZONTAL RULE BLUE %]

=over 4

=item HORIZONTAL RULE [color]             = A solid line of [color] background

=back

=head1 AUTHOR & COPYRIGHT

Richard Kelsch

 Copyright (C) 2025 Richard Kelsch
 All Rights Reserved
 Perl Artistic License

This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

L<https://perlfoundation.org/artistic-license-20.html>

=cut
