getLines(); $cols = $buffer->getColumns(); $cw = 8; $ch = 17; $this->cellWidth = $cw; $this->cellHeight = $ch; $this->gd = imagecreatetruecolor($cols * $cw, $lines * $ch); for ($line = 0; $line < $lines; $line++) { for ($column = 0; $column < $cols; $column++) { $this->renderChar($line*$ch, $column*$cw, $cw, $ch, $buffer->bufferGetRaw($line, $column)); } } [$cursorLine,$cursorColumn] = $buffer->getCursorPosition(); $cursorX = $cursorColumn * $cw; $cursorY = $cursorLine * $ch; imagerectangle($this->gd, $cursorX, $cursorY, $cursorX + $cw, $cursorY + $ch - 1, 0xFFFFFF); $this->drawAnnotations(); $this->drawKeyOverlay(); } private function renderChar(int $y, int $x, int $w, int $h, array $raw) { [ $char, $attrs ] = $raw; $bold = false; $underline = false; $color = 0xFFFFFF; $bgcolor = 0x000000; //echo "\rattr: " . json_encode($attrs); usleep(100000); foreach ($attrs as $attr) { $attr = intval($attr); if ($attr === 0) { $color = 0xFFFFFF; $bgcolor = 0x000000; $bold = false; $underline = false; } elseif ($attr == 1) { $bold = true; } elseif ($attr == 4) { $underline = true; } elseif ($attr == 22) { $bold = false; } elseif ($attr == 24) { $underline = false; } elseif ($attr >= 30 && $attr <= 39) { $color = $this->palette[$attr - 30]; } elseif ($attr >= 40 && $attr <= 49) { $bgcolor = $this->palette[$attr - 40]; } } //$attr = join(";", $attrs)??"0"; //printf("\e[%d;%dH\e[%sm%s", $line + 1, $column + 1, $attr, $char); imagefilledrectangle($this->gd, $x, $y, $x + $w, $y + $h, $bgcolor); imagettftext($this->gd, 10, 0, $x, $y + 12, $color, $bold?$this->boldFont:$this->font, $char); if ($bold) { //imagettftext($this->gd, 10, 0, $x + 1, $y + 10, $color, $this->font, $char); } if ($underline) { imageline($this->gd, $x, $y + $h - 2, $x + $w, $y + $h - 2, $color); } } public function writePng(string $filename) { imagepng($this->gd, $filename); } private function drawAnnotations() { $fw = imagefontwidth($this->annotationFontSize); $fh = imagefontheight($this->annotationFontSize); $bg = 0xFFEEDD; $bgs = 0x808080; $bgf = 0xFF8800; foreach ($this->annotations as $annotation) { [$line, $column, $text, $width] = $annotation; $x = $this->cellWidth * $column; $y = $this->cellHeight * ($line + 1) + 5; $lines = explode("\\n", $text); $lc = count($lines); $lw = max(array_map("strlen", $lines)); $w = 4 + ($fw * $lw); $h = 2 + ($fh * $lc); $aw = floor($this->cellWidth / 2); for ($n = 0; $n < $aw; $n++) { imageline($this->gd, $x + $n, $y - $n, $x + (2 * $aw) - $n, $y - $n, $bg); } imagerectangle($this->gd, $x+1, $y+1, $x + $w + 1, $y + $h + 1, $bgs); imagefilledrectangle($this->gd, $x, $y, $x + $w, $y + $h, $bg); $lp = 0; foreach ($lines as $line) { imagestring($this->gd, $this->annotationFontSize, $x + 2, $y + 1 + ($lp++ * $fh), $line, 0x0); } if ($width > 0) { imageline($this->gd, $x - 1, $y - 4, $x - 1, $y - 7, $bgf); imageline($this->gd, $x + ($width * $this->cellWidth) + 1, $y - 4, $x + ($width * $this->cellWidth) + 1, $y - 7, $bgf); imageline($this->gd, $x - 1, $y - 4, $x + ($width * $this->cellWidth) + 1, $y - 4, $bgf); imageline($this->gd, $x - 1, $y - 5, $x + ($width * $this->cellWidth) + 1, $y - 5, $bgf); } } } public function addAnnotation(string $id, int $line, int $column, string $text) { $this->annotations[$id] = [ $line, $column, $text, 0 ]; } public function updateAnnotation(string $id, array $props) { if (!array_key_exists($id, $this->annotations)) { return; } foreach ($props as $k=>$v) { switch ($k) { case 'length': $this->annotations[$id][3] = $v; break; } } } public function removeAnnotation(string $id) { unset ($this->annotations[$id]); } public function clearAnnotations() { $this->annotations = []; } private function drawKeyOverlay() { $x = 0; $d = 5; $y = imagesy($this->gd) - 32; $h = 24; $w = 32; $ww = 48; foreach ($this->keyOverlay as $key) { $x += $d; $char = $this->translateKeyToGlyph($key); if (mb_strlen($char) > 2) { $this->drawRoundedRectangle($x, $y, $ww, $h, 3, 0xCCCCCC); imagettftext($this->gd, 8, 0, $x + 5, $y + 16, 0x000000, $this->boldFont, $char); $x += $ww; } else { $this->drawRoundedRectangle($x, $y, $w, $h, 3, 0xCCCCCC); imagettftext($this->gd, 10, 0, $x + ((mb_strlen($char)==1)?12:8), $y + 16, 0x000000, $this->boldFont, $char); $x += $w; } } } public function setKeyOverlay(array $keys) { $this->keyOverlay = $keys; } private function drawRoundedRectangle(int $left, int $top, int $width, int $height, int $radius, $color) { $right = $left + $width; $bottom = $top + $height; imagefilledrectangle($this->gd, $left + $radius, $top, $right - $radius, $bottom, $color); imagefilledrectangle($this->gd, $left, $top + $radius, $right, $bottom - $radius, $color); imagefilledellipse($this->gd, $left + $radius, $top + $radius, $radius * 2, $radius * 2, $color); imagefilledellipse($this->gd, $left + $radius, $bottom - $radius, $radius * 2, $radius * 2, $color); imagefilledellipse($this->gd, $right - $radius, $top + $radius, $radius * 2, $radius * 2, $color); imagefilledellipse($this->gd, $right - $radius, $bottom - $radius, $radius * 2, $radius * 2, $color); } private function translateKeyToGlyph(string $key) { switch ($key) { case "up": return "↑"; case "down": return "↓"; case "left": return "←"; case "right": return "→"; case "backspace": case "bs": return mb_chr(0x232b); default: return $key; } } }