clrghouz/app/Classes/BBS/Page/Viewdata.php
2024-05-28 12:44:55 +10:00

370 lines
9.1 KiB
PHP

<?php
namespace App\Classes\BBS\Page;
use Illuminate\Support\Arr;
use App\Classes\BBS\Frame\{Char,Field};
use App\Classes\BBS\Page;
use App\Models\BBS\Mode;
class Viewdata extends Page
{
protected const FRAME_WIDTH = 40;
protected const FRAME_HEIGHT = 22;
protected const FRAME_PROVIDER_LENGTH = 23;
protected const FRAME_PAGE_LENGTH = 11; // Spec is 9+1 - including our color code.
protected const FRAME_COST_LENGTH = 6; // including our color code
protected const FRAME_SPACE = 0; // Since colors take a space, this is not needed
public const MOSIAC = 0x10;
// Toggles
public const CONCEAL = 0x20;
public const REVEAL = 0x2000; // @temp Turns off Conceal
public const SEPARATED = 0x40;
public const BLOCKS = 0x4000; // @temp Turns off Separated
public const STEADY = 0x8000; // @temp (turn off flash)
public const DOUBLE = 0x100;
public const NORMAL = 0x1000; // @temp Turns off Double Height
public const HOLD = 0x200;
public const RELEASE = 0x20000; // @temp turns off Hold
public const NEWBACK = 0x400;
public const BLACKBACK = 0x800;
//public const ESC = 27;
//public const I_CLEAR_CODE = 0;
//public const I_HIGH_CODE = 1;
public const FG_BLACK_CODE = 0x40;
public const FG_RED_CODE = 0x41;
public const FG_GREEN_CODE = 0x42;
public const FG_YELLOW_CODE = 0x43;
public const FG_BLUE_CODE = 0x44;
public const FG_MAGENTA_CODE = 0x45;
public const FG_CYAN_CODE = 0x46;
public const FG_WHITE_CODE = 0x47;
public const I_BLINK_CODE = 0x48;
public const I_STEADY = 0x49;
public const I_NORMAL = 0x4c;
public const I_DOUBLE_CODE = 0x4d;
public const I_CONCEAL = 0x58;
public const I_BLOCKS = 0x59;
public const I_SEPARATED = 0x5a;
public const I_BLACKBACK = 0x5c;
public const I_NEWBACK = 0x5d;
public const I_HOLD = 0x5e;
public const I_REVEAL = 0x5f;
public const RED = 1;
//public const GREEN = 2;
public const YELLOW = 3;
public const BLUE = 4;
//public const MAGENTA = 5;
public const CYAN = 6;
public const WHITE = 7;
public const MOSIAC_RED_CODE = 0x51;
public const MOSIAC_GREEN_CODE = 0x52;
public const MOSIAC_YELLOW_CODE = 0x53;
public const MOSIAC_BLUE_CODE = 0x54;
public const MOSIAC_MAGENTA_CODE = 0x55;
public const MOSIAC_CYAN_CODE = 0x56;
public const MOSIAC_WHITE_CODE = 0x57; // W
public const input_map = [
'd' => 'DATE',
'e' => 'EMAIL',
'f' => 'FULLNAME',
'n' => 'USER',
'p' => 'PASS',
't' => 'TIME',
'y' => 'NODE',
'z' => 'TOKEN',
];
public static function strlenv($text):int
{
return strlen($text)-substr_count($text,ESC);
}
public function __construct(int $frame,string $index='a')
{
parent::__construct($frame,$index);
$this->mo = Mode::where('name','Viewdata')->single();
}
public function __get(string $key): mixed
{
switch ($key) {
case 'color_page':
return chr(self::WHITE);
case 'color_unit':
return chr(self::GREEN);
default:
return parent::__get($key);
}
}
public function attr(array $field): string
{
// Noop
return '';
}
/**
* This function converts Viewtex BIN data into an array of attributes
*
* With viewdata, a character is used/display regardless of whether it is a control character, or an actual display
* character.
*
* @param string $contents Our ANSI content to convert
* @param int $width Canvas width before we wrap to the next line
* @param int $yoffset fields offset when rendered (based on main window)
* @param int $xoffset fields offset when rendered (based on main window)
* @param int|null $debug Enable debug mode
* @return array
* @throws \Exception
*/
public function parse(string $contents,int $width,int $yoffset=0,int $xoffset=0,?int $debug=NULL): array
{
$result = [];
$lines = collect(explode("\r\n",$contents));
if ($debug)
dump(['lines'=>$lines]);
$i = 0; // Intensity
$bg = self::BG_BLACK; // Background color
$fg = self::WHITE; // Foreground color
$new_line = $fg + $bg + $i; // Attribute int
// Attribute state on a new line
$attr = $new_line;
$y = 0;
while ($lines->count() > 0) {
$x = 0;
$line = $lines->shift();
$result[$y+1] = [];
if ($this->debug)
dump(['next line'=>$line,'length'=>strlen($line)]);
while (strlen($line) > 0) {
if ($debug)
dump(['y:'=>$y,'attr'=>$attr,'line'=>$line,'length'=>strlen($line)]);
if ($x >= $width) {
$x = 0;
// Each new line, we reset the attrs
$attr = $new_line;
$y++;
}
/* parse control codes */
$m = [];
preg_match('/^([\x00-\x09\x0c-\x1a\x1c-\x1f])/',$line,$m);
if (count($m)) {
$line = substr($line,strlen(array_shift($m)));
$attr = 0;
switch ($xx=ord(array_shift($m))) {
case 0x00:
$attr += self::BLACK;
break;
case 0x01:
$attr += self::RED;
break;
case 0x02:
$attr += self::GREEN;
break;
case 0x03:
$attr += self::YELLOW;
break;
case 0x04:
$attr += self::BLUE;
break;
case 0x05:
$attr += self::MAGENTA;
break;
case 0x06:
$attr += self::CYAN;
break;
case 0x07:
$attr += self::WHITE;
break;
case 0x08:
$attr = self::BLINK;
break;
case 0x09:
$attr = self::STEADY;
break;
/*
case 0x0a:
//$attr = self::ENDBOX; // End Box (Unused?)
break;
case 0x0b:
//$attr = self::STARTBOX; // Start Box (Unused?)
break;
*/
case 0x0c:
$attr = self::NORMAL;
break;
case 0x0d:
$attr = self::DOUBLE;
break;
case 0x0e:
$attr = self::NORMAL; // @todo Double Width (Unused)?
break;
case 0x0f:
$attr = self::NORMAL; // @todo Double Width (Unused?)
break;
case 0x10:
$attr = self::MOSIAC|self::BLACK;
break;
case 0x11:
$attr = self::MOSIAC|self::RED;
break;
case 0x12:
$attr = self::MOSIAC|self::GREEN;
break;
case 0x13:
$attr = self::MOSIAC|self::YELLOW;
break;
case 0x14:
$attr = self::MOSIAC|self::BLUE;
break;
case 0x15:
$attr = self::MOSIAC|self::MAGENTA;
break;
case 0x16:
$attr = self::MOSIAC|self::CYAN;
break;
case 0x17:
$attr = self::MOSIAC|self::WHITE;
break;
case 0x18:
$attr = self::CONCEAL;
break;
case 0x19:
$attr = self::BLOCKS;
break;
case 0x1a:
$attr = self::SEPARATED;
break;
/*
// We are using this for field input
case 0x1b:
//$attr = self::NORMAL; // CSI
break;
*/
case 0x1c:
$attr = self::BLACKBACK; // Black Background
break;
case 0x1d:
$attr = self::NEWBACK; // New Background
break;
case 0x1e:
$attr = self::HOLD; // Mosiac Hold
break;
case 0x1f:
$attr = self::RELEASE; // Mosiac Release
break;
// Catch all for other codes
default:
dump(['char'=>$xx]);
$attr = 0xff00;
}
if ($debug)
dump(sprintf('- got control code [%02x] at [%02dx%02d]',$attr,$y,$x));
$result[$y+1][$x+1] = new Char(NULL,$attr);
$x++;
continue;
}
/**
* For response frames, a dialogue field is signalled by a CLS (0x0c) followed by a number of dialogue
* characters [a-z]. The field ends by the first different character from the initial dialogue character.
* The CLS is a "privileged space" and the dialogue characters defined the dialogue field.
*
* Standard dialogue characters:
* + n = name
* + t = telephone number
* + d = date and time
* + a = address
* + anything else free form, typically 'f' is used
*
* Source: Prestel Bulk Update Technical Specification
*/
/* parse an input field */
// Since 0x0c is double, we'll use good ol' ESC 0x1b
$m = [];
preg_match('/^([\x1b|\x9b])([a-z])\2+/',$line,$m);
if (count($m)) {
$line = substr($line,strlen($m[0]));
$len = strlen(substr($m[0],1));
$field = new Field([
'attribute' => [],
'name' => Arr::get(self::input_map,$m[2],$m[2]),
'pad' => '.',
'size' => $len,
'type' => $m[2],
'value' => NULL,
'x' => $x+$xoffset,
'y' => $y+$yoffset,
]);
(($m[1] === "\x1b") ? $this->fields_input : $this->fields_dynamic)->push($field);
$result[$y+1][++$x] = new Char(' ',$attr); // The \x1b|\x9b is the privileged space.
for ($xx=0;$xx<$len;$xx++)
$result[$y+1][$x+1+$xx] = new Char('.',$attr);
$x += $len;
continue;
}
/* set character and attribute */
$ch = $line[0];
$line = substr($line,1);
if ($debug)
dump(sprintf('Storing [%02xx%02x] [%s] with [%02x]',$y,$x,$ch,$attr));
/* validate position */
if ($y < 0)
$y = 0;
if ($x < 0)
$x = 0;
if ($attr === null)
throw new \Exception('Attribute is null?');
$result[$y+1][$x+1] = new Char($ch,$attr);
$x++;
}
// Each new line, we reset the attrs
$attr = $new_line;
$y++;
}
return $result;
}
}