Added converting ANSI to a binary format, and custom font rendering
This commit is contained in:
380
app/Classes/ANSI.php
Normal file
380
app/Classes/ANSI.php
Normal file
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class ANSI
|
||||
{
|
||||
/* 8 BIT COLORS
|
||||
* Foreground 0-3, Background 4-7
|
||||
* Color 0-2, 4-6, High 3, 7
|
||||
*/
|
||||
private const COLOR_8BIT = 0x1B;
|
||||
private const COLOR_HIGH = 1; // Bit 1.
|
||||
private const COLOR_BLACK = 0; // F30 B40
|
||||
private const COLOR_RED = 1; // F31 B41
|
||||
private const COLOR_GREEN = 2; // F32 B42
|
||||
private const COLOR_YELLOW = 3; // F33 B43
|
||||
private const COLOR_BLUE = 4; // F34 B44
|
||||
private const COLOR_MAGENTA = 5; // F35 B45
|
||||
private const COLOR_CYAN = 6; // F36 B46
|
||||
private const COLOR_WHITE = 7; // F37 B47
|
||||
|
||||
private const DEFAULT_FORE = 37;
|
||||
private const DEFAULT_BACK = 40;
|
||||
|
||||
/* 256 BIT COLORS */
|
||||
/* 0x26 0xAA 0xBB */
|
||||
|
||||
public const LOGO_BUFFER_WIDTH = 1;
|
||||
public const LOGO_BUFFER_HEIGHT = 0; // Not implemented
|
||||
public const LOGO_OFFSET_WIDTH = 1;
|
||||
public const LOGO_OFFSET_HEIGHT = 0; // Not implemented
|
||||
|
||||
private Collection $width;
|
||||
private Collection $ansi;
|
||||
private const BUFREAD = 2048;
|
||||
|
||||
/* MAGIC METHODS */
|
||||
|
||||
public function __construct(string $file)
|
||||
{
|
||||
$this->width = collect();
|
||||
$this->ansi = collect();
|
||||
|
||||
$f = fopen($file,'r');
|
||||
while (! feof($f)) {
|
||||
$line = stream_get_line($f,self::BUFREAD,"\r");
|
||||
|
||||
// If the last line is blank, we'll ignore it
|
||||
if ((! feof($f)) || $line) {
|
||||
$this->width->push(self::line_width($line,FALSE));
|
||||
$this->ansi->push(array_map(function($item) { return ord($item); },str_split($line,1)));
|
||||
}
|
||||
}
|
||||
fclose($f);
|
||||
|
||||
return $this->ansi;
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
switch ($key) {
|
||||
case 'width':
|
||||
return $this->width;
|
||||
|
||||
case 'max_width':
|
||||
return $this->width->max()+self::LOGO_BUFFER_WIDTH+self::LOGO_OFFSET_WIDTH;
|
||||
|
||||
case 'height':
|
||||
return $this->ansi->count()+self::LOGO_BUFFER_HEIGHT+self::LOGO_OFFSET_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
/* STATIC METHODS */
|
||||
|
||||
/**
|
||||
* Convert a binary ANSI file back to its ANSI version
|
||||
*
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
public static function ansi(string $file)
|
||||
{
|
||||
return static::bin_to_ansi((new self($file))->ansi->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a binary ANS file to ANSI text
|
||||
*
|
||||
* @param array $ansi
|
||||
* @param bool $return
|
||||
* @return string
|
||||
*/
|
||||
public static function bin_to_ansi(array $ansi,bool $return=TRUE): string
|
||||
{
|
||||
$output = '';
|
||||
$escape = FALSE;
|
||||
$current = []; // Default Screen
|
||||
|
||||
foreach ($ansi as $line) {
|
||||
foreach ($line as $char) {
|
||||
if ($char == 0x1b) {
|
||||
$escape = TRUE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($escape) {
|
||||
if ($x=static::color($char,$current))
|
||||
$output .= "\x1b[".$x;
|
||||
|
||||
$escape = FALSE;
|
||||
|
||||
} else {
|
||||
$output .= chr($char);
|
||||
}
|
||||
}
|
||||
|
||||
if ($return)
|
||||
$output .= "\r\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ANSI file into a binary form
|
||||
*
|
||||
* @param string $file
|
||||
* @return Collection
|
||||
*/
|
||||
public static function binary(string $file): Collection
|
||||
{
|
||||
$f = fopen($file,'r');
|
||||
$escape = FALSE;
|
||||
$ansi = FALSE;
|
||||
$buffer = '';
|
||||
$line = '';
|
||||
$result = collect();
|
||||
|
||||
$current = self::reset();
|
||||
|
||||
while (! feof($f)) {
|
||||
$c = fread($f,1);
|
||||
|
||||
switch (ord($c)) {
|
||||
// Ignore \n (0x0a)
|
||||
case 0x0a:
|
||||
continue 2;
|
||||
|
||||
// New line \r (0x0d)
|
||||
case 0x0d:
|
||||
$result->push($line);
|
||||
$line = '';
|
||||
continue 2;
|
||||
|
||||
// We got our ESC
|
||||
case 0x1b:
|
||||
$escape = TRUE;
|
||||
continue 2;
|
||||
|
||||
case ord('['):
|
||||
if ($escape) {
|
||||
$ansi = TRUE;
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($ansi) {
|
||||
switch ($c) {
|
||||
case ';':
|
||||
case 'm':
|
||||
if ((int)$buffer === 0) {
|
||||
$current = self::reset();
|
||||
|
||||
} elseif ((int)$buffer === 1) {
|
||||
$current['h'] = 1;
|
||||
|
||||
} elseif (((int)$buffer >= 30) && (int)$buffer <= 37) {
|
||||
$current['f'] = (int)$buffer;
|
||||
|
||||
} elseif (((int)$buffer >= 40) && (int)$buffer <= 47) {
|
||||
$current['b'] = (int)$buffer;
|
||||
}
|
||||
|
||||
if ($c == 'm') {
|
||||
$ansi = FALSE;
|
||||
$escape = FALSE;
|
||||
$line .= chr(0x1b).chr(self::code($current));
|
||||
}
|
||||
|
||||
$buffer = '';
|
||||
break;
|
||||
|
||||
default:
|
||||
$buffer .= $c;
|
||||
}
|
||||
|
||||
} else {
|
||||
// If escape is still set, but we didnt get an ANSI starter, then we need to record the ESC.
|
||||
if ($escape) {
|
||||
$line .= chr(0x1b);
|
||||
$escape = FALSE;
|
||||
}
|
||||
|
||||
$line .= $c;
|
||||
}
|
||||
}
|
||||
|
||||
// In case our line didnt end \r and we still have data
|
||||
if ($line)
|
||||
$result->push($line);
|
||||
|
||||
fclose($f);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of 8 bit color codes to a binary form
|
||||
*
|
||||
* @param array $code
|
||||
* @return int
|
||||
*/
|
||||
private static function code(array $code): int
|
||||
{
|
||||
$result = 0;
|
||||
foreach ($code as $item) {
|
||||
switch ($item) {
|
||||
// Color Reset
|
||||
case 0: $result = 0; break;
|
||||
// High Intensity
|
||||
case 1: $result |= self::COLOR_HIGH; break;
|
||||
|
||||
// Foreground
|
||||
case 30: $result |= (self::COLOR_BLACK<<1); break;
|
||||
case 31: $result |= (self::COLOR_RED<<1); break;
|
||||
case 32: $result |= (self::COLOR_GREEN<<1); break;
|
||||
case 33: $result |= (self::COLOR_YELLOW<<1); break;
|
||||
case 34: $result |= (self::COLOR_BLUE<<1); break;
|
||||
case 35: $result |= (self::COLOR_MAGENTA<<1); break;
|
||||
case 36: $result |= (self::COLOR_CYAN<<1); break;
|
||||
case 37: $result |= (self::COLOR_WHITE<<1); break;
|
||||
|
||||
// Background
|
||||
case 40: $result |= (self::COLOR_BLACK<<5); break;
|
||||
case 41: $result |= (self::COLOR_RED<<5); break;
|
||||
case 42: $result |= (self::COLOR_GREEN<<5); break;
|
||||
case 43: $result |= (self::COLOR_YELLOW<<5); break;
|
||||
case 44: $result |= (self::COLOR_BLUE<<5); break;
|
||||
case 45: $result |= (self::COLOR_MAGENTA<<5); break;
|
||||
case 46: $result |= (self::COLOR_CYAN<<5); break;
|
||||
case 47: $result |= (self::COLOR_WHITE<<5); break;
|
||||
|
||||
default:
|
||||
dd('unhandled code:'.$item);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert binary code to ANSI escape code
|
||||
*
|
||||
* @param int $code
|
||||
* @param array $current
|
||||
* @return string
|
||||
*/
|
||||
private static function color(int $code,array &$current): string
|
||||
{
|
||||
if (! $current)
|
||||
$current = static::reset();
|
||||
|
||||
$h = ($code&0x01);
|
||||
|
||||
switch ($x=(($code>>1)&0x07)) {
|
||||
case self::COLOR_BLACK: $f = '30'; break;
|
||||
case self::COLOR_RED: $f = '31'; break;
|
||||
case self::COLOR_GREEN: $f = '32'; break;
|
||||
case self::COLOR_YELLOW: $f = '33'; break;
|
||||
case self::COLOR_BLUE: $f = '34'; break;
|
||||
case self::COLOR_MAGENTA: $f = '35'; break;
|
||||
case self::COLOR_CYAN: $f = '36'; break;
|
||||
case self::COLOR_WHITE: $f = '37'; break;
|
||||
default:
|
||||
dump(['unknown color'=>$x]);
|
||||
}
|
||||
|
||||
switch ($x=(($code>>5)&0x07)) {
|
||||
case self::COLOR_BLACK: $b = '40'; break;
|
||||
case self::COLOR_RED: $b = '41'; break;
|
||||
case self::COLOR_GREEN: $b = '42'; break;
|
||||
case self::COLOR_YELLOW: $b = '43'; break;
|
||||
case self::COLOR_BLUE: $b = '44'; break;
|
||||
case self::COLOR_MAGENTA: $b = '45'; break;
|
||||
case self::COLOR_CYAN: $b = '46'; break;
|
||||
case self::COLOR_WHITE: $b = '47'; break;
|
||||
default:
|
||||
dump(['unknown color'=>$x]);
|
||||
}
|
||||
$return = '';
|
||||
$highlight_changed = false;
|
||||
|
||||
if ($h !== $current['h']) {
|
||||
$return .= $h;
|
||||
$current['h'] = $h;
|
||||
$highlight_changed = TRUE;
|
||||
}
|
||||
|
||||
if ($f !== $current['f']) {
|
||||
if (! $highlight_changed || $h || (($f != self::DEFAULT_FORE) || ($b != self::DEFAULT_BACK)))
|
||||
$return .= (strlen($return) ? ';' : '').$f;
|
||||
|
||||
$x = $f;
|
||||
$current['f'] = $f;
|
||||
}
|
||||
|
||||
if ($b !== $current['b']) {
|
||||
if (! $highlight_changed || $h || (($x != self::DEFAULT_FORE) || ($b != self::DEFAULT_BACK)))
|
||||
$return .= (strlen($return) ? ';' : '').$b;
|
||||
|
||||
$current['b'] = $b;
|
||||
}
|
||||
|
||||
return ($return !== '') ? $return.'m' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the width of a line
|
||||
*
|
||||
* @param string $line
|
||||
* @param bool $buffer
|
||||
* @return int
|
||||
*/
|
||||
public static function line_width(string $line,bool $buffer=TRUE): int
|
||||
{
|
||||
return strlen(preg_replace('/\x1b./','',$line))+($buffer ? self::LOGO_OFFSET_WIDTH+self::LOGO_BUFFER_WIDTH : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ANSI was reset (normally CSI [ 0m)
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private static function reset(): array
|
||||
{
|
||||
return [
|
||||
'h'=>0,
|
||||
'f'=>self::DEFAULT_FORE,
|
||||
'b'=>self::DEFAULT_BACK
|
||||
];
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* Return a specific line
|
||||
*
|
||||
* @param int $line
|
||||
* @return array
|
||||
*/
|
||||
public function line(int $line): array
|
||||
{
|
||||
return $this->ansi->get($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the binary line
|
||||
*
|
||||
* @param int $line
|
||||
* @return string
|
||||
*/
|
||||
public function line_raw(int $line): string
|
||||
{
|
||||
return join('',array_map(function($item) { return chr($item); },$this->line($line)));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user