1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-25 15:00:58 +01:00
phorge-phorge/externals/pear-figlet/Text/Figlet.php
epriestley 6bd8ee861c Use PEAR Text_Figlet to render figlet fonts
Summary:
Ref T7785. Makes Figlet available without installing the `figlet` package.

The PEAR Text_Figlet code is really sketchy and includes this API, which is quite marvelous:

```
    function loadFont($filename, $loadgerman = true)
```

At some point, this should probably be rewritten into a modern style, but it's not trivial since the figlet file format and rendering engine are somewhat complicated. I made some adjustments:

  - Broke the dependency on the PEAR core.
  - Prevented it from doing any wrong HTML escaping.
  - Looked through it for any glaring security or correctness problems.

This code isn't very pretty or modern, but as far as I can tell it's safe and does render Figlet fonts in a reasonable way.

Test Plan: {F803268}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9408, T7785

Differential Revision: https://secure.phabricator.com/D14102
2015-09-13 12:31:07 -07:00

500 lines
14 KiB
PHP

<?php
/* vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4: */
/**
* ASCII art text creation
*
* Project home page (Russian): http://bolknote.ru/files/figlet/
*
* PHP Version 4
*
* @category Text
* @package Text_Figlet
* @author Evgeny Stepanischev <imbolk@gmail.com>
* @author Christian Weiske <cweiske@php.net>
* @license http://www.php.net/license PHP License
* @version CVS: $Id$
* @link http://pear.php.net/package/Text_Figlet
*/
/**
* ASCII art text creation
*
* Project home page (Russian): http://bolknote.ru/files/figlet/
*
* PHP Version 4
*
* @category Text
* @package Text_Figlet
* @author Evgeny Stepanischev <imbolk@gmail.com>
* @author Christian Weiske <cweiske@php.net>
* @license http://www.php.net/license PHP License
* @link http://pear.php.net/package/Text_Figlet
*/
class Text_Figlet
{
/**
* Height of a letter
*
* @var integer
*
* @access protected
*/
var $height;
/**
* Letter baseline
*
* @var integer
*
* @access protected
*/
var $oldlayout;
/**
* Flag - RTL (right to left) or LTR (left to right) text direction
*
* @var integer
*
* @access protected
*/
var $rtol;
/**
* Information about special 'hardblank' character
*
* @var integer
*
* @access protected
*/
var $hardblank;
/**
* Is used for keeping font
*
* @var array
*
* @access protected
*/
var $font;
/**
* Flag is true if smushing occured in letters printing cycle
*
* @var integer
*
* @access protected
*/
var $smush_flag;
/**
* Comment lines buffer
*
* @var string
*
* @access public
*/
var $font_comment;
/**
* Load user font. Must be invoked first.
* Automatically tries the Text_Figlet font data directory
* as long as no path separator is in the filename.
*
* @param string $filename font file name
* @param bool $loadgerman (optional) load German character set or not
*
* @access public
* @return mixed PEAR_Error or true for success
*/
function loadFont($filename, $loadgerman = true)
{
$this->font = array();
if (!file_exists($filename)) {
return self::raiseError('Figlet font file "'
. $filename
. '" cannot be found', 1);
}
$this->font_comment = '';
// If Gzip compressed font
if (substr($filename, -3, 3) == '.gz') {
$filename = 'compress.zlib://' . $filename;
$compressed = true;
if (!function_exists('gzcompress')) {
return self::raiseError('Cannot load gzip compressed fonts since'
. ' gzcompress() is not available.',
3);
}
} else {
$compressed = false;
}
if (!($fp = fopen($filename, 'rb'))) {
return self::raiseError('Cannot open figlet font file ' . $filename, 2);
}
if (!$compressed) {
/* ZIPed font */
if (fread($fp, 2) == 'PK') {
if (!function_exists('zip_open')) {
return self::raiseError('Cannot load ZIP compressed fonts since'
. ' ZIP PHP extension is not available.',
5);
}
fclose($fp);
if (!($fp = zip_open($filename))) {
return self::raiseError('Cannot open figlet font file ' . $filename, 2);
}
$name = zip_entry_name(zip_read($fp));
zip_close($fp);
if (!($fp = fopen('zip://' . realpath($filename) . '#' . $name, 'rb'))) {
return self::raiseError('Cannot open figlet font file ' . $filename, 2);
}
$compressed = true;
} else {
flock($fp, LOCK_SH);
rewind($fp);
}
}
// flf2a$ 6 5 20 15 3 0 143 229
// | | | | | | | | | |
// / / | | | | | | | \
// Signature / / | | | | | \ Codetag_Count
// Hardblank / / | | | \ Full_Layout
// Height / | | \ Print_Direction
// Baseline / \ Comment_Lines
// Max_Length Old_Layout
$header = explode(' ', fgets($fp, 2048));
if (substr($header[0], 0, 5) <> 'flf2a') {
return self::raiseError('Unknown FIGlet font format.', 4);
}
@list ($this->hardblank, $this->height,,,
$this->oldlayout, $cmt_count, $this->rtol) = $header;
$this->hardblank = substr($this->hardblank, -1, 1);
for ($i = 0; $i < $cmt_count; $i++) {
$this->font_comment .= fgets($fp, 2048);
}
// ASCII charcters
for ($i = 32; $i < 127; $i++) {
$this->font[$i] = $this->_char($fp);
}
foreach (array(196, 214, 220, 228, 246, 252, 223) as $i) {
if ($loadgerman) {
$letter = $this->_char($fp);
// Invalid character but main font is loaded and I can use it
if ($letter === false) {
fclose($fp);
return true;
}
// Load if it is not blank only
if (trim(implode('', $letter)) <> '') {
$this->font[$i] = $letter;
}
} else {
$this->_skip($fp);
}
}
// Extented characters
for ($n = 0; !feof($fp); $n++) {
list ($i) = explode(' ', rtrim(fgets($fp, 1024)), 2);
if ($i == '') {
continue;
}
// If comment
if (preg_match('/^\-0x/i', $i)) {
$this->_skip($fp);
} else {
// If Unicode
if (preg_match('/^0x/i', $i)) {
$i = hexdec(substr($i, 2));
} else {
// If octal
if ($i{0} === '0' && $i !== '0' || substr($i, 0, 2) == '-0') {
$i = octdec($i);
}
}
$letter = $this->_char($fp);
// Invalid character but main font is loaded and I can use it
if ($letter === false) {
fclose($fp);
return true;
}
$this->font[$i] = $letter;
}
}
fclose($fp);
return true;
}
/**
* Print string using font loaded by LoadFont method
*
* @param string $str string for printing
* @param bool $inhtml (optional) output mode
* - HTML (true) or plain text (false)
*
* @access public
* @return string contains
*/
function lineEcho($str, $inhtml = false)
{
$out = array();
for ($i = 0; $i<strlen($str); $i++) {
// Pseudo Unicode support
if (substr($str, $i, 2) == '%u') {
$lt = hexdec(substr($str, $i+2, 4));
$i += 5;
} else {
$lt = ord($str{$i});
}
$hb = preg_quote($this->hardblank, '/');
$sp = "$hb\\x00\\s";
// If chosen character not found try to use default
// If default character is not defined skip it
if (!isset($this->font[$lt])) {
if (isset($this->font[0])) {
$lt = 0;
} else {
continue;
}
}
for ($j = 0; $j < $this->height; $j++) {
$line = $this->font[$lt][$j];
// Replace hardblanks
if (isset($out[$j])) {
if ($this->rtol) {
$out[$j] = $line . $out[$j];
} else {
$out[$j] .= $line;
}
} else {
$out[$j] = $line;
}
}
if ($this->oldlayout > -1 && $i) {
// Calculate minimal distance between two last letters
$mindiff = -1;
for ($j = 0; $j < $this->height; $j++) {
if (preg_match("/\S(\s*\\x00\s*)\S/", $out[$j], $r)) {
if ($mindiff == -1) {
$mindiff = strlen($r[1]);
} else {
$mindiff = min($mindiff, strlen($r[1]));
}
}
}
// Remove spaces between two last letter
// dec mindiff for exclude \x00 symbol
if (--$mindiff > 0) {
for ($j = 0; $j < $this->height; $j++) {
if (preg_match("/\\x00(\s{0,{$mindiff}})/", $out[$j], $r)) {
$l = strlen($r[1]);
$b = $mindiff - $l;
$out[$j] = preg_replace("/\s{0,$b}\\x00\s{{$l}}/",
"\0",
$out[$j],
1);
}
}
}
// Smushing
$this->smush_flag = 0;
for ($j = 0; $j < $this->height; $j++) {
$out[$j] = preg_replace_callback("#([^$sp])\\x00([^$sp])#",
array(&$this, '_rep'),
$out[$j]);
}
// Remove one space if smushing
// and remove all \x00 except tail whenever
if ($this->smush_flag) {
$pat = array("/\s\\x00(?!$)|\\x00\s/", "/\\x00(?!$)/");
$rep = array('', '');
} else {
$pat = "/\\x00(?!$)/";
$rep = '';
}
for ($j = 0; $j<$this->height; $j++) {
$out[$j] = preg_replace($pat, $rep, $out[$j]);
}
}
}
$trans = array("\0" => '', $this->hardblank => ' ');
$str = strtr(implode("\n", $out), $trans);
if ($inhtml) {
self::raiseError(
'Do not use the HTML escaping provided by this class in '.
'a Phabricator context.');
}
return $str;
}
/**
* It is preg_replace callback function that makes horizontal letter smushing
*
* @param array $r preg_replace matches array
*
* @return string
* @access private
*/
function _rep($r)
{
if ($this->oldlayout & 1 && $r[1] == $r[2]) {
$this->smush_flag = 1;
return $r[1];
}
if ($this->oldlayout & 2) {
$symb = '|/\\[]{}()<>';
if ($r[1] == '_' && strpos($symb, $r[2]) !== false ||
$r[2] == '_' && strpos($symb, $r[1]) !== false) {
$this->smush_flag = 1;
return $r[1];
}
}
if ($this->oldlayout & 4) {
$classes = '|/\\[]{}()<>';
if (($left = strpos($classes, $r[1])) !== false) {
if (($right = strpos($classes, $r[2])) !== false) {
$this->smush_flag = 1;
return $right > $left ? $r[2] : $r[1];
}
}
}
if ($this->oldlayout & 8) {
$t = array('[' => ']', ']' => '[', '{' => '}', '}' => '{',
'(' => ')', ')' => '(');
if (isset($t[$r[2]]) && $r[1] == $t[$r[2]]) {
$this->smush_flag = 1;
return '|';
}
}
if ($this->oldlayout & 16) {
$t = array("/\\" => '|', "\\/" => 'Y', '><' => 'X');
if (isset($t[$r[1].$r[2]])) {
$this->smush_flag = 1;
return $t[$r[1].$r[2]];
}
}
if ($this->oldlayout & 32) {
if ($r[1] == $r[2] && $r[1] == $this->hardblank) {
$this->smush_flag = 1;
return $this->hardblank;
}
}
return $r[1]."\00".$r[2];
}
/**
* Function loads one character in the internal array from file
*
* @param resource &$fp handle of font file
*
* @return mixed lines of the character or false if foef occured
* @access private
*/
function _char(&$fp)
{
$out = array();
for ($i = 0; $i < $this->height; $i++) {
if (feof($fp)) {
return false;
}
$line = rtrim(fgets($fp, 2048), "\r\n");
if (preg_match('/(.){1,2}$/', $line, $r)) {
$line = str_replace($r[1], '', $line);
}
$line .= "\x00";
$out[] = $line;
}
return $out;
}
/**
* Function for skipping one character in a font file
*
* @param resource &$fp handle of font file
*
* @return boolean always return true
* @access private
*/
function _skip(&$fp)
{
for ($i = 0; $i<$this->height && !feof($fp); $i++) {
fgets($fp, 2048);
}
return true;
}
private static function raiseError($message, $code = 1) {
throw new Exception($message);
}
}