370 lines
9.1 KiB
PHP
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;
|
|
}
|
|
} |