2021-09-26 10:42:21 +00:00
< ? php
namespace App\Classes ;
use Illuminate\Support\Arr ;
use Illuminate\Support\Collection ;
class Font
{
private const DEBUG = FALSE ;
protected const MSG_WIDTH = 79 ;
2021-10-02 00:02:21 +00:00
private string $text = '' ;
private int $width = 0 ;
private int $height = 0 ;
public function __get ( $key )
{
switch ( $key ) {
case 'height' :
return $this -> height ;
case 'width' :
return $this -> width ;
default :
throw new \Exception ( sprintf ( 'Unknown key %s' , $key ));
}
}
/**
* Message text , goes after header , and if a logo , to the right of it
*
* @ param string $text
*/
public function addText ( string $text )
{
$this -> text = $text ;
$this -> dimensions ();
}
/**
* Characters used in the font
*
* @ return Collection
*/
private function chars () : Collection
{
2022-12-04 02:30:38 +00:00
static $chars = [];
2021-10-02 00:02:21 +00:00
2022-12-04 02:30:38 +00:00
if ( $this -> text && empty ( $chars [ $this -> text ])) {
2021-10-02 00:02:21 +00:00
// Trim any leading/trailing spaces
$text = trim ( strtolower ( $this -> text ));
2022-12-04 02:30:38 +00:00
$chars [ $this -> text ] = collect ();
2021-10-02 00:02:21 +00:00
// Work out the characters we need
foreach ( array_unique ( str_split ( $text )) as $c ) {
if ( ! $x = Arr :: get ( static :: FONT , $c ))
continue ;
2022-12-04 02:30:38 +00:00
$chars [ $this -> text ] -> put ( $c , $x );
2021-10-02 00:02:21 +00:00
}
}
2022-12-04 02:30:38 +00:00
return $chars [ $this -> text ] ? : collect ();
2021-10-02 00:02:21 +00:00
}
/**
* Full width of the rendered text
*
* @ return void
*/
private function dimensions () : void
{
$chars = $this -> chars ();
$escape = FALSE ;
foreach ( str_split ( strtolower ( $this -> text )) as $c ) {
if ( $c == " \x1b " ) {
$escape = TRUE ;
continue ;
} elseif ( $escape ) {
$escape = FALSE ;
continue ;
}
$this -> width += ( $x = Arr :: get ( $chars -> get ( $c ), 0 )) ? count ( $x ) : 1 ;
if ( $x )
$this -> height = (( $y = count ( $chars -> get ( $c ))) > $this -> height ) ? $y : $this -> height ;
}
// If the last character is a space, we'll reduce the width
$space = TRUE ;
2022-12-04 02:30:38 +00:00
foreach ( $chars -> get ( $c ) as $x )
2021-10-02 00:02:21 +00:00
if ( array_pop ( $x ) != 32 ) {
$space = FALSE ;
break ;
}
if ( $space )
$this -> width -- ;
}
public function render_line ( int $line ) : string
{
$chars = $this -> chars ();
$result = '' ;
$escape = FALSE ;
$ansi = FALSE ;
foreach ( str_split ( strtolower ( $this -> text )) as $c ) {
if ( ord ( $c ) == 0x1b ) {
$escape = TRUE ;
} elseif ( $escape && $c ) {
$result .= ANSI :: ansi_color ( ord ( $c ));
$escape = FALSE ;
$ansi = TRUE ;
} elseif (( $c == ' ' ) || ( ! $font_chars = $chars -> get ( $c ))) {
$result .= $c ;
} else {
foreach ( Arr :: get ( $font_chars , $line ) as $char )
$result .= chr ( $char );
}
}
return $result . ( $ansi ? ANSI :: ansi_color ( 0x0e ) : '' );
}
2021-09-26 10:42:21 +00:00
/**
* This function will format text to static :: MSG_WIDTH , as well as adding the logo .
* It is up to the text to be spaced appropriately to wrap around the icon .
*
* @ param string $text
* @ param ANSI | null $logo
* @ param bool $right
* @ param int $step
* @ return string
*/
public static function format_msg ( string $text , ANSI $logo = NULL , int $step = 1 , bool $right = FALSE ) : string
{
$result = '' ;
$result_height = 0 ;
$current_pos = 0 ;
while ( $current_pos < strlen ( $text )) {
$result_line = '' ; // Line being created
$lc = 0 ; // Line length count (without ANSI)
$buffer = $step ? $logo -> width -> skip ( intdiv ( $result_height , $step ) * $step ) -> take ( $step ) -> max () : 1 ;
// Add our logo
if ( $result_height <= $logo -> height - 1 ) {
$line = ANSI :: bin_to_ansi ([ $logo -> line ( $result_height )], FALSE );
$lc = $logo -> line_width ( $logo -> line_raw ( $result_height ), FALSE );
$result_line = str_repeat ( ' ' , ANSI :: LOGO_OFFSET_WIDTH )
. $line
. str_repeat ( ' ' , ANSI :: LOGO_BUFFER_WIDTH + ( $right ? 0 : $buffer - $lc ));
}
// Look for a return
$return_pos = strpos ( $text , " \r " , $current_pos );
// We have a return
if ( $return_pos !== FALSE ) {
$subtext = substr ( $text , $current_pos , $return_pos - $current_pos );
// If the reset of the string will fit on the current line
} elseif ( strlen ( $text ) - $current_pos < static :: MSG_WIDTH - $lc ) {
$subtext = substr ( $text , $current_pos );
// Get the next lines worth of chars
} else {
$subtext = substr ( $text , $current_pos , static :: MSG_WIDTH - $buffer );
// Include the text up to the last space
if ( substr ( $text , $current_pos + strlen ( $subtext ), 1 ) !== ' ' )
$subtext = substr ( $text , $current_pos , strrpos ( $subtext , ' ' ));
}
$result .= $result_line .
str_repeat ( ' ' ,( $right ? static :: MSG_WIDTH - $lc - strlen ( $subtext ) + (( ! $lc ) ? ( ANSI :: LOGO_OFFSET_WIDTH + ANSI :: LOGO_BUFFER_WIDTH ) : 0 ) : 0 ))
. $subtext . " \r \n " ;
$current_pos += strlen ( $subtext ) + 1 ;
$result_height ++ ;
}
// In case our text is shorter than the logo
for (; $result_height < $logo -> height ; $result_height ++ ) {
$result .= str_repeat ( ' ' , ANSI :: LOGO_OFFSET_WIDTH )
. ANSI :: bin_to_ansi ([ $logo -> line ( $result_height )], FALSE )
. str_repeat ( ' ' , ANSI :: LOGO_BUFFER_WIDTH )
. " \r \n " ;
}
return $result ;
}
/**
* The height of this font ( based on the 1 st char )
*
* @ return int
*/
public static function height () : int
{
return count ( Arr :: get ( static :: FONT , 'a' ));
}
/**
* Convert text into a graphical font
*
* @ param string $text
* @ param Collection $width
* @ param int $height
* @ param int $step
* @ return string
*/
public static function fontText ( string $text , Collection $width , int $height , int $step ) : string
{
return self :: text_to_font ( $text , $width , $height , $step );
}
/**
* Convert text to this font
* This function will pad the text to fit around the icon , so that the icon + font fils to self :: MSG_WIDTH
*
* @ param string $text
* @ param Collection $icon_width Width to make the font
* @ param int $icon_height Minimal width for this height , then full width ( self :: MSG_WIDTH )
* @ param int $step The grouping of lines ( normally font height ) around the icon
* @ return string
*/
protected static function text_to_font ( string $text , Collection $icon_width , int $icon_height , int $step ) : string
{
$chars = collect (); // Characters needed for this $text
$font_height = 0 ; // Max height of text using font
// Trim any leading/trailing spaces
$text = trim ( strtolower ( $text ));
// Work out the characters we need
foreach ( array_unique ( str_split ( $text )) as $c ) {
if (( $c == ' ' ) || ( ! $x = Arr :: get ( static :: FONT , $c ))) {
continue ;
}
$chars -> put ( $c , $x );
$font_height = (( $y = count ( $x )) > $font_height ) ? $y : $font_height ;
}
if ( self :: DEBUG ) dump ([ 'uniquechars' => $chars -> count (), 'font_height' => $font_height ]);
if ( self :: DEBUG ) dump ([ 'drawing' => $text , 'textlen' => strlen ( $text ), 'logo_width' => $icon_width , 'logo_height' => $icon_height ]);
$result = '' ; // Our result
$current_pos = 0 ; // Our current position through $text
$result_height = 0 ; // Our current line height
$line_pos = 0 ; // Our current character position for this line of the font
while ( $current_pos < strlen ( $text )) {
if ( self :: DEBUG ) dump ( sprintf ( 'current position %d of %d' , $current_pos , strlen ( $text )));
for ( $line = 0 ; $line < $font_height ; $line ++ ) {
if ( $line == 0 ) {
$line_icon_width = $icon_width
2021-10-02 00:02:21 +00:00
-> skip ( intdiv ( $result_height , $step ) * $step )
-> take ( $step )
-> max ();
2021-09-26 10:42:21 +00:00
if ( $line_icon_width )
$line_icon_width += ANSI :: LOGO_OFFSET_WIDTH + ANSI :: LOGO_BUFFER_WIDTH ;
$line_width = self :: MSG_WIDTH - $line_icon_width ; // Width we are working towards, initially $icon_width until height then its self::MSG_WIDTH
}
$line_result = '' ; // Our current line of font
if ( self :: DEBUG ) dump ( sprintf ( '- current line %d of %d' , $line + 1 , $font_height ));
// If we are mid way through rendering a font, and have already finished with the height offset, we'll need to fill with blanks
if (( $line_width !== self :: MSG_WIDTH ) && ( $result_height > $icon_height - 1 ))
$line_result .= str_repeat ( ' ' , $line_icon_width );
$line_pos = $current_pos ;
$next_space_pos = $current_pos ;
$next_next_space_width = 0 ; // What our width will be after the next next space
$next_next_space_pos = 0 ; // The position of the next space after the next one
$next_next_space_chars = 0 ; // The number of chars between the next space and the next next space
$current_line_width = 0 ; // Our current width of the line
while ( $current_line_width < $line_width ) {
if ( self :: DEBUG ) dump ( sprintf ( ' - current width %d of %d, and we are working on char %d' , $current_line_width , $line_width , $line_pos ));
$find_space_pos = $line_pos ;
// Find our next char
if ( self :: DEBUG ) dump ( sprintf ( ' - find our next space from %d after %d' , $find_space_pos , $next_space_pos ));
$next_space_chars = 0 ;
if ( $next_space_pos <= $line_pos ) {
if ( ! $next_next_space_pos ) {
while (( $find_space_pos < strlen ( $text )) && (( $c = substr ( $text , $find_space_pos ++ , 1 )) !== ' ' )) {
$x = count ( Arr :: get ( $chars -> get ( $c ), $line ));
if ( self :: DEBUG ) dump ( sprintf ( ' + char is [%s] (%x) and will take %d chars' , $c , ord ( $c ), $x ));
$next_space_chars += $x ;
}
$next_space_pos = $find_space_pos ;
$next_next_space_pos = $find_space_pos ;
$next_next_space_width = $current_line_width + $next_space_chars ;
} else {
$next_space_pos = $next_next_space_pos ;
$next_space_chars = $next_next_space_chars ;
}
// Find our next next space, which we'll use to decide whether we need to include a space when we find one
$next_next_space_chars = 0 ;
while (( $next_next_space_pos < strlen ( $text )) && (( $c = substr ( $text , $next_next_space_pos ++ , 1 )) !== ' ' )) {
$next_next_space_chars += count ( Arr :: get ( $chars -> get ( $c ), $line ,[]));
if ( self :: DEBUG ) dump ( sprintf ( ' + char is [%s] (%x) and will take us to %d' , $c , ord ( $c ), $next_next_space_chars ));
}
$next_next_space_width = $current_line_width + $next_space_chars + $next_next_space_chars ;
};
if ( self :: DEBUG ) dump ( sprintf ( ' - our next space is: [%s] (%x) at %d in %d chars, taking %d chars (taking our width to %d)' , $c , ord ( $c ), $find_space_pos , $find_space_pos - $line_pos , $next_space_chars , $current_line_width + $next_space_chars ));
// We are only spaces, so we can return to the next line
if ( $current_line_width + $next_space_chars > $line_width ) {
if ( self :: DEBUG ) dump ( ' = next char should go onto new line' );
// Only go to a new line if we already have chars
if ( $current_line_width )
break ;
}
$c = substr ( $text , $line_pos , 1 );
if (( $c == ' ' ) || ( ! $font_chars = $chars -> get ( $c ))) {
// Ignore this space if we are at the beginning of the line
if ( $current_line_width && ( $next_next_space_width < $line_width )) {
$line_result .= $c ;
$current_line_width ++ ;
}
} else {
if ( self :: DEBUG ) dump ( sprintf ( 'adding char [%s] which is [%s]' , $c , join ( '|' , Arr :: get ( $font_chars , $line ))));
foreach ( $x = Arr :: get ( $font_chars , $line ) as $char )
$line_result .= chr ( $char );
$current_line_width += count ( $x );
}
$line_pos ++ ;
if ( self :: DEBUG ) dump ( sprintf ( ' = line width [%d of %d] and we are on char [%d] our space is [%d]' , $current_line_width , $line_width , $line_pos , $find_space_pos ));
if ( $line_pos == strlen ( $text )) {
if ( self :: DEBUG ) dump ( sprintf ( ' = we are finished, as we are on char %d on line %d' , $line_pos , $line ));
break ;
}
}
$result_height ++ ;
$result .= $line_result . " \r " ;
}
$current_pos = $line_pos ;
if ( self :: DEBUG ) dump ( sprintf ( '= new line starting with char [%d] - our width is [%d] and we are on line [%d]' , $current_pos , $line_width , $result_height ));
}
if ( self :: DEBUG )
dd ([ 'result' => $result ]);
return $result ;
}
}