<?php namespace App\Classes; use Illuminate\Support\Arr; /** * Page layout is as follows * * |----------|--------------| * | LOGO | HEADER | * | LEFT BOX | TEXT | * |----------|--------------| */ class Page { protected const MSG_WIDTH = 78; private const LOGO_OFFSET_WIDTH = 1; private Font $header; private string $header_foot = ''; private bool $header_right; private int $header_underline = 0; private Font $left_box; private ANSI $logo; private int $step = 0; private string $text = ''; private bool $text_right = FALSE; public function __construct(bool $crlf=FALSE) { $this->header = new Font; $this->logo = new ANSI; $this->left_box = new Font; $this->crlf = $crlf; $this->text = ''; } public function __get($key) { switch ($key) { // Height of the header case 'header_height': return $this->header->height+($this->header_foot ? 1 : 0)+($this->header_underline ? 1 : 0); // The width of the left column case 'left_width': return $this->logo->width->max() ?: $this->left_box->width; // The height of the left column case 'left_height': return $this->logo->height+$this->left_box->height+self::LOGO_OFFSET_WIDTH; // The right width case 'right_width': return self::MSG_WIDTH-$this->left_width; default: throw new \Exception(sprintf('Unknown key %s',$key)); } } /** * Message header - goes at top, right of logo * * @param Font $text * @param string $foot * @param bool $right * @param int $underline * @throws \Exception */ public function addHeader(Font $text,string $foot='',bool $right=FALSE,int $underline=0): void { if (($text->width > $this->right_width) || (strlen($foot) > $this->right_width)) throw new \Exception(sprintf('Header or Header Footer greater than available width')); $this->header = $text; $this->header_foot = $foot; $this->header_right = $right; $this->header_underline = $underline; } /** * Content that can go below logo, to the left of the text, if text $logo_left_border is TRUE * * @param Font $text * @throws \Exception */ public function addLeftBoxContent(Font $text): void { if ($this->left_width && ($text->width > $this->left_width)) throw new \Exception(sprintf('Leftbox content greater than icon width')); $this->left_box = $text; } /** * Message logo - goes at top left * * @param ANSI $ansi */ public function addLogo(ANSI $ansi): void { $this->logo = $ansi; } /** * Message text, goes after header, and if a logo, to the right of it * * @param string $text Main Body Text * @param bool $right Right Aligned */ public function addText(string $text,bool $right=FALSE) { $this->text .= $text; $this->text_right = $right; } /** * Render the page. * * @return string * @throws \Exception */ public function render(): string { $result = ''; $result_height = 0; $current_pos = 0; $text_length = strlen($this->text); $this->step = 0; // @todo temp $text_current_color = NULL; while (TRUE) { $result_line = ''; // Line being created $lc = 0; // Line length count (without ANSI control codes) // The buffer represents how many spaces need to pad between the left_width and whatever is drawn on the left if (! $this->step) { if (($this->left_height > $this->header_height) || ($result_height < $this->header_height-1)) $buffer = $this->left_width+($this->left_width ? self::LOGO_OFFSET_WIDTH*2 : 0); else $buffer = 0; } else { $buffer = 0; /* // @todo $buffer = $this->step ? $this->logo->width->skip(intdiv($result_height,$this->step)*$this->step)->take($this->step)->max() : 1; */ } if ($this->left_width) { // Add our logo if ($result_height < $this->logo->height) { $line = ANSI::bin_to_ansi([$this->logo->line($result_height)],FALSE); $lc = $this->logo->line_width($this->logo->line_raw($result_height),FALSE); $result_line = str_repeat(' ',self::LOGO_OFFSET_WIDTH) .$line .str_repeat(' ',$buffer-$lc-($this->left_width ? self::LOGO_OFFSET_WIDTH : 0)); } elseif (self::LOGO_OFFSET_WIDTH && $this->logo->height && ($result_height === $this->logo->height) && $this->left_box->height) { $result_line = str_repeat(' ',$buffer); } elseif ($result_height < $this->left_height-($this->logo->height ? 0 : self::LOGO_OFFSET_WIDTH)) { $line = $this->left_box->render_line($result_height-($this->logo->height ? self::LOGO_OFFSET_WIDTH : 0)-$this->logo->height); $lc = $this->left_box->width; $result_line = str_repeat(' ',($this->left_box->width ? self::LOGO_OFFSET_WIDTH : 0)) .$line .str_repeat(' ',($this->left_box->width ? $buffer-$lc-($this->left_width ? self::LOGO_OFFSET_WIDTH : 0) : (($current_pos < $text_length) ? $buffer : 0))); } else { if ($current_pos < $text_length) $result_line = str_repeat(' ',$buffer); } } // Add our header if ($result_height <= $this->header->height-1) { $result_line .= str_repeat(' ',$this->header_right ? self::MSG_WIDTH-($this->left_width ? self::LOGO_OFFSET_WIDTH*2 : 0)-$this->left_width-$this->header->width : 0) .$this->header->render_line($result_height); } // Add our header footer if ($x=ANSI::line_width($this->header_foot,FALSE)) { if ($result_height === $this->header->height) { $result_line .= str_repeat(' ',($this->header_right ? self::MSG_WIDTH-($this->left_width ? self::LOGO_OFFSET_WIDTH*2 : 0)-$this->left_width-$x : 0)) .ANSI::text_to_ansi($this->header_foot); } } // Add our header underline if ($this->header_underline) { if ($result_height === $this->header->height+($this->header_foot ? 1 : 0)) { $result_line .= str_repeat(chr($this->header_underline),self::MSG_WIDTH-$buffer); } } $subtext = ''; $subtext_ansi = ''; $subtext_length = 0; if ($current_pos < $text_length) { if ($result_height >= $this->header_height) { // Look for a return $return_pos = strpos($this->text,"\r",$current_pos); // We have a return if ($return_pos !== FALSE) { // If the remaining text is within our width, we'll use it all. if ($return_pos-$current_pos < self::MSG_WIDTH-$buffer) { $subtext = substr($this->text,$current_pos,$return_pos-$current_pos); // Look for the space. } else { $space_pos = strrpos(substr($this->text,$current_pos,($return_pos-$current_pos > static::MSG_WIDTH-$this->left_width ? static::MSG_WIDTH-$this->left_width : $return_pos-$current_pos)),' '); $subtext = substr($this->text,$current_pos,$space_pos); } // If the reset of the string will fit on the current line } elseif ($text_length-$current_pos < static::MSG_WIDTH-$buffer) { $subtext = substr($this->text,$current_pos); // Get the next lines worth of chars } else { $subtext = $this->text_substr(substr($this->text,$current_pos),static::MSG_WIDTH-$buffer); // Include the text up to the last space if (substr($this->text,$current_pos+strlen($subtext),1) !== ' ') $subtext = substr($subtext,0,strrpos($subtext,' ')); } $current_pos += strlen($subtext)+1; } if ($subtext) { $subtext_length = ANSI::line_width($subtext,FALSE); $subtext_ansi = ANSI::text_to_ansi(($text_current_color ? "\x1b".$text_current_color : '').$subtext); // Get our last color used, for the next line. $m = []; preg_match('/^.*(\x1b(.))+(.*?)$/s',$subtext,$m); if (Arr::get($m,2)) $text_current_color = $m[2]; } } if ($result_line || $subtext) { $result .= $result_line. ($subtext_ansi ? str_repeat(' ', ($this->text_right && ($result_height > $this->header_height-1) ? static::MSG_WIDTH-$subtext_length-$buffer : 0)).$subtext_ansi : ''); $result .= $this->crlf ? "\n\r" : "\r"; } $result_height++; if (($result_height > $this->logo->height) && ($result_height > $this->left_height) && ($current_pos >= $text_length)) { break; } } return $result; } private function text_substr(string $text,int $goal): string { $chars = $goal; while (($x=ANSI::line_width($subtext=substr($text,0,$chars),FALSE)) < $goal) { $chars += ($chars-$x); } // If the last char is an escape, we need to more chars until the last char is no longer an escape. while (preg_match('/\x1b$/',$subtext) && (strlen($subtext) < strlen($text))) { $subtext .= substr($text,strlen($subtext),2); } return $subtext; } }