1) { $out .= "\x1B[".(($position === false)? ($line.';'.$clear_to.'H') : ($clear_to.'G'))."\x1B[1K"; $position = $clear_to; } foreach($chars as $pos => $char) { if($clear_from != 0 && $pos >= $clear_from) { if($position != $clear_from) {$out .= "\x1B[".(($position === false)? ($line.';'.$clear_from.'H') : (($clear_from - $position > 1? ($clear_from - $position) : '').'C'));} $out .= "\x1B[K"; if($clear_from != $pos) {$out .= "\x1B[".($pos - $clear_from > 1? ($pos - $clear_from) : '').'C';} $clear_from = 0; } elseif($position === false) {$out .= "\x1B[".($pos > 1? ($line.';'.$pos) : ($line > 1? $line : '')).'H';} else { $diff = $pos - $position; if($diff < 0) {$out .= "\x1B[".(-$diff > 1? -$diff : '').'D';} elseif($diff > 0) {$out .= "\x1B[".($diff > 1? $diff : '').'C';} } if($clear_to === true) { $out .= "\x1B[2K"; $clear_to = 0; } if(($last == false && $dec[$pos]) || ($last != false && $dec[$pos] != $dec[$last])) {$out .= "\x1B(".($dec[$pos]? '0' : 'B');} $set_on = ''; $set_off = ''; $set_stay = ';0'; foreach($color[$pos] as $type => $value) { $old = $last == false? 0 : $color[$last][$type]; $value_add = 0; $value_off = 0; switch($type) { case 'b'://bold $value_off = 22; break; case 'i'://italic $value_add = 2; $value_off = 23; break; case 'u'://underline $value_add = 3; $value_off = 24; break; case 'bl'://blink $value_add = 4; $value_off = 25; break; case 's'://inverse $value_add = 6; $value_off = 27; break; case 'c'://conceal $value_add = 7; $value_off = 28; break; case 'fg'://foreground $value_off = 39; break; case 'bg'://background $value_off = 49; break; } if($value == 0 && $value != $old) {$set_off .= ';'.$value_off;} elseif($value != 0 && $value != $old) {$set_on .= ';'.($value + $value_add);} elseif($value != 0) {$set_stay .= ';'.($value + $value_add);} } if($set_on != '' && $set_off == '') {$out .= "\x1B[".substr($set_on, 1).'m';} elseif($set_on != '') {$out .= "\x1B[".(strlen($set_off) < strlen($set_stay)? substr($set_off, 1) : substr($set_stay, 1)).$set_on.'m';} elseif($set_off != '') {$out .= "\x1B[".(strlen($set_off) < strlen($set_stay)? substr($set_off, 1) : substr($set_stay, 1)).'m';} $out .= $char; $last = $pos; $position = $pos + 1; } if(isset($dec[$last]) && $dec[$last]) {$out .= "\x1B(B";} if(isset($color[$last])) {foreach($color[$last] as $value) {if($value != 0) {$out .= "\x1B[0m"; break;}}} if($clear_from > 0) {//we didn't reach this position yet if($position < $clear_from) {$out .= "\x1B[".($clear_from - $position > 1? ($clear_from - $position) : '').'C';} $out .= "\x1B[K"; } return $out; } //try to make the given frame smaller //passing false or an empty string as argument resets the cursor position to 1 / 1 (usefull if a new file starts) function shrink_frame($frame, $ignore_error, &$error = '') { static $x = 1; static $y = 1; static $x_save = false; static $y_save = false; if(is_bool($frame) && $frame == false) {$x = 1; $y = 1; $x_save = false; $y_save = false; return false;} $length = strlen($frame); //maybe static? $dec = false; $color = array('b' => 0, 'i' => 0, 'u' => 0, 'bl' => 0, 's' => 0, 'c' => 0, 'fg' => 0, 'bg' => 0); /* $color['b']: 0 normal, 1 bold, 2 faint $color['i']: 0 normal, 1 italic $color['u']: 0 normal, 1 underlined, 2 double $color['bl']: 0 not blinking, 1 slow, fast $color['s']: 0 normal, 1 fg and bg swapped $color['c']: 0 normal, 1 concealed $color['fg']: 0 normal, 30-37,90-97 specific foreground color $color['bg']: 0 normal, 40-47,100-107 specific background color */ $read = 0; $out_frame = ''; $output = array('char' => array(), 'dec' => array(), 'color' => array()); $clear = array('after' => 0, 'before' => 0); /* $clear == true -- clear whole screen $clear['before'] = y -- clear all lines before this one $clear['after'] = y -- clear this line and all lines after it $clear[y] = true -- clear this line $clear[y]['before'] = x -- clear from start of line to here (excluding this char) $clear[y]['after'] = x -- clear from here to end of line (including this char) */ while($read < $length) { if($frame[$read] == "\x0D") {$x = 1;}//^M -- carriage return elseif($frame[$read] == "\x08") {$x = max(1, $x - 1);}//^H -- backspace elseif($frame[$read] == "\n") {$y = 1; $x = 1;}//newline elseif($frame[$read] == "\x1B")//ESC { if(isset($frame[$read + 2]) && $frame[$read + 1] == '(') {//dec-graphics if($frame[$read + 2] == '0') {$dec = true;} elseif($frame[$read + 2] == 'B') {$dec = false;} elseif(!$ignore_error) { $error = 'At Byte '.$read.': Found unknown escape code: "<ESC>('.$frame[$read + 2].'". Aborting.'; return false; } $read += 2; } elseif(isset($frame[$read + 2]) && $frame[$read + 1] == ')') {//don't know what this code does... if($frame[$read + 2] == '0') {$out_frame .= "\x1B)0";} elseif(!$ignore_error) { $error = 'At Byte '.$read.': Found unknown escape code: "<ESC>)'.$frame[$read + 2].'". Aborting.'; return false; } $read += 2; } elseif(isset($frame[$read + 2]) && $frame[$read + 1] == '[') {/* summarizing http://en.wikipedia.org/wiki/ANSI_escape_code#Codes we get this regex for a valid code: \e\[[<=>?]?[0-9]*(;[0-9]*)*;?[\x20-\x2f]*[\x40-\x7e] read everything from ESC to the next char in range 0x40 - 0x7e and see if we can handle that code, if we can't just pass it to the output */ $last = $read + 2; while(ord($frame[$last]) < 0x40 || ord($frame[$last]) > 0x7e) { $last++; if(!isset($frame[$last]) && !$ignore_error) { $error = 'At Byte '.$read.': Found unfinised escape code: "<ESC>['.substr($frame, $read + 2).'". Aborting.'; return false; } elseif(!isset($frame[$last])) {break;} } $code = substr($frame, $read + 2, $last - $read - 1); if((0x3c <= ord($code[0]) && ord($code[0]) <= 0x3f) || (0x70 <= ord($code[strlen($code) - 1]) && ord($code[strlen($code) - 1]) <= 0x7e)) {//this are private codes, put them in front to get them out of the way $out_frame .= "\x1B[".$code; } /* taking the above regex, leaving out the \e\[ (which isn't part of $code) and limiting it to non-private codes we get: [0-9]*(;[0-9]*)*;?[\x20-\x2f]*[\x40-\x6f] for now complain about the [\x20-\x2f]* part which is allowed but not standardized, so we get [0-9]*(;[0-9]*)*;?[\x40-\x6f] as a valid code */ elseif(preg_match('/^[0-9]*(;[0-9]*)*;?[\x40-\x6f]$/', $code) == 1) {//this seems valid, the last char determines what to do; if given too many parameter, silently ignore them //since almost every code takes parameters, parse them here $param = array(); $number = 0; $value = -1; for($i = 0; $i < strlen($code) - 1; $i++)//we don't want to read the command { if($code[$i] == ';') { if($value != -1) {$param[$number] = $value;} $number++; $value = -1; } else {//not a ';' so it must be a digit if($value == -1) {$value = (int) $code[$i];} else {$value = $value * 10 + (int) $code[$i];} } } if($value != -1) {$param[$number] = $value;} switch($code[strlen($code) - 1]) {// n, o, p, ... denote the first, second, third, ... parameter; n=1 means n has a default value of 1 case 'A'://cursor n=1 up $y -= max(1, isset($param[0])? $param[0] : 1); break; case 'B'://cursor n=1 down $y += isset($param[0])? $param[0] : 1; break; case 'C'://cursor n=1 right $x += isset($param[0])? $param[0] : 1; break; case 'D'://cursor n=1 left $x -= max(1, isset($param[0])? $param[0] : 1); break; case 'E'://cursor n=1 up, to beginning of line $y -= max(1, isset($param[0])? $param[0] : 1); $x = 1; break; case 'F'://cursor n=1 down, to beginning of line $y += isset($param[0])? $param[0] : 1; $x = 1; break; case 'G'://cursor to column n (no default value) - ingore this command if no parameter is given $x = max(1, isset($param[0])? $param[0] : $x); break; case 'H'://cursor to row n=1, column o=1 case 'f': $y = max(1, isset($param[0])? $param[0] : 1); $x = max(1, isset($param[1])? $param[1] : 1); break; case 'J'://n=0, clear from cursor to {n=0: end, n=1: beginning} of the screen, n=2 means whole screen if(!isset($param[0])) {$param[0] = 0;} if($param[0] == 2) { foreach($output as $type => $coords) {$output[$type] = array();} $clear = true; } else { foreach($output as $type => $coords) { foreach($output[$type] as $cy => $row) { if(($param[0] == 0 && $cy > $y) || ($param[0] == 1 && $cy < $y)) {unset($output[$type][$cy]);} elseif($cy == $y) { foreach($output[$type][$cy] as $cx => $char) { if(($param[0] == 0 && $cx >= $x) || ($param[0] == 1 && $cx < $x)) {unset($output[$type][$cy][$cx]);} } if($output[$type][$cy] == array()) {unset($output[$type][$cy]);} } } } if($param[0] == 0 && is_array($clear)) { $clear['after'] = $clear['after'] == 0? ($y + 1) : min($clear['after'], $y + 1); if(!isset($clear[$y])) {$clear[$y] = array('after' => $x, 'before' => 0);} elseif(is_array($clear[$y])) {$clear[$y]['after'] = min($clear[$y]['after'], $x);} } elseif($param[0] == 1 && is_array($clear)) { if($y > 1) {$clear['before'] = max($clear['before'], $y);} if($x > 1 && !isset($clear[$y])) {$clear[$y] = array('after' => 0, 'before' => $x);} elseif($x > 1 && is_array($clear[$y])) {$clear[$y]['before'] = max($clear[$y]['before'], $x);} } } break; case 'K'://n=0, clear from cursor to {n=0: end, n=1: beginning} of the line, n=2 means whole line if(!isset($param[0])) {$param[0] = 0;} if($param[0] == 2) { foreach($output as $type => $coords) {unset($output[$type][$y]);} if(is_array($clear)) {$clear[$y] = true;} } else { foreach($output as $type => $coords) { if(isset($output[$type][$y])) { foreach($output[$type][$y] as $cx => $char) { if(($param[0] == 0 && $cx >= $x) || ($param[0] == 1 && $cx < $x)) {unset($output[$type][$y][$cx]);} } if($output[$type][$y] == array()) {unset($output[$type][$y]);} } } if($param[0] == 0 && is_array($clear)) { if($x == 1) {$clear[$y] = true;} elseif(!isset($clear[$y])) {$clear[$y] = array('after' => $x, 'before' => 0);} elseif(is_array($clear[$y])) {$clear[$y]['after'] = min($clear[$y]['after'], $x);} } elseif($param[0] == 1 && $x > 1 && is_array($clear)) { if(!isset($clear[$y])) {$clear[$y] = array('after' => 0, 'before' => $x);} elseif(is_array($clear[$y])) {$clear[$y]['before'] = max($clear[$y]['before'], $x);} } } break; //the commands S and T are not supported yet /*case 'S'://move everything up n=1 lines //TODO: generate output for $y <= n, append \e[nS, unset $output[][$y <= n], subtract n from every index w/o overwriting break; case 'T'://move everything down n=1 lines //TODO: put \e[nT to output, add n to every index w/o overwriting break;*/ case 'm'://change color, bold, ... /* possible params, from wiki, sorted in categories [0] Reset / Normal all attributes off 1 Intensity: Bold [22] Intensity: Normal 2 Intensity: Faint 3 Italic: on -- no off? doesn't work on xterm [23] Italic: off -- guess!! x + 20 seems to revert x [24] Underline: None 4 Underline: Single 21 Underline: Double -- doesn't work on xterm, turns bold off instead, i treat it as bold off [25] Blink: off 5 Blink: Slow -- doesn't work on xterm 6 Blink: Rapid -- dito [27] Image: Positive [7] Image: Negative -- swap foreground and background 8 Conceal [28] Reveal 30–39 Set foreground color 40–49 Set background color 90–99 Set foreground color, high intensity -- uses bold color, doesn't render bold 100–109 set background color, high intensity -- uses bold color for background $color['b']: 0 normal, 1 bold, 2 faint $color['i']: 0 normal, 1 italic $color['u']: 0 normal, 1 underlined, 2 double $color['bl']: 0 not blinking, 1 slow, fast $color['s']: 0 normal, 1 fg and bg swapped $color['c']: 0 normal, 1 concealed $color['fg']: 0 normal, 30-37,90-97 specific foreground color $color['bg']: 0 normal, 40-47,100-107 specific background color */ foreach($param as $option) { switch($option) { case 0://reset all $color = array('b' => 0, 'i' => 0, 'u' => 0, 'bl' => 0, 's' => 0, 'c' => 0, 'fg' => 0, 'bg' => 0); break; case 1://bold case 2://faint $color['b'] = $option; break; case 21: case 22: $color['b'] = 0; break; case 3://italic $color['i'] = 1; break; case 23: $color['i'] = 0; break; case 4://underline $color['u'] = 1; break; case 24: $color['u'] = 0; break; case 5://blink slow case 6://blink fast $color['bl'] = $option - 4; break; case 25: $color['bl'] = 0; break; case 7://swap fg and bg $color['s'] = 1; break; case 27: $color['s'] = 0; break; case 8://conceal $color['c'] = 1; break; case 28: $color['c'] = 0; break; case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37://fg color case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97://fg color, bright $color['fg'] = $option; break; case 39: $color['fg'] = 0; break; case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47://bg color case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107://bg color, bright $color['bg'] = $option; break; case 39: $color['bg'] = 0; break; default: break; } } break; case 's'://save cursor position $x_save = $x; $y_save = $y; break; case 'u'://restore cursor position if(($x_save === false || $y_save === false) && !$ignore_error) { $error = 'At Byte '.$read.': Found restore cursor code, but never seen a store cursor code. Aborting.'; return false; } elseif($x_save === false || $y_save === false) { $x_save = $x; $y_save = $y; } $x = $x_save; $y = $y_save; break; default: if(!$ingore_error) { $error = 'At Byte '.$read.': Found unknown escape code: "<ESC>['.$code.'". Aborting.'; return false; } break; } } elseif(!$ignore_error) {//invalid code, complain and abort $error = 'At Byte '.$read.': Found unknown escape code: "<ESC>['.$code.'". Aborting.'; return false; } $read = $last; } elseif(!$ignore_error) { $error = 'At Byte '.$read.': Found unknown escape code. Aborting.'; return false; } } else { $output['char'][$y][$x] = $frame[$read]; $output['dec'][$y][$x] = $dec; $output['color'][$y][$x] = $color; $x++; } $read++; } /* $clear == true -- clear whole screen $clear['before'] = y -- clear all lines before this one $clear['after'] = y -- clear this line and all lines after it $clear[y] = true -- clear this line $clear[y]['before'] = x -- clear from start of line to here (excluding this char) $clear[y]['after'] = x -- clear from here to end of line (including this char) */ //try to expand these regions for($row = $clear['before']; isset($clear[$row]) && ($clear[$row] === true || (is_array($clear[$row]) && $clear[$row]['after'] <= $clear[$row]['before'])); $row++) { unset($clear[$row]); $clear['before']++; } for($row = $clear['after'] - 1; isset($clear[$row]) && ($clear[$row] === true || (is_array($clear[$row]) && ($clear[$row]['after'] <= $clear[$row]['before'] || $clear[$row]['after'] == 1))); $row--) { unset($clear[$row]); $clear['after']--; } if($clear === true || $clear['after'] == 1 || ($clear['after'] != 0 && $clear['before'] != 0 && $clear['after'] <= $clear['before'])) {$clear = array('after' => 0, 'before' => 0); $out_frame .= "\x1B[2J";} //remove unnecessary elements foreach($clear as $row => $col) { if($row == 'before' || $row == 'after') {continue;} elseif($row < $clear['before'] || ($clear['after'] != 0 && $clear['after'] <= $row)) {unset($clear[$row]);} elseif(is_array($col) && $col['after'] <= $col['before']) {$clear[$row] = true;} } //$clear[y] now is either true or 'before' and 'after' are not overlapping ksort($output['char']);//we don't want to jump all over the screen //... and now we can finally start writing the frame... if($clear['before'] > 1)//0 if nothing to clear, 1 impossible, x clear above x { $out_y = $clear['before']; $out_x = isset($clear[$out_y]['before']) && $clear[$out_y]['before'] > 1? $clear[$out_y]['before'] : 1; $out_frame .= "\x1B[".$out_y.($out_x > 1? (';'.$out_x) : '')."H\x1B[1J"; if(isset($output['char'][$out_y])) {$out_frame .= write_frame_line($out_y, $out_x, $output['char'][$out_y], $output['dec'][$out_y], $output['color'][$out_y], 0, isset($clear[$out_y])? $clear[$out_y]['after'] : 0);} } foreach($output['char'] as $out_y => $out_line) { if($clear['after'] != 0 && $clear['after'] - 1 == $out_y && isset($clear[$out_y])) { $out_x = $clear[$out_y]['after']; $out_frame .= "\x1B[".$out_y.';'.$out_x."H\x1B[J"; $clear['after'] = 0; $out_frame .= write_frame_line($out_y, $out_x, $output['char'][$out_y], $output['dec'][$out_y], $output['color'][$out_y], $clear[$out_y]['before'], 0); unset($clear[$out_y]); continue; } elseif($clear['after'] != 0 && $clear['after'] - 1 < $out_y) { $out_frame .= "\x1B[".$clear['after']."H\x1B[J"; $clear['after'] = 0; } $out_frame .= write_frame_line($out_y, false, $output['char'][$out_y], $output['dec'][$out_y], $output['color'][$out_y], isset($clear[$out_y])? ($clear[$out_y] === true? true : $clear[$out_y]['before']) : 0, isset($clear[$out_y])? ($clear[$out_y] === true? 0 : $clear[$out_y]['after']) : 0); unset($clear[$out_y]); } //clears in lines we didn't write foreach($clear as $row => $col) { if($row == 'before' || $row == 'after') {continue;} if($col === true) {$out_frame .= "\x1B[".$row."H\x1B[K";} else { if($col['before'] != 0) {$out_frame .= "\x1B[".$row.';'.$col['before']."H\x1B[1K";} if($col['after'] != 0) {$out_frame .= "\x1B[".($col['before'] == 0?($row.';'.$col['after']."H") : (($col['after'] - $col['before']).'C'))."\x1B[K";} } } $out_frame .= "\x1B[".($x > 1? ($y.';'.$x) : ($y > 1? $y : '')).'H'; /* echo '
'; var_dump($frame); echo '

'; var_dump($out_frame); /*die();*/ return $out_frame; } ?>