1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13: namespace ManiaLib\Utils;
14:
15: class StyleParser
16: {
17: 18: 19:
20: const COLORED = 0x1000;
21: const ITALIC = 0x2000;
22: const BOLD = 0x4000;
23: const SHADOWED = 0x8000;
24: const CAPITALIZED = 0x10000;
25: const WIDE = 0x20000;
26: const NARROW = 0x40000;
27:
28: static private $gameProtocol = 'maniaplanet';
29: static private $contrast = Color::CONTRAST_AUTO;
30: static private $background = null;
31: static private $fonts = array();
32:
33: static function getGameProtocol()
34: {
35: return self::$gameProtocol;
36: }
37:
38: static function setGameProtocol($protocol)
39: {
40: self::$gameProtocol = $protocol;
41: }
42:
43: static function getContrast()
44: {
45: return self::$contrast;
46: }
47:
48: 49: 50:
51: static function contrastDisable()
52: {
53: self::$contrast = null;
54: }
55:
56: 57: 58:
59: static function contrastAuto()
60: {
61: self::$contrast = Color::CONTRAST_AUTO;
62: }
63:
64: 65: 66:
67: static function contrastForceDarker()
68: {
69: self::$contrast = Color::CONTRAST_DARKER;
70: }
71:
72: 73: 74:
75: static function contrastForceLighter()
76: {
77: self::$contrast = Color::CONTRAST_LIGHTER;
78: }
79:
80: static function getBackground()
81: {
82: return self::$background;
83: }
84:
85: 86: 87:
88: static function setBackground($background)
89: {
90: self::$background = Color::StringToRgb24($background);
91: }
92:
93: 94: 95:
96: static function declareFont($name, $normal, $bold = null, $italic = null, $boldItalic = null)
97: {
98: $this->fonts[$name] = array(
99: 0 => $normal,
100: StyleParser::BOLD => $bold ? : $normal,
101: StyleParser::ITALIC => $italic ? : $normal,
102: StyleParser::BOLD | StyleParser::ITALIC => $boldItalic ? : ($bold ? : ($italic ? : $normal))
103: );
104: }
105:
106: static function getFont($name, $style)
107: {
108: if(isset(self::$fonts[$name]))
109: return self::$fonts[$name][$style];
110: return null;
111: }
112:
113: 114: 115:
116: static function toHtml($string)
117: {
118: return implode('', self::parseString($string));
119: }
120:
121: 122: 123: 124: 125: 126: 127: 128: 129: 130:
131: static function onImage($string, $image, $fontName, $x = 0, $y = 0, $size = 10, $defaultColor = '000')
132: {
133: if($size <= 5)
134: self::onImageQuality($string, $image, $fontName, $x, $y, $size, $defaultColor, 3);
135: else if($size <= 10)
136: self::onImageQuality($string, $image, $fontName, $x, $y, $size, $defaultColor, 2);
137: else if($size <= 20)
138: self::onImageQuality($string, $image, $fontName, $x, $y, $size, $defaultColor, 1);
139: else
140: self::onImageFast($string, $image, $fontName, $x, $y, $size, $defaultColor);
141: }
142:
143: 144: 145: 146: 147: 148: 149: 150: 151: 152:
153: static function onImageFast($string, $image, $fontName, $x = 0, $y = 0, $size = 10, $defaultColor = '000')
154: {
155: $defaultColor = Color::StringToRgb24($defaultColor);
156:
157: $xOffset = 0;
158: foreach(self::parseString($string) as $token)
159: $xOffset += $token->onImage($image, $fontName, $x + $xOffset, $y, $size, $defaultColor, 1);
160: }
161:
162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172:
173: static function onImageQuality($string, $image, $fontName, $x = 0, $y = 0, $size = 10, $defaultColor = '000', $precision = 2)
174: {
175: $defaultColor = Color::StringToRgb24($defaultColor);
176: $factor = 1 << ($precision > 31 ? 31 : $precision);
177:
178: $tokens = self::parseString($string);
179: $rawText = '';
180: foreach($tokens as $token)
181: $rawText .= $token->text;
182: $maxBBox = imagettfbbox($size, 0, self::$fonts[$fontName]->getFile(), $rawText);
183:
184: $brush = imagecreatetruecolor($maxBBox[2] * 2, -$maxBBox[5] + 5);
185: imagefill($brush, 0, 0, 0x7fffffff);
186: $hugeBrush = imagecreatetruecolor(imagesx($brush) * $factor, imagesy($brush) * $factor);
187: imagefill($hugeBrush, 0, 0, 0x7fffffff);
188:
189: $xOffset = 0;
190: foreach($tokens as $token)
191: $xOffset += $token->onImage($hugeBrush, $fontName, $xOffset, imagesy($hugeBrush) * .75, $factor * $size, $defaultColor, $factor);
192:
193: $brushX = $x + imagesx($brush) / 2;
194: $brushY = $y - imagesy($brush) * .25;
195: imagecopyresampled($brush, $hugeBrush, 0, 0, 0, 0, imagesx($brush), imagesy($brush), imagesx($hugeBrush), imagesy($hugeBrush));
196: imagesetbrush($image, $brush);
197: imageline($image, $brushX, $brushY, $brushX, $brushY, IMG_COLOR_BRUSHED);
198: }
199:
200: static private function parseString($string)
201: {
202: $tokens = array();
203: $textToken = new TextToken();
204: $linkToken = null;
205:
206: $stylesStack = array();
207: $style = 0;
208:
209: $isCode = false;
210: $isQuickLink = false;
211: $isPrettyLink = false;
212: $linkLevel = 0;
213: $color = '';
214:
215: $lambdaEndLink = function() use(&$tokens, &$textToken, &$linkToken, &$isQuickLink, &$isPrettyLink, &$style) {
216: if($textToken->text !== '')
217: {
218: $tokens[] = $textToken;
219: $textToken = new TextToken($style);
220: }
221: if(end($tokens) === $linkToken)
222: array_pop($tokens);
223: else
224: $tokens[] = new KnilToken();
225:
226: $linkToken = null;
227: $isQuickLink = false;
228: $isPrettyLink = false;
229: };
230: $lambdaEndText = function($force=false) use(&$tokens, &$textToken, &$style) {
231: if($force || $style != $textToken->style)
232: {
233: if($textToken->text !== '')
234: {
235: $tokens[] = $textToken;
236: $textToken = new TextToken($style);
237: }
238: else
239: $textToken->style = $style;
240: }
241: };
242:
243: foreach(preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY) as $char)
244: {
245: if($isCode)
246: {
247: $isManialink = false;
248:
249: switch($char)
250: {
251: case 'i':
252: $style ^= self::ITALIC;
253: break;
254: case 'o':
255: $style ^= self::BOLD;
256: break;
257: case 's':
258: $style ^= self::SHADOWED;
259: break;
260: case 'w':
261: $style |= self::WIDE;
262: $style &= ~self::NARROW;
263: break;
264: case 'n':
265: $style |= self::NARROW;
266: $style &= ~self::WIDE;
267: break;
268: case 'm':
269: $style &= ~(self::NARROW | self::WIDE);
270: break;
271: case 't':
272: $style ^= self::CAPITALIZED;
273: break;
274: case 'g':
275: $style &= empty($stylesStack) ? ~0x1fff : (end($stylesStack) | ~0x1fff);
276: break;
277: case 'z':
278: $style = empty($stylesStack) ? 0 : end($stylesStack);
279: if($linkToken)
280: $lambdaEndLink();
281: break;
282: case 'h':
283: case 'p':
284: $isManialink = true;
285: case 'l':
286: if($linkToken)
287: $lambdaEndLink();
288: else
289: {
290: $lambdaEndText(true);
291: $tokens[] = $linkToken = new LinkToken($isManialink);
292: $isQuickLink = true;
293: $isPrettyLink = true;
294: $linkLevel = count($stylesStack);
295: }
296: break;
297: case '$':
298: $textToken->text .= '$';
299: break;
300: case '<':
301: array_push($stylesStack, $style);
302: break;
303: case '>':
304: if(!empty($stylesStack))
305: {
306: $style = array_pop($stylesStack);
307: if($linkToken && $linkLevel > count($stylesStack))
308: $lambdaEndLink();
309: }
310: break;
311: default:
312: if(stripos('0123456789abcdef', $char) !== false)
313: $color = $char;
314: }
315:
316: $lambdaEndText();
317: $isCode = false;
318: }
319: else if($char === '$')
320: {
321: $isCode = true;
322: $color = '';
323: if($isQuickLink && $isPrettyLink)
324: $isPrettyLink = false;
325: }
326: else if($color !== '')
327: {
328: $color .= preg_replace('/[^0-9a-f]/iu', '0', $char);
329: if(strlen($color) == 3)
330: {
331: $style &= ~0xfff;
332: $style |= self::COLORED | Color::StringToRgb12($color);
333:
334: $lambdaEndText();
335: $color = '';
336: }
337: }
338: else if($isQuickLink && $isPrettyLink)
339: {
340: if($char === '[')
341: $isQuickLink = false;
342: else
343: {
344: $isPrettyLink = false;
345: $textToken->text .= $char;
346: $linkToken->link .= $char;
347: }
348: }
349: else if($isPrettyLink)
350: {
351: if($char === ']')
352: $isPrettyLink = false;
353: else
354: $linkToken->link .= $char;
355: }
356: else
357: {
358: $textToken->text .= $char;
359: if($isQuickLink)
360: $linkToken->link .= $char;
361: }
362: }
363:
364: if($textToken->text !== '')
365: $tokens[] = $textToken;
366:
367: if($linkToken)
368: {
369: if(end($tokens) === $linkToken)
370: array_pop($tokens);
371: else
372: $tokens[] = new KnilToken();
373: }
374:
375: return $tokens;
376: }
377: }
378:
379: class TextToken
380: {
381: public $style;
382: public $text;
383:
384: function __construct($style = 0, $text = '')
385: {
386: $this->style = $style;
387: $this->text = $text;
388: }
389:
390: function __toString()
391: {
392: if($this->style)
393: {
394: $styles = '';
395: if($this->style & StyleParser::COLORED)
396: {
397: if(StyleParser::getBackground() !== null && StyleParser::getContrast() !== null)
398: {
399: $color = Color::Rgb12ToRgb24($this->style & 0xfff);
400: $color = Color::Contrast($color, StyleParser::getBackground(), StyleParser::getContrast());
401: $color = Color::Rgb24ToString($color);
402: }
403: else
404: $color = Color::Rgb12ToString($this->style & 0xfff);
405: $styles .= 'color:#'.$color.';';
406: }
407: if($this->style & StyleParser::ITALIC)
408: $styles .= 'font-style:italic;';
409: if($this->style & StyleParser::BOLD)
410: $styles .= 'font-weight:bold;';
411: if($this->style & StyleParser::SHADOWED)
412: $styles .= 'text-shadow:1px 1px 1px rgba(0,0,0,.5);';
413: if($this->style & StyleParser::CAPITALIZED)
414: $this->text = strtoupper($this->text);
415: if($this->style & StyleParser::WIDE)
416: $styles .= 'letter-spacing:.1em;font-size:105%;';
417: else if($this->style & StyleParser::NARROW)
418: $styles .= 'letter-spacing:-.1em;font-size:95%;';
419: return '<span style="'.$styles.'">'.htmlentities($this->text, ENT_QUOTES, 'UTF-8').'</span>';
420: }
421: else
422: return htmlentities($this->text, ENT_QUOTES, 'UTF-8');
423: }
424:
425: function onImage($image, $fontName, $x, $y, $size, $color, $shadowOffset)
426: {
427: $fontFile = StyleParser::getFont($fontName, $this->style & (StyleParser::BOLD | StyleParser::ITALIC));
428:
429: if($this->style & StyleParser::COLORED)
430: {
431: $color = Color::Rgb12ToRgb24($this->style & 0xfff);
432: if(StyleParser::getBackground() !== null && StyleParser::getContrast() !== null)
433: $color = Color::Contrast($color, StyleParser::getBackground(), StyleParser::getContrast());
434: }
435:
436: $width = 0;
437: $extraSpace = $size / 5;
438: if($this->style & StyleParser::WIDE || $this->style & StyleParser::NARROW)
439: {
440: $ratio = ($this->style & StyleParser::WIDE) ? 1.5 : 1 / 1.5;
441: $extraSpace *= $ratio;
442: foreach((array) $this->text as $char)
443: {
444: $bBox = imagettfbbox($size, 0, $fontFile, $char);
445:
446:
447: $temp = imagecreatetruecolor(($bBox[2] - $bBox[0]) * 2, ($bBox[3] - $bBox[5]) * 2);
448: imagefill($temp, 0, 0, 0x7fffffff);
449: $brush = imagecreatetruecolor(imagesx($temp) * $ratio, imagesy($temp));
450: imagefill($brush, 0, 0, 0x7fffffff);
451: $brushX = $x + $width + imagesx($brush) / 2;
452: $brushY = $y;
453:
454: if($this->style & StyleParser::SHADOWED)
455: imagettftext($temp, $size, 0, -$bBox[0] + $shadowOffset, $bBox[3] - $bBox[5] + $shadowOffset, 0x3f000000, $fontFile, $char);
456: imagettftext($temp, $size, 0, -$bBox[0], $bBox[3] - $bBox[5], $color, $fontFile, $char);
457: imagecopyresampled($brush, $temp, 0, 0, 0, 0, imagesx($brush), imagesy($brush), imagesx($temp), imagesy($temp));
458: imagesetbrush($image, $brush);
459: imageline($image, $brushX, $brushY, $brushX, $brushY, IMG_COLOR_BRUSHED);
460:
461: imagedestroy($temp);
462: imagedestroy($brush);
463:
464: $width += ($bBox[2] - $bBox[0] + $extraSpace) * $ratio;
465: }
466: }
467: else
468: {
469: foreach((array) $this->text as $char)
470: {
471: $bBox = imagettfbbox($size, 0, $fontFile, $char);
472:
473: if($this->style & StyleParser::SHADOWED)
474: imagettftext($image, $size, 0, $x + $width - $bBox[0] + $shadowOffset, $y + $shadowOffset, 0x3f000000, $fontFile, $char);
475: imagettftext($image, $size, 0, $x + $width - $bBox[0], $y, $color, $fontFile, $char);
476:
477: $width += $bBox[2] - $bBox[0] + $extraSpace;
478: }
479: }
480: return $width;
481: }
482: }
483:
484: class LinkToken
485: {
486: public $link;
487: public $isManialink;
488:
489: function __construct($isManialink)
490: {
491: $this->link = '';
492: $this->isManialink = $isManialink;
493: }
494:
495: function __toString()
496: {
497: $link = $this->link;
498: if($this->isManialink)
499: {
500: $protocol = StyleParser::getGameProtocol().'://';
501: if(substr($link, 0, strlen($protocol)) != $protocol)
502: $link = StyleParser::getGameProtocol().':///:'.$link;
503: }
504: else if(!preg_match('/^[a-z][a-z0-9+.-]*:\/\//ui', $link))
505: {
506: $link = 'http://'.$link;
507: }
508: $link = htmlentities($link, ENT_QUOTES, 'UTF-8');
509: $target = $this->isManialink ? '' : 'target="_blank"';
510: return sprintf('<a href="%s" %s style="color:inherit">', $link, $target);
511: }
512:
513: function onImage($image, Font $font, $x, $y, $size, $color, $shadowOffset)
514: {
515: return null;
516: }
517: }
518:
519: class KnilToken
520: {
521:
522: function __toString()
523: {
524: return '</a>';
525: }
526:
527: function onImage($image, Font $font, $x, $y, $size, $color, $shadowOffset)
528: {
529: return null;
530: }
531: }
532:
533: ?>
534: