290 lines
7.0 KiB
PHP
290 lines
7.0 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace App\Classes\BBS\Frame;
|
||
|
|
||
|
use App\Classes\BBS\Page\{Ansi,Viewdata};
|
||
|
use App\Models\BBS\Mode;
|
||
|
|
||
|
class Char {
|
||
|
/** @var int|null Attributes for the character (ie: color) */
|
||
|
private ?int $attr;
|
||
|
/** @var string|null Character to be shown */
|
||
|
private ?string $ch;
|
||
|
|
||
|
public function __construct(string $ch=NULL,int $attr=NULL)
|
||
|
{
|
||
|
$this->ch = $ch;
|
||
|
$this->attr = $attr;
|
||
|
}
|
||
|
|
||
|
public function __get(string $key): mixed
|
||
|
{
|
||
|
switch ($key) {
|
||
|
case 'attr': return $this->attr;
|
||
|
case 'ch': return $this->ch;
|
||
|
|
||
|
default:
|
||
|
throw new \Exception('Unknown key:'.$key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function __isset($key): bool
|
||
|
{
|
||
|
return isset($this->{$key});
|
||
|
}
|
||
|
|
||
|
public function __set(string $key,mixed $value): void
|
||
|
{
|
||
|
switch ($key) {
|
||
|
case 'ch':
|
||
|
if (strlen($value) !== 1)
|
||
|
throw new \Exception(sprintf('CH can only be 1 char: [%s]',$value));
|
||
|
|
||
|
$this->{$key} = $value;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new \Exception('Unknown key:'.$key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function __toString()
|
||
|
{
|
||
|
return sprintf('%04x [%s]|',$this->attr,$this->ch);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the color codes required to draw the current character
|
||
|
*
|
||
|
* @param Mode $mo Service we are rendering for
|
||
|
* @param int|null $last last rendered char
|
||
|
* @param bool $debug debug mode
|
||
|
* @return string|NULL
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public function attr(Mode $mo,int $last=NULL,bool $debug=FALSE): string|NULL
|
||
|
{
|
||
|
$ansi = collect();
|
||
|
|
||
|
if ($debug)
|
||
|
dump('- last:'.$last.', this:'.$this->attr);
|
||
|
|
||
|
switch ($mo->name) {
|
||
|
case 'ansi':
|
||
|
if ($debug) {
|
||
|
dump(' - this BG_BLACK:'.($this->attr & Ansi::BG_BLACK));
|
||
|
dump(' - last BG_BLACK:'.($last & Ansi::BG_BLACK));
|
||
|
|
||
|
dump(' - this HIGH:'.($this->attr & Ansi::HIGH));
|
||
|
dump(' - last HIGH:'.($last & Ansi::HIGH));
|
||
|
|
||
|
dump(' - this BLINK:'.($this->attr & Ansi::BLINK));
|
||
|
dump(' - last BLINK:'.($last & Ansi::BLINK));
|
||
|
}
|
||
|
|
||
|
// If high was in the last, and we dont have high now, we need 0, but we need to turn back on flash if it was there
|
||
|
// If flash was in the last, and we dont have flash now, we need to 0 but we need to turn on high if it was there
|
||
|
$reset = FALSE;
|
||
|
if ((($this->attr & Ansi::BG_BLACK) && (! ($last & Ansi::BG_BLACK)))
|
||
|
|| ((! ($this->attr & Ansi::BLINK)) && ($last & Ansi::BLINK))
|
||
|
|| ((! ($this->attr & Ansi::HIGH)) && ($last & Ansi::HIGH)))
|
||
|
{
|
||
|
$ansi->push(Ansi::I_CLEAR_CODE);
|
||
|
$reset = TRUE;
|
||
|
$last = Ansi::BG_BLACK|Ansi::LIGHTGRAY;
|
||
|
}
|
||
|
|
||
|
if (($this->attr & Ansi::HIGH)
|
||
|
&& ((($this->attr & Ansi::HIGH) !== ($last & Ansi::HIGH)) || ($reset && ($last & Ansi::HIGH)))) {
|
||
|
$ansi->push(Ansi::I_HIGH_CODE);
|
||
|
}
|
||
|
|
||
|
if (($this->attr & Ansi::BLINK)
|
||
|
&& ((($this->attr & Ansi::BLINK) !== ($last & Ansi::BLINK)) || ($reset && ($last & Ansi::BLINK)))) {
|
||
|
$ansi->push(Ansi::I_BLINK_CODE);
|
||
|
}
|
||
|
|
||
|
$c = ($this->attr & 0x07);
|
||
|
$l = ($last & 0x07);
|
||
|
|
||
|
// Foreground
|
||
|
switch ($c) {
|
||
|
case Ansi::BLACK:
|
||
|
$r = Ansi::FG_BLACK_CODE;
|
||
|
break;
|
||
|
case Ansi::RED:
|
||
|
$r = Ansi::FG_RED_CODE;
|
||
|
break;
|
||
|
case Ansi::GREEN:
|
||
|
$r = Ansi::FG_GREEN_CODE;
|
||
|
break;
|
||
|
case Ansi::BROWN:
|
||
|
$r = Ansi::FG_BROWN_CODE;
|
||
|
break;
|
||
|
case Ansi::BLUE:
|
||
|
$r = Ansi::FG_BLUE_CODE;
|
||
|
break;
|
||
|
case Ansi::MAGENTA:
|
||
|
$r = Ansi::FG_MAGENTA_CODE;
|
||
|
break;
|
||
|
case Ansi::CYAN:
|
||
|
$r = Ansi::FG_CYAN_CODE;
|
||
|
break;
|
||
|
case Ansi::LIGHTGRAY:
|
||
|
$r = Ansi::FG_LIGHTGRAY_CODE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ($r && ($c !== $l))
|
||
|
$ansi->push($r);
|
||
|
|
||
|
// Background
|
||
|
if ($this->attr & 0x70) {
|
||
|
$c = ($this->attr & 0x70);
|
||
|
$l = ($last & 0x70);
|
||
|
|
||
|
switch ($this->attr & 0x70) {
|
||
|
case Ansi::BG_BLACK:
|
||
|
$r = Ansi::BG_BLACK_CODE;
|
||
|
break;
|
||
|
case Ansi::BG_RED:
|
||
|
$r = Ansi::BG_RED_CODE;
|
||
|
break;
|
||
|
case Ansi::BG_GREEN:
|
||
|
$r = Ansi::BG_GREEN_CODE;
|
||
|
break;
|
||
|
case Ansi::BG_BROWN:
|
||
|
$r = Ansi::BG_BROWN_CODE;
|
||
|
break;
|
||
|
case Ansi::BG_BLUE:
|
||
|
$r = Ansi::BG_BLUE_CODE;
|
||
|
break;
|
||
|
case Ansi::BG_MAGENTA:
|
||
|
$r = Ansi::BG_MAGENTA_CODE;
|
||
|
break;
|
||
|
case Ansi::BG_CYAN:
|
||
|
$r = Ansi::BG_CYAN_CODE;
|
||
|
break;
|
||
|
case Ansi::BG_LIGHTGRAY:
|
||
|
$r = Ansi::BG_LIGHTGRAY_CODE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ($r && ($c !== $l))
|
||
|
$ansi->push($r);
|
||
|
}
|
||
|
|
||
|
if ($debug)
|
||
|
dump([' - ansi:' =>$ansi]);
|
||
|
|
||
|
return $ansi->count() ? sprintf('%s[%sm',($debug ? '': "\x1b"),$ansi->join(';')) : NULL;
|
||
|
|
||
|
case 'viewdata':
|
||
|
if ($debug)
|
||
|
dump(sprintf('Last: %02x, Attr: %02x',$last,$this->attr));
|
||
|
|
||
|
switch ($this->attr) {
|
||
|
// \x08
|
||
|
case Viewdata::BLINK:
|
||
|
$r = Viewdata::I_BLINK_CODE;
|
||
|
break;
|
||
|
// \x09
|
||
|
case Viewdata::STEADY:
|
||
|
$r = Viewdata::I_STEADY;
|
||
|
break;
|
||
|
// \x0c
|
||
|
case Viewdata::NORMAL:
|
||
|
$r = Viewdata::I_NORMAL;
|
||
|
break;
|
||
|
// \x0d
|
||
|
case Viewdata::DOUBLE:
|
||
|
$r = Viewdata::I_DOUBLE_CODE;
|
||
|
break;
|
||
|
// \x18
|
||
|
case Viewdata::CONCEAL:
|
||
|
$r = Viewdata::I_CONCEAL;
|
||
|
break;
|
||
|
// \x19
|
||
|
case Viewdata::BLOCKS:
|
||
|
$r = Viewdata::I_BLOCKS;
|
||
|
break;
|
||
|
// \x1a
|
||
|
case Viewdata::SEPARATED:
|
||
|
$r = Viewdata::I_SEPARATED;
|
||
|
break;
|
||
|
// \x1c
|
||
|
case Viewdata::BLACKBACK:
|
||
|
$r = Viewdata::I_BLACKBACK;
|
||
|
break;
|
||
|
// \x1d
|
||
|
case Viewdata::NEWBACK:
|
||
|
$r = Viewdata::I_NEWBACK;
|
||
|
break;
|
||
|
// \x1e
|
||
|
case Viewdata::HOLD:
|
||
|
$r = Viewdata::I_HOLD;
|
||
|
break;
|
||
|
// \x1f
|
||
|
case Viewdata::RELEASE:
|
||
|
$r = Viewdata::I_REVEAL;
|
||
|
break;
|
||
|
|
||
|
// Not handled
|
||
|
// \x0a-b,\x0e-f,\x1b
|
||
|
case 0xff00:
|
||
|
dump($this->attr);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
$mosiac = ($this->attr & Viewdata::MOSIAC);
|
||
|
$c = ($this->attr & 0x07);
|
||
|
|
||
|
if ($debug)
|
||
|
dump(sprintf('Last: %02x, Attr: %02x, Color: %02x',$last,$this->attr,$c));
|
||
|
|
||
|
// Color control \x00-\x07, \x10-\x17
|
||
|
switch ($c) {
|
||
|
/*
|
||
|
case Viewdata::BLACK:
|
||
|
$r = Viewdata::FG_BLACK_CODE;
|
||
|
break;
|
||
|
*/
|
||
|
case Viewdata::RED:
|
||
|
$r = $mosiac ? Viewdata::MOSIAC_RED_CODE : Viewdata::FG_RED_CODE;
|
||
|
break;
|
||
|
case Viewdata::GREEN:
|
||
|
$r = $mosiac ? Viewdata::MOSIAC_GREEN_CODE : Viewdata::FG_GREEN_CODE;
|
||
|
break;
|
||
|
case Viewdata::YELLOW:
|
||
|
$r = $mosiac ? Viewdata::MOSIAC_YELLOW_CODE : Viewdata::FG_YELLOW_CODE;
|
||
|
break;
|
||
|
case Viewdata::BLUE:
|
||
|
$r = $mosiac ? Viewdata::MOSIAC_BLUE_CODE : Viewdata::FG_BLUE_CODE;
|
||
|
break;
|
||
|
case Viewdata::MAGENTA:
|
||
|
$r = $mosiac ? Viewdata::MOSIAC_MAGENTA_CODE : Viewdata::FG_MAGENTA_CODE;
|
||
|
break;
|
||
|
case Viewdata::CYAN:
|
||
|
$r = $mosiac ? Viewdata::MOSIAC_CYAN_CODE : Viewdata::FG_CYAN_CODE;
|
||
|
break;
|
||
|
case Viewdata::WHITE:
|
||
|
$r = $mosiac ? Viewdata::MOSIAC_WHITE_CODE : Viewdata::FG_WHITE_CODE;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
if ($debug)
|
||
|
dump('Not a color?:'.$c);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($debug)
|
||
|
dump(sprintf('= result: ESC[%s](%02x) for [%s]',chr($r),$r,$this->ch));
|
||
|
|
||
|
return chr($r);
|
||
|
|
||
|
default:
|
||
|
throw new \Exception($this->type.': has not been implemented');
|
||
|
}
|
||
|
}
|
||
|
}
|