composer package updates
[openemr.git] / vendor / zendframework / zend-text / src / Figlet / Figlet.php
blob9622bbfdee99ca45d3f470a8d2b9b493b66d1591
1 <?php
2 /**
3 * @see https://github.com/zendframework/zend-text for the canonical source repository
4 * @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (https://www.zend.com)
5 * @license https://github.com/zendframework/zend-text/blob/master/LICENSE.md New BSD License
6 */
8 namespace Zend\Text\Figlet;
10 use Traversable;
11 use Zend\Stdlib\ArrayUtils;
12 use Zend\Stdlib\StringUtils;
14 /**
15 * Zend\Text\Figlet is a PHP implementation of FIGlet
17 class Figlet
19 /**
20 * Smush2 layout modes
22 const SM_EQUAL = 0x01;
23 const SM_LOWLINE = 0x02;
24 const SM_HIERARCHY = 0x04;
25 const SM_PAIR = 0x08;
26 const SM_BIGX = 0x10;
27 const SM_HARDBLANK = 0x20;
28 const SM_KERN = 0x40;
29 const SM_SMUSH = 0x80;
31 /**
32 * Smush mode override modes
34 const SMO_NO = 0;
35 const SMO_YES = 1;
36 const SMO_FORCE = 2;
38 /**
39 * Justifications
41 const JUSTIFICATION_LEFT = 0;
42 const JUSTIFICATION_CENTER = 1;
43 const JUSTIFICATION_RIGHT = 2;
45 /**
46 * Write directions
48 const DIRECTION_LEFT_TO_RIGHT = 0;
49 const DIRECTION_RIGHT_TO_LEFT = 1;
51 /**
52 * Magic fontfile number
54 const FONTFILE_MAGIC_NUMBER = 'flf2';
56 /**
57 * Array containing all characters of the current font
59 * @var array
61 protected $charList = [];
63 /**
64 * Indicates if a font was loaded yet
66 * @var bool
68 protected $fontLoaded = false;
70 /**
71 * Latin-1 codes for German letters, respectively:
73 * LATIN CAPITAL LETTER A WITH DIAERESIS = A-umlaut
74 * LATIN CAPITAL LETTER O WITH DIAERESIS = O-umlaut
75 * LATIN CAPITAL LETTER U WITH DIAERESIS = U-umlaut
76 * LATIN SMALL LETTER A WITH DIAERESIS = a-umlaut
77 * LATIN SMALL LETTER O WITH DIAERESIS = o-umlaut
78 * LATIN SMALL LETTER U WITH DIAERESIS = u-umlaut
79 * LATIN SMALL LETTER SHARP S = ess-zed
81 * @var array
83 protected $germanChars = [196, 214, 220, 228, 246, 252, 223];
85 /**
86 * Output width, defaults to 80.
88 * @var int
90 protected $outputWidth = 80;
92 /**
93 * Hard blank character
95 * @var string
97 protected $hardBlank;
99 /**
100 * Height of the characters
102 * @var int
104 protected $charHeight;
107 * Max length of any character
109 * @var int
111 protected $maxLength;
114 * Smush mode
116 * @var int
118 protected $smushMode = 0;
121 * Smush defined by the font
123 * @var int
125 protected $fontSmush = 0;
128 * Smush defined by the user
130 * @var int
132 protected $userSmush = 0;
135 * Whether to handle paragraphs || not
137 * @var bool
139 protected $handleParagraphs = false;
142 * Justification for the text, according to $outputWidth
144 * For using font default, this parameter should be null, else one of
145 * the values of Zend\Text\Figlet::JUSTIFICATION_*
147 * @var int
149 protected $justification = null;
152 * Direction of text-writing, namely right to left
154 * For using font default, this parameter should be null, else one of
155 * the values of Zend\Text\Figlet::DIRECTION_*
157 * @var int
159 protected $rightToLeft = null;
162 * Override font file smush layout
164 * @var int
166 protected $smushOverride = 0;
169 * Options of the current font
171 * @var array
173 protected $fontOptions = [];
176 * Previous character width
178 * @var int
180 protected $previousCharWidth = 0;
183 * Current character width
185 * @var int
187 protected $currentCharWidth = 0;
190 * Current outline length
192 * @var int
194 protected $outlineLength = 0;
197 * Maximum outline length
199 * @var int
201 protected $outlineLengthLimit = 0;
204 * In character line
206 * @var string
208 protected $inCharLine;
211 * In character line length
213 * @var int
215 protected $inCharLineLength = 0;
218 * Maximum in character line length
220 * @var int
222 protected $inCharLineLengthLimit = 0;
225 * Current char
227 * @var array
229 protected $currentChar = null;
232 * Current output line
234 * @var array
236 protected $outputLine;
239 * Current output
241 * @var string
243 protected $output;
246 * Option keys to skip when calling setOptions()
248 * @var array
250 protected $skipOptions = [
251 'options',
252 'config',
256 * Instantiate the FIGlet with a specific font. If no font is given, the
257 * standard font is used. You can also supply multiple options via
258 * the $options variable, which can either be an array or an instance of
259 * Zend\Config\Config.
261 * @param array|Traversable $options Options for the output
263 public function __construct($options = null)
265 // Set options
266 if ($options instanceof Traversable) {
267 $options = ArrayUtils::iteratorToArray($options);
269 if (is_array($options)) {
270 $this->setOptions($options);
273 // If no font was defined, load default font
274 if (! $this->fontLoaded) {
275 $this->_loadFont(__DIR__ . '/zend-framework.flf');
280 * Set options from array
282 * @param array $options Configuration for Figlet
283 * @return Figlet
285 public function setOptions(array $options)
287 foreach ($options as $key => $value) {
288 if (in_array(strtolower($key), $this->skipOptions)) {
289 continue;
292 $method = 'set' . ucfirst($key);
293 if (method_exists($this, $method)) {
294 $this->$method($value);
297 return $this;
301 * Set a font to use
303 * @param string $font Path to the font
304 * @return Figlet
306 public function setFont($font)
308 $this->_loadFont($font);
309 return $this;
313 * Set handling of paragraphs
315 * @param bool $handleParagraphs Whether to handle paragraphs or not
316 * @return Figlet
318 public function setHandleParagraphs($handleParagraphs)
320 $this->handleParagraphs = (bool) $handleParagraphs;
321 return $this;
325 * Set the justification. 0 stands for left aligned, 1 for centered and 2
326 * for right aligned.
328 * @param int $justification Justification of the output text
329 * @return Figlet
331 public function setJustification($justification)
333 $this->justification = min(3, max(0, (int) $justification));
334 return $this;
338 * Set the output width
340 * @param int $outputWidth Output with which should be used for word
341 * wrapping and justification
342 * @return Figlet
344 public function setOutputWidth($outputWidth)
346 $this->outputWidth = max(1, (int) $outputWidth);
347 return $this;
351 * Set right to left mode. For writing from left to right, use
352 * Zend\Text\Figlet::DIRECTION_LEFT_TO_RIGHT. For writing from right to left,
353 * use Zend\Text\Figlet::DIRECTION_RIGHT_TO_LEFT.
355 * @param int $rightToLeft Right-to-left mode
356 * @return Figlet
358 public function setRightToLeft($rightToLeft)
360 $this->rightToLeft = min(1, max(0, (int) $rightToLeft));
361 return $this;
365 * Set the smush mode.
367 * Use one of the constants of Zend\Text\Figlet::SM_*, you may combine them.
369 * @param int $smushMode Smush mode to use for generating text
370 * @return Figlet
372 public function setSmushMode($smushMode)
374 $smushMode = (int) $smushMode;
376 if ($smushMode < -1) {
377 $this->smushOverride = self::SMO_NO;
378 } else {
379 if ($smushMode === 0) {
380 $this->userSmush = self::SM_KERN;
381 } elseif ($smushMode === -1) {
382 $this->userSmush = 0;
383 } else {
384 $this->userSmush = (($smushMode & 63) | self::SM_SMUSH);
387 $this->smushOverride = self::SMO_YES;
390 $this->_setUsedSmush();
392 return $this;
396 * Render a FIGlet text
398 * @param string $text Text to convert to a figlet text
399 * @param string $encoding Encoding of the input string
400 * @throws Exception\InvalidArgumentException When $text is not a string
401 * @throws Exception\UnexpectedValueException When $text it not properly encoded
402 * @return string
404 public function render($text, $encoding = 'UTF-8')
406 if (! is_string($text)) {
407 throw new Exception\InvalidArgumentException('$text must be a string');
410 // Get the string wrapper supporting UTF-8 character encoding and the input encoding
411 $strWrapper = StringUtils::getWrapper($encoding, 'UTF-8');
413 // Convert $text to UTF-8 and check encoding
414 $text = $strWrapper->convert($text);
415 if (! StringUtils::isValidUtf8($text)) {
416 throw new Exception\UnexpectedValueException('$text is not encoded with ' . $encoding);
419 $strWrapper = StringUtils::getWrapper('UTF-8');
421 $this->output = '';
422 $this->outputLine = [];
424 $this->_clearLine();
426 $this->outlineLengthLimit = ($this->outputWidth - 1);
427 $this->inCharLineLengthLimit = ($this->outputWidth * 4 + 100);
429 $wordBreakMode = 0;
430 $lastCharWasEol = false;
431 $textLength = $strWrapper->strlen($text);
433 for ($charNum = 0; $charNum < $textLength; $charNum++) {
434 // Handle paragraphs
435 $char = $strWrapper->substr($text, $charNum, 1);
437 if ($char === "\n" && $this->handleParagraphs && ! $lastCharWasEol) {
438 $nextChar = $strWrapper->substr($text, ($charNum + 1), 1);
439 if (! $nextChar) {
440 $nextChar = null;
443 $char = (ctype_space($nextChar)) ? "\n" : ' ';
446 $lastCharWasEol = (ctype_space($char) && $char !== "\t" && $char !== ' ');
448 if (ctype_space($char)) {
449 $char = ($char === "\t" || $char === ' ') ? ' ' : "\n";
452 // Skip unprintable characters
453 $ordChar = $this->_uniOrd($char);
454 if (($ordChar > 0 && $ordChar < 32 && $char !== "\n") || $ordChar === 127) {
455 continue;
458 // Build the character
459 // Note: The following code is complex and thoroughly tested.
460 // Be careful when modifying!
461 do {
462 $charNotAdded = false;
464 if ($wordBreakMode === -1) {
465 if ($char === ' ') {
466 break;
467 } elseif ($char === "\n") {
468 $wordBreakMode = 0;
469 break;
472 $wordBreakMode = 0;
475 if ($char === "\n") {
476 $this->_appendLine();
477 $wordBreakMode = false;
478 } elseif ($this->_addChar($char)) {
479 if ($char !== ' ') {
480 $wordBreakMode = ($wordBreakMode >= 2) ? 3 : 1;
481 } else {
482 $wordBreakMode = ($wordBreakMode > 0) ? 2 : 0;
484 } elseif ($this->outlineLength === 0) {
485 for ($i = 0; $i < $this->charHeight; $i++) {
486 if ($this->rightToLeft === 1 && $this->outputWidth > 1) {
487 $offset = (strlen($this->currentChar[$i]) - $this->outlineLengthLimit);
488 $this->_putString(substr($this->currentChar[$i], $offset));
489 } else {
490 $this->_putString($this->currentChar[$i]);
494 $wordBreakMode = -1;
495 } elseif ($char === ' ') {
496 if ($wordBreakMode === 2) {
497 $this->_splitLine();
498 } else {
499 $this->_appendLine();
502 $wordBreakMode = -1;
503 } else {
504 if ($wordBreakMode >= 2) {
505 $this->_splitLine();
506 } else {
507 $this->_appendLine();
510 $wordBreakMode = ($wordBreakMode === 3) ? 1 : 0;
511 $charNotAdded = true;
513 } while ($charNotAdded);
516 if ($this->outlineLength !== 0) {
517 $this->_appendLine();
520 return $this->output;
524 * Puts the given string, substituting blanks for hardblanks. If outputWidth
525 * is 1, puts the entire string; otherwise puts at most outputWidth - 1
526 * characters. Puts a newline at the end of the string. The string is left-
527 * justified, centered or right-justified (taking outputWidth as the screen
528 * width) if justification is 0, 1 or 2 respectively.
530 * @param string $string The string to add to the output
531 * @return void
533 // @codingStandardsIgnoreStart
534 protected function _putString($string)
536 // @codingStandardsIgnoreEnd
537 $length = strlen($string);
539 if ($this->outputWidth > 1) {
540 if ($length > ($this->outputWidth - 1)) {
541 $length = ($this->outputWidth - 1);
544 if ($this->justification > 0) {
545 for ($i = 1;
546 ((3 - $this->justification) * $i + $length + $this->justification - 2) < $this->outputWidth;
547 $i++) {
548 $this->output .= ' ';
553 $this->output .= str_replace($this->hardBlank, ' ', $string) . "\n";
557 * Appends the current line to the output
559 * @return void
561 // @codingStandardsIgnoreStart
562 protected function _appendLine()
564 // @codingStandardsIgnoreEnd
565 for ($i = 0; $i < $this->charHeight; $i++) {
566 $this->_putString($this->outputLine[$i]);
569 $this->_clearLine();
573 * Splits inCharLine at the last word break (bunch of consecutive blanks).
574 * Makes a new line out of the first part and appends it using appendLine().
575 * Makes a new line out of the second part and returns.
577 * @return void
579 // @codingStandardsIgnoreStart
580 protected function _splitLine()
582 // @codingStandardsIgnoreEnd
583 $gotSpace = false;
584 for ($i = ($this->inCharLineLength - 1); $i >= 0; $i--) {
585 if (! $gotSpace && $this->inCharLine[$i] === ' ') {
586 $gotSpace = true;
587 $lastSpace = $i;
590 if ($gotSpace && $this->inCharLine[$i] !== ' ') {
591 break;
595 $firstLength = ($i + 1);
596 $lastLength = ($this->inCharLineLength - $lastSpace - 1);
598 $firstPart = '';
599 for ($i = 0; $i < $firstLength; $i++) {
600 $firstPart[$i] = $this->inCharLine[$i];
603 $lastPart = '';
604 for ($i = 0; $i < $lastLength; $i++) {
605 $lastPart[$i] = $this->inCharLine[($lastSpace + 1 + $i)];
608 $this->_clearLine();
610 for ($i = 0; $i < $firstLength; $i++) {
611 $this->_addChar($firstPart[$i]);
614 $this->_appendLine();
616 for ($i = 0; $i < $lastLength; $i++) {
617 $this->_addChar($lastPart[$i]);
622 * Clears the current line
624 * @return void
626 // @codingStandardsIgnoreStart
627 protected function _clearLine()
629 // @codingStandardsIgnoreEnd
630 for ($i = 0; $i < $this->charHeight; $i++) {
631 $this->outputLine[$i] = '';
634 $this->outlineLength = 0;
635 $this->inCharLineLength = 0;
639 * Attempts to add the given character onto the end of the current line.
640 * Returns true if this can be done, false otherwise.
642 * @param string $char Character which to add to the output
643 * @return bool
645 // @codingStandardsIgnoreStart
646 protected function _addChar($char)
648 // @codingStandardsIgnoreEnd
649 $this->_getLetter($char);
651 if ($this->currentChar === null) {
652 return true;
655 $smushAmount = $this->_smushAmount();
657 if (($this->outlineLength + $this->currentCharWidth - $smushAmount) > $this->outlineLengthLimit
658 || ($this->inCharLineLength + 1) > $this->inCharLineLengthLimit) {
659 return false;
662 for ($row = 0; $row < $this->charHeight; $row++) {
663 if ($this->rightToLeft === 1) {
664 $tempLine = $this->currentChar[$row];
666 for ($k = 0; $k < $smushAmount; $k++) {
667 $position = ($this->currentCharWidth - $smushAmount + $k);
668 $tempLine[$position] = $this->_smushem($tempLine[$position], $this->outputLine[$row][$k]);
671 $this->outputLine[$row] = $tempLine . substr($this->outputLine[$row], $smushAmount);
672 } else {
673 for ($k = 0; $k < $smushAmount; $k++) {
674 if (($this->outlineLength - $smushAmount + $k) < 0) {
675 continue;
678 $position = ($this->outlineLength - $smushAmount + $k);
679 if (isset($this->outputLine[$row][$position])) {
680 $leftChar = $this->outputLine[$row][$position];
681 } else {
682 $leftChar = null;
685 $this->outputLine[$row][$position] = $this->_smushem($leftChar, $this->currentChar[$row][$k]);
688 $this->outputLine[$row] .= substr($this->currentChar[$row], $smushAmount);
692 $this->outlineLength = strlen($this->outputLine[0]);
693 $this->inCharLine[$this->inCharLineLength++] = $char;
695 return true;
699 * Gets the requested character and sets current and previous char width.
701 * @param string $char The character from which to get the letter of
702 * @return void
704 // @codingStandardsIgnoreStart
705 protected function _getLetter($char)
707 // @codingStandardsIgnoreEnd
708 if (array_key_exists($this->_uniOrd($char), $this->charList)) {
709 $this->currentChar = $this->charList[$this->_uniOrd($char)];
710 $this->previousCharWidth = $this->currentCharWidth;
711 $this->currentCharWidth = strlen($this->currentChar[0]);
712 } else {
713 $this->currentChar = null;
718 * Returns the maximum amount that the current character can be smushed into
719 * the current line.
721 * @return int
723 // @codingStandardsIgnoreStart
724 protected function _smushAmount()
726 // @codingStandardsIgnoreEnd
727 if (($this->smushMode & (self::SM_SMUSH | self::SM_KERN)) === 0) {
728 return 0;
731 $maxSmush = $this->currentCharWidth;
733 for ($row = 0; $row < $this->charHeight; $row++) {
734 if ($this->rightToLeft === 1) {
735 $charbd = strlen($this->currentChar[$row]);
736 while (true) {
737 if (! isset($this->currentChar[$row][$charbd])) {
738 $leftChar = null;
739 } else {
740 $leftChar = $this->currentChar[$row][$charbd];
743 if ($charbd > 0 && ($leftChar === null || $leftChar == ' ')) {
744 $charbd--;
745 } else {
746 break;
750 $linebd = 0;
751 while (true) {
752 if (! isset($this->outputLine[$row][$linebd])) {
753 $rightChar = null;
754 } else {
755 $rightChar = $this->outputLine[$row][$linebd];
758 if ($rightChar === ' ') {
759 $linebd++;
760 } else {
761 break;
765 $amount = ($linebd + $this->currentCharWidth - 1 - $charbd);
766 } else {
767 $linebd = strlen($this->outputLine[$row]);
768 while (true) {
769 if (! isset($this->outputLine[$row][$linebd])) {
770 $leftChar = null;
771 } else {
772 $leftChar = $this->outputLine[$row][$linebd];
775 if ($linebd > 0 && ($leftChar === null || $leftChar == ' ')) {
776 $linebd--;
777 } else {
778 break;
782 $charbd = 0;
783 while (true) {
784 if (! isset($this->currentChar[$row][$charbd])) {
785 $rightChar = null;
786 } else {
787 $rightChar = $this->currentChar[$row][$charbd];
790 if ($rightChar === ' ') {
791 $charbd++;
792 } else {
793 break;
797 $amount = ($charbd + $this->outlineLength - 1 - $linebd);
800 if (empty($leftChar) || $leftChar === ' ') {
801 $amount++;
802 } elseif (! empty($rightChar)) {
803 if ($this->_smushem($leftChar, $rightChar) !== null) {
804 $amount++;
808 $maxSmush = min($amount, $maxSmush);
811 return $maxSmush;
815 * Given two characters, attempts to smush them into one, according to the
816 * current smushmode. Returns smushed character or false if no smushing can
817 * be done.
819 * Smushmode values are sum of following (all values smush blanks):
821 * 1: Smush equal chars (not hardblanks)
822 * 2: Smush '_' with any char in hierarchy below
823 * 4: hierarchy: "|", "/\", "[]", "{}", "()", "<>"
824 * Each class in hier. can be replaced by later class.
825 * 8: [ + ] -> |, { + } -> |, ( + ) -> |
826 * 16: / + \ -> X, > + < -> X (only in that order)
827 * 32: hardblank + hardblank -> hardblank
829 * @param string $leftChar Left character to smush
830 * @param string $rightChar Right character to smush
831 * @return string
833 // @codingStandardsIgnoreStart
834 protected function _smushem($leftChar, $rightChar)
836 // @codingStandardsIgnoreEnd
837 if ($leftChar === ' ') {
838 return $rightChar;
841 if ($rightChar === ' ') {
842 return $leftChar;
845 if ($this->previousCharWidth < 2 || $this->currentCharWidth < 2) {
846 // Disallows overlapping if the previous character or the current
847 // character has a width of one or zero.
848 return;
851 if (($this->smushMode & self::SM_SMUSH) === 0) {
852 // Kerning
853 return;
856 if (($this->smushMode & 63) === 0) {
857 // This is smushing by universal overlapping
858 if ($leftChar === ' ') {
859 return $rightChar;
860 } elseif ($rightChar === ' ') {
861 return $leftChar;
862 } elseif ($leftChar === $this->hardBlank) {
863 return $rightChar;
864 } elseif ($rightChar === $this->hardBlank) {
865 return $rightChar;
866 } elseif ($this->rightToLeft === 1) {
867 return $leftChar;
868 } else {
869 // Occurs in the absence of above exceptions
870 return $rightChar;
874 if (($this->smushMode & self::SM_HARDBLANK) > 0) {
875 if ($leftChar === $this->hardBlank && $rightChar === $this->hardBlank) {
876 return $leftChar;
880 if ($leftChar === $this->hardBlank && $rightChar === $this->hardBlank) {
881 return;
884 if (($this->smushMode & self::SM_EQUAL) > 0) {
885 if ($leftChar === $rightChar) {
886 return $leftChar;
890 if (($this->smushMode & self::SM_LOWLINE) > 0) {
891 if ($leftChar === '_' && strchr('|/\\[]{}()<>', $rightChar) !== false) {
892 return $rightChar;
893 } elseif ($rightChar === '_' && strchr('|/\\[]{}()<>', $leftChar) !== false) {
894 return $leftChar;
898 if (($this->smushMode & self::SM_HIERARCHY) > 0) {
899 if ($leftChar === '|' && strchr('/\\[]{}()<>', $rightChar) !== false) {
900 return $rightChar;
901 } elseif ($rightChar === '|' && strchr('/\\[]{}()<>', $leftChar) !== false) {
902 return $leftChar;
903 } elseif (strchr('/\\', $leftChar) && strchr('[]{}()<>', $rightChar) !== false) {
904 return $rightChar;
905 } elseif (strchr('/\\', $rightChar) && strchr('[]{}()<>', $leftChar) !== false) {
906 return $leftChar;
907 } elseif (strchr('[]', $leftChar) && strchr('{}()<>', $rightChar) !== false) {
908 return $rightChar;
909 } elseif (strchr('[]', $rightChar) && strchr('{}()<>', $leftChar) !== false) {
910 return $leftChar;
911 } elseif (strchr('{}', $leftChar) && strchr('()<>', $rightChar) !== false) {
912 return $rightChar;
913 } elseif (strchr('{}', $rightChar) && strchr('()<>', $leftChar) !== false) {
914 return $leftChar;
915 } elseif (strchr('()', $leftChar) && strchr('<>', $rightChar) !== false) {
916 return $rightChar;
917 } elseif (strchr('()', $rightChar) && strchr('<>', $leftChar) !== false) {
918 return $leftChar;
922 if (($this->smushMode & self::SM_PAIR) > 0) {
923 if ($leftChar === '[' && $rightChar === ']') {
924 return '|';
925 } elseif ($rightChar === '[' && $leftChar === ']') {
926 return '|';
927 } elseif ($leftChar === '{' && $rightChar === '}') {
928 return '|';
929 } elseif ($rightChar === '{' && $leftChar === '}') {
930 return '|';
931 } elseif ($leftChar === '(' && $rightChar === ')') {
932 return '|';
933 } elseif ($rightChar === '(' && $leftChar === ')') {
934 return '|';
938 if (($this->smushMode & self::SM_BIGX) > 0) {
939 if ($leftChar === '/' && $rightChar === '\\') {
940 return '|';
941 } elseif ($rightChar === '/' && $leftChar === '\\') {
942 return 'Y';
943 } elseif ($leftChar === '>' && $rightChar === '<') {
944 return 'X';
948 return;
952 * Load the specified font
954 * @param string $fontFile Font file to load
955 * @throws Exception\RuntimeException When font file was not found
956 * @throws Exception\RuntimeException When GZIP library is required but not found
957 * @throws Exception\RuntimeException When font file is not readable
958 * @throws Exception\UnexpectedValueException When font file is not a FIGlet 2 font file
959 * @return void
961 // @codingStandardsIgnoreStart
962 protected function _loadFont($fontFile)
964 // @codingStandardsIgnoreEnd
965 // Check if the font file exists
966 if (! file_exists($fontFile)) {
967 throw new Exception\RuntimeException($fontFile . ': Font file not found');
970 // Check if gzip support is required
971 if (substr($fontFile, -3) === '.gz') {
972 if (! function_exists('gzcompress')) {
973 throw new Exception\RuntimeException('GZIP library is required for gzip compressed font files');
976 $fontFile = 'compress.zlib://' . $fontFile;
977 $compressed = true;
978 } else {
979 $compressed = false;
982 // Try to open the file
983 $fp = fopen($fontFile, 'rb');
984 if ($fp === false) {
985 throw new Exception\RuntimeException($fontFile . ': Could not open file');
988 // If the file is not compressed, lock the stream
989 if (! $compressed) {
990 flock($fp, LOCK_SH);
993 // Get magic
994 $magic = $this->_readMagic($fp);
996 // Get the header
997 $line = fgets($fp, 1000) ?: '';
998 $numsRead = sscanf(
999 $line,
1000 '%*c%c %d %*d %d %d %d %d %d',
1001 $this->hardBlank,
1002 $this->charHeight,
1003 $this->maxLength,
1004 $smush,
1005 $cmtLines,
1006 $rightToLeft,
1007 $this->fontSmush
1010 if ($magic !== self::FONTFILE_MAGIC_NUMBER || $numsRead < 5) {
1011 throw new Exception\UnexpectedValueException($fontFile . ': Not a FIGlet 2 font file');
1014 // Set default right to left
1015 if ($numsRead < 6) {
1016 $rightToLeft = 0;
1019 // If no smush2, decode smush into smush2
1020 if ($numsRead < 7) {
1021 if ($smush === 2) {
1022 $this->fontSmush = self::SM_KERN;
1023 } elseif ($smush < 0) {
1024 $this->fontSmush = 0;
1025 } else {
1026 $this->fontSmush = (($smush & 31) | self::SM_SMUSH);
1030 // Correct char height && maxlength
1031 $this->charHeight = max(1, $this->charHeight);
1032 $this->maxLength = max(1, $this->maxLength);
1034 // Give ourselves some extra room
1035 $this->maxLength += 100;
1037 // See if we have to override smush settings
1038 $this->_setUsedSmush();
1040 // Get left to right value
1041 if ($this->rightToLeft === null) {
1042 $this->rightToLeft = $rightToLeft;
1045 // Get justification value
1046 if ($this->justification === null) {
1047 $this->justification = (2 * $this->rightToLeft);
1050 // Skip all comment lines
1051 for ($line = 1; $line <= $cmtLines; $line++) {
1052 $this->_skipToEol($fp);
1055 // Fetch all ASCII characters
1056 for ($asciiCode = 32; $asciiCode < 127; $asciiCode++) {
1057 $this->charList[$asciiCode] = $this->_loadChar($fp);
1060 // Fetch all german characters
1061 foreach ($this->germanChars as $uniCode) {
1062 $char = $this->_loadChar($fp);
1064 if ($char === false) {
1065 fclose($fp);
1066 return;
1069 if (trim(implode('', $char)) !== '') {
1070 $this->charList[$uniCode] = $char;
1074 // At the end fetch all extended characters
1075 while (! feof($fp)) {
1076 // Get the Unicode
1077 $uniCode = fgets($fp, 2048);
1079 if (false === $uniCode) {
1080 continue;
1083 list($uniCode) = explode(' ', $uniCode);
1085 if (empty($uniCode)) {
1086 continue;
1089 // Convert it if required
1090 if (substr($uniCode, 0, 2) === '0x') {
1091 $uniCode = hexdec(substr($uniCode, 2));
1092 } elseif (substr($uniCode, 0, 1) === '0' and
1093 $uniCode !== '0' or
1094 substr($uniCode, 0, 2) === '-0') {
1095 $uniCode = octdec($uniCode);
1096 } else {
1097 $uniCode = (int) $uniCode;
1100 // Now fetch the character
1101 $char = $this->_loadChar($fp);
1103 if ($char === false) {
1104 fclose($fp);
1105 return;
1108 $this->charList[$uniCode] = $char;
1111 fclose($fp);
1113 $this->fontLoaded = true;
1117 * Set the used smush mode, according to smush override, user smush and
1118 * font smush.
1120 * @return void
1122 // @codingStandardsIgnoreStart
1123 protected function _setUsedSmush()
1125 // @codingStandardsIgnoreEnd
1126 if ($this->smushOverride === self::SMO_NO) {
1127 $this->smushMode = $this->fontSmush;
1128 } elseif ($this->smushOverride === self::SMO_YES) {
1129 $this->smushMode = $this->userSmush;
1130 } elseif ($this->smushOverride === self::SMO_FORCE) {
1131 $this->smushMode = ($this->fontSmush | $this->userSmush);
1136 * Reads a four-character magic string from a stream
1138 * @param resource $fp File pointer to the font file
1139 * @return string
1141 // @codingStandardsIgnoreStart
1142 protected function _readMagic($fp)
1144 // @codingStandardsIgnoreEnd
1145 $magic = '';
1147 for ($i = 0; $i < 4; $i++) {
1148 $magic .= fgetc($fp);
1151 return $magic;
1155 * Skip a stream to the end of line
1157 * @param resource $fp File pointer to the font file
1158 * @return void
1160 // @codingStandardsIgnoreStart
1161 protected function _skipToEol($fp)
1163 // @codingStandardsIgnoreEnd
1164 $dummy = fgetc($fp);
1165 while ($dummy !== false && ! feof($fp)) {
1166 if ($dummy === "\n") {
1167 return;
1170 if ($dummy === "\r") {
1171 $dummy = fgetc($fp);
1173 if (! feof($fp) && $dummy !== "\n") {
1174 fseek($fp, -1, SEEK_SET);
1177 return;
1180 $dummy = fgetc($fp);
1185 * Load a single character from the font file
1187 * @param resource $fp File pointer to the font file
1188 * @return array
1190 // @codingStandardsIgnoreStart
1191 protected function _loadChar($fp)
1193 // @codingStandardsIgnoreEnd
1194 $char = [];
1196 for ($i = 0; $i < $this->charHeight; $i++) {
1197 if (feof($fp)) {
1198 return false;
1201 $line = rtrim(fgets($fp, 2048), "\r\n");
1203 if (preg_match('#(.)\\1?$#', $line, $result) === 1) {
1204 $line = str_replace($result[1], '', $line);
1207 $char[] = $line;
1210 return $char;
1214 * Unicode compatible ord() method
1216 * @param string $c The char to get the value from
1217 * @return int
1219 // @codingStandardsIgnoreStart
1220 protected function _uniOrd($c)
1222 // @codingStandardsIgnoreEnd
1223 $h = ord($c[0]);
1225 if ($h <= 0x7F) {
1226 $ord = $h;
1227 } elseif ($h < 0xC2) {
1228 $ord = 0;
1229 } elseif ($h <= 0xDF) {
1230 $ord = (($h & 0x1F) << 6 | (ord($c[1]) & 0x3F));
1231 } elseif ($h <= 0xEF) {
1232 $ord = (($h & 0x0F) << 12 | (ord($c[1]) & 0x3F) << 6 | (ord($c[2]) & 0x3F));
1233 } elseif ($h <= 0xF4) {
1234 $ord = (($h & 0x0F) << 18 | (ord($c[1]) & 0x3F) << 12 |
1235 (ord($c[2]) & 0x3F) << 6 | (ord($c[3]) & 0x3F));
1236 } else {
1237 $ord = 0;
1240 return $ord;