'systel','n'=>'username','a'=>'address#','d'=>'%date']; public $fields = NULL; // The fields in this frame. // @todo Move this to the database private $header = RED.'T'.BLUE.'E'.GREEN.'S'.YELLOW.'T'.MAGENTA.'!'; public function __construct(\App\Models\Frame $o,string $msg=NULL) { $this->frame = $o; $this->output = $this->hasFlag('clear') ? CLS : HOME; if ($msg) $this->output .= UP.$msg.HOME; if (! $this->hasFlag('ip')) { // Set the page header: CUG/Site Name | Page # | Cost $this->output .= $this->render_header($this->header). $this->render_page($this->frame->frame,$this->frame->index). $this->render_cost($this->frame->cost); } // Calculate fields and render output. $this->fields(); } public function __toString() { return $this->output; } /** * Convert the frame from Binary to Output * Look for fields within the frame. * * @param int $startline */ public function fields($startline=0) { $infield = FALSE; $this->fields = collect(); if ($startline) $this->output .= str_repeat(DOWN,$startline); // $fieldadrline = 1; // Scan the frame for a field start for ($y=$startline;$y<=$this->frame_length;$y++) { $fieldx = $fieldy = FALSE; for ($x=0;$x<$this->frame_width;$x++) { $posn = $y*40+$x; $byte = ord(isset($this->frame->content{$posn}) ? $this->frame->content{$posn} : ' ')%128; // dump(sprintf('Y: %s,X: %s, POSN: %s, BYTE: %s',$y,$x,$posn,$byte)); // Check for start-of-field if ($byte == ord(ESC)) { // Esc designates start of field (Esc-K is end of edit) $infield = TRUE; $fieldlength = 0; $fieldtype = ord(substr($this->frame->content,$posn+1,1))%128; $byte = ord(' '); // Replace ESC with space. } else { if ($infield) { if ($byte == $fieldtype) { $fieldlength++; $byte = ord(' '); // Replace field with space. if ($fieldx === false) { $fieldx = $x; $fieldy = $y; } // Is this a magic field? // @todo For page redisplay *00, we should show entered contents - for refresh *09 we should show updated contents if (array_get($this->fieldmap,chr($fieldtype)) ) { $field = $this->fieldmap[chr($fieldtype)]; //dump(['infield','byte'=>$byte,'fieldtype'=>$fieldtype,'field'=>$field,'strpos'=>strpos($field,'#')]); /* // address field has many lines. increment when hit on first character. if ($fieldlength == 1 && strpos($field,'#') !== false) { $field = str_replace('#',$fieldadrline,$field); dump(['field'=>$field,'fieldadrline'=>$fieldadrline,'fieldadrline'=>$fieldadrline]); $fieldadrline++; } */ // Replace field with Date if ($field == '%date') { if ($fieldlength == 1) $datetime = date('D d M H:ia'); if ($fieldlength <= strlen($datetime)) $byte = ord($datetime{$fieldlength-1}); } // @todo user data /* else if (isset($user[$field])) { if ($fieldlength <= strlen($user[$field])) { $byte = ord($user[$field]{$fieldlength-1}); } } /*else // pre-load field contents. PAM or *00 ? if (isset($fields[what]['value'])) { } */ } } else { Log::debug(sprintf('Frame: %s%s, Field found at [%s,%s], Type: %s, Length: %s','TBA','TBA',$fieldx,$fieldy,$fieldtype,$fieldlength)); $this->fields->push([ 'type'=>chr($fieldtype), 'length'=>$fieldlength, 'x'=>$fieldx, 'y'=>$fieldy, ]); $infield = FALSE; $fieldx = $fieldy = FALSE; } } } // truncate end of lines if (isset($pageflags['tru']) && substr($this->frame->content,$posn,40-$x) === str_repeat(' ',40-$x)) { $this->output .= CR . LF; break; } $this->output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte); } } } /** * Return the Frame Number */ public function framenum() { return $this->frame->frame.$this->frame->index; } /** * Return the flag for this page * * CLEAR: Clear Screen before rendering. * * @param $flag * @return bool */ private function hasFlag($flag) { return $this->frame->hasFlag($flag); } /** * Render the cost of the frame * * @param int $cost * @return string * @throws \Exception */ private function render_cost(int $cost) { if ($cost > 999) throw new \Exception('Price too high'); if ($cost > 100) $color = RED; elseif ($cost > 0) $color = YELLOW; else $color = GREEN; return sprintf($color.'% '.$this->cost_length.'.0f%s',$cost,$this->cost_unit); } /** * Render the Site Header * * @param string $header * @return bool|string */ private function render_header(string $header) { $filler = ($this->strlenv($header) < $this->header_length) ? str_repeat(' ',$this->header_length-$this->strlenv($header)) : ''; return substr($header.$filler,0,$this->header_length+substr_count($this->header,ESC)); } /** * Render the Frame Number * * @param int $num * @param string $frame * @return string * @throws \Exception */ private function render_page(int $num,string $frame) { if ($num > 999999999) throw new \Exception('Page Number too big',500); if (strlen($frame) !== 1) throw new \Exception('Frame invalid',500); return sprintf(WHITE.'% '.$this->pagenum_length.'.0f%s',$num,$frame); } /** * Get the route for the key press * * @param string $read */ public function route(string $read) { // @todo return FALSE; } /** * Calculate the length of text * * ESC characters are two chars, and need to be counted as one. * * @param $text * @return int */ function strlenv($text) { return strlen($text)-substr_count($text,ESC); } public static function testFrame() { // Simulate a DB load $o = new \App\Models\Frame; $o->content = ''; $o->flags = ['ip']; $o->type = 'a'; $o->frame = 999; $o->index = 'a'; // Header $o->content .= substr(R_RED.'T'.R_BLUE.'E'.R_GREEN.'S'.R_YELLOW.'T-12345678901234567890',0,20). R_WHITE.'999999999a'.R_RED.sprintf('%07.0f',999).'u'; $o->content .= str_repeat('+-',18).' '.R_RED.'01'; $o->content .= 'Date: '.ESC.str_repeat('d',25).str_repeat('+-',4); $o->content .= 'Name: '.ESC.str_repeat('u',5).str_repeat('+-',14); $o->content .= 'Address: '.ESC.str_repeat('a',20).''.str_repeat('+-',5); $o->content .= ' : '.ESC.str_repeat('a',20).''.str_repeat('+-',5); return $o; } /** * Return the Frame Type */ public function type() { return $this->frame->type(); } }