├── .gitignore ├── .styleci.yaml ├── LICENSE ├── README.md ├── composer.json └── src ├── Contracts ├── GDTool.php └── StringDecorator.php ├── Exceptions └── PersianGDException.php ├── GDTool.php └── PersianStringDecorator.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | composer.lock 3 | vendor/ 4 | .idea/ 5 | .DS_Store 6 | 7 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 8 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 9 | # composer.lock 10 | -------------------------------------------------------------------------------- /.styleci.yaml: -------------------------------------------------------------------------------- 1 | risky: false 2 | version: 7 3 | tab-width: 4 4 | use-tabs: false 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Quince PHP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # persian-gd 2 | PHP GD library for Persian text support 3 | 4 | ## Installation 5 | 6 | Add persian-gd to your composer.json: 7 | 8 | ``` 9 | "require": { 10 | "quince/persian-gd": "~1.0" 11 | } 12 | ``` 13 | 14 | or run: 15 | 16 | ``` 17 | composer require quince/persian-gd ~1.0 18 | ``` 19 | 20 | ## Usage 21 | 22 | ### Simple usage 23 | 24 | ```php 25 | setFileName('/path/to/output/image') 30 | ->addLine("سلام دنیا") 31 | ->build(); 32 | ``` 33 | 34 | ### Advanced Usage 35 | 36 | GDTool has many options that you can set them in two different way. 37 | 38 | #### Setting GDTools options on construction 39 | 40 | You can set the options while you're constructing a GDTool instance. 41 | 42 | ```php 43 | 1200, 47 | 'backgroundColor' => '#FFF000', 48 | 'fontColor' => '#000000', 49 | // ... 50 | ]); 51 | 52 | $gdTool->addLine('لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است.') 53 | ->addLine('چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد.') 54 | ->addLine('کتابهای زیادی در شصت و سه درصد گذشته، حال و آینده شناخت فراوان جامعه و متخصصان را می طلبد تا با نرم افزارها شناخت بیشتری را برای طراحان رایانه ای علی الخصوص طراحان خلاقی و فرهنگ پیشرو در زبان فارسی ایجاد کرد.') 55 | ->build(); 56 | ``` 57 | 58 | #### Setting GDTools options on the go 59 | 60 | In a situation that you have an instance of GDTool, you can set (or change) any option you want by their setter method. 61 | 62 | ```php 63 | setWidth(500) // set image width - default: 500 68 | ->setFont('/path/to/font') // set path to your desired font 69 | ->setBackgroundColor('#FF0000') // set background color in hex code - default: #FFFFFF 70 | ->setFontColor('#00FF00') // set foreground color in hex code - default: #000000 71 | ->setFontSize(10) // set size of font in px - default: 12 72 | ->setLineHeight(16) // set line height - default: 25 73 | ->setAngle(45) // set angle of text in degree - default: 0 74 | ->setHorizontalPosition(100) // set position of start point from top of image - default: 10 75 | ->setVerticalPosition(100) // set position of start point from left or right of image - default: 10 76 | ->setUseLocalNumber(true) // set weather use local (persian) numbers character or not - default: true 77 | ->setFileName('/path/to/output/image') // set the path of output image 78 | ->addLine('لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است.') 79 | ->addLine('چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد.') 80 | ->build(); 81 | 82 | } 83 | ``` 84 | 85 | #### Add multiple line at once 86 | 87 | You can pass an array of strings, and GDTool will print them in given order in canvas. 88 | 89 | ```php 90 | addLines([ 95 | 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است.', 96 | 'چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد.', 97 | 'کتابهای زیادی در شصت و سه درصد گذشته، حال و آینده شناخت فراوان جامعه و متخصصان را می طلبد تا با نرم افزارها شناخت بیشتری را برای طراحان رایانه ای علی الخصوص طراحان خلاقی و فرهنگ پیشرو در زبان فارسی ایجاد کرد.', 98 | ])->setFileName('/path/to/output/image')->build(); 99 | ``` 100 | 101 | #### Outputting generating image 102 | 103 | There may be a situation that you don't want to save generated image in a file, and you want for example return it as image response. 104 | 105 | ```php 106 | setOutputImage(false) 111 | ->addLine('سلام دنیا!') 112 | ->build(); 113 | 114 | header('content-type', 'image/png'); 115 | echo $imageContent; 116 | 117 | ``` 118 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quince/persian-gd", 3 | "description": "PHP GD library for Persian text support", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Behzad Shabani", 9 | "email": "behzad.shabani@gmail.com" 10 | } 11 | ], 12 | "minimum-stability": "stable", 13 | "autoload": { 14 | "psr-4": { 15 | "Quince\\PersianGD\\": "src" 16 | } 17 | }, 18 | "require": { 19 | "ext-gd": "*" 20 | }, 21 | "require-dev": { 22 | "styleci/cli": "^0.6.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Contracts/GDTool.php: -------------------------------------------------------------------------------- 1 | setOptions($options); 138 | } 139 | 140 | /** 141 | * Sets the image canvas width. 142 | * 143 | * @param int $width 144 | * 145 | * @return GDTool 146 | */ 147 | public function setWidth($width) 148 | { 149 | $this->width = $width; 150 | 151 | return $this; 152 | } 153 | 154 | /** 155 | * Sets the name of output image file. 156 | * 157 | * @param string $fileName 158 | * 159 | * @return GDTool 160 | */ 161 | public function setFileName($fileName) 162 | { 163 | $this->fileName = $fileName; 164 | 165 | return $this; 166 | } 167 | 168 | /** 169 | * Sets the condition of saving or outputting image. 170 | * 171 | * @param bool $outputImage 172 | * 173 | * @return GDTool 174 | */ 175 | public function setOutputImage($outputImage) 176 | { 177 | $this->outputImage = $outputImage; 178 | 179 | return $this; 180 | } 181 | 182 | /** 183 | * Sets background color hexadecimal code. 184 | * 185 | * @param string $backgroundColor 186 | * 187 | * @return GDTool 188 | */ 189 | public function setBackgroundColor($backgroundColor) 190 | { 191 | $this->backgroundColor = $backgroundColor; 192 | 193 | return $this; 194 | } 195 | 196 | /** 197 | * Sets the font to be used. 198 | * 199 | * @param string $font 200 | * 201 | * @return GDTool 202 | */ 203 | public function setFont($font) 204 | { 205 | $this->font = $font; 206 | 207 | return $this; 208 | } 209 | 210 | /** 211 | * Sets font color hexadecimal code. 212 | * 213 | * @param string $fontColor 214 | * 215 | * @return GDTool 216 | */ 217 | public function setFontColor($fontColor) 218 | { 219 | $this->fontColor = $fontColor; 220 | 221 | return $this; 222 | } 223 | 224 | /** 225 | * Sets font size. 226 | * 227 | * @param int $fontSize 228 | * 229 | * @return GDTool 230 | */ 231 | public function setFontSize($fontSize) 232 | { 233 | $this->fontSize = $fontSize; 234 | 235 | return $this; 236 | } 237 | 238 | /** 239 | * Sets height of each lines. 240 | * 241 | * @param int $lineHeight 242 | * 243 | * @return GDTool 244 | */ 245 | public function setLineHeight($lineHeight) 246 | { 247 | $this->lineHeight = $lineHeight; 248 | 249 | return $this; 250 | } 251 | 252 | /** 253 | * Sets the angle of the line. 254 | * 255 | * @param int $angle 256 | * 257 | * @return GDTool 258 | */ 259 | public function setAngle($angle) 260 | { 261 | $this->angle = $angle; 262 | 263 | return $this; 264 | } 265 | 266 | /** 267 | * Sets the horizontal position of the text. 268 | * 269 | * @param int $horizontalPosition 270 | * 271 | * @return GDTool 272 | */ 273 | public function setHorizontalPosition($horizontalPosition) 274 | { 275 | $this->horizontalPosition = $horizontalPosition; 276 | 277 | return $this; 278 | } 279 | 280 | /** 281 | * Sets the vertical position of the text. 282 | * 283 | * @param int $verticalPosition 284 | * 285 | * @return GDTool 286 | */ 287 | public function setVerticalPosition($verticalPosition) 288 | { 289 | $this->verticalPosition = $verticalPosition; 290 | 291 | return $this; 292 | } 293 | 294 | /** 295 | * Set whether using local (Persian) numeric character or not. 296 | * 297 | * @param bool $useLocalNumber 298 | * 299 | * @return GDTool 300 | */ 301 | public function setUseLocalNumber($useLocalNumber) 302 | { 303 | $this->useLocalNumber = $useLocalNumber; 304 | 305 | return $this; 306 | } 307 | 308 | /** 309 | * Sets the decorator. 310 | * 311 | * @param StringDecorator $decorator 312 | * 313 | * @return GDTool 314 | */ 315 | public function setDecorator(StringDecorator $decorator) 316 | { 317 | $this->decorator = $decorator; 318 | 319 | return $this; 320 | } 321 | 322 | /** 323 | * Sets class options. 324 | * 325 | * @param array $options 326 | * 327 | * @return GDTool 328 | */ 329 | public function setOptions(array $options) 330 | { 331 | foreach ($options as $option => $value) { 332 | if (in_array($option, $this->getAvailableOptions())) { 333 | $this->$option = $value; 334 | } 335 | } 336 | 337 | return $this; 338 | } 339 | 340 | /** 341 | * Add new string line to image. 342 | * 343 | * @param string $line 344 | * 345 | * @return GDTool 346 | */ 347 | public function addLine($line) 348 | { 349 | array_push($this->lines, $line); 350 | 351 | return $this; 352 | } 353 | 354 | /** 355 | * Add multiple line to the list of lines to be generated. 356 | * 357 | * @param array $lines 358 | * 359 | * @return GDTool 360 | */ 361 | public function addLines(array $lines) 362 | { 363 | $lines = array_filter($lines, function ($line) { 364 | return is_string($line); 365 | }); 366 | 367 | $this->lines = array_merge($this->lines, $lines); 368 | 369 | return $this; 370 | } 371 | 372 | /** 373 | * Generate requested image. 374 | * 375 | * 376 | * @return false|string 377 | */ 378 | public function build() 379 | { 380 | $this->initDecorator(); 381 | $this->initImage(); 382 | $this->generateColorAllocates(); 383 | $this->writeLines(); 384 | 385 | return $this->generate(); 386 | } 387 | 388 | /** 389 | * Get available options for class. 390 | * 391 | * 392 | * @return array 393 | */ 394 | protected function getAvailableOptions() 395 | { 396 | return array_keys(get_object_vars($this)); 397 | } 398 | 399 | /** 400 | * Initialize decorator. 401 | */ 402 | protected function initDecorator() 403 | { 404 | if (!isset($this->decorator) || is_null($this->decorator)) { 405 | $this->decorator = new PersianStringDecorator(); 406 | } 407 | } 408 | 409 | /** 410 | * Initialize image canvas. 411 | */ 412 | protected function initImage() 413 | { 414 | $this->imageResource = imagecreate($this->width, $this->getHeight()); 415 | } 416 | 417 | /** 418 | * Calculate and return the height of canvas. 419 | * 420 | * 421 | * @return int 422 | */ 423 | protected function getHeight() 424 | { 425 | $lineCounts = count($this->lines); 426 | 427 | return ($lineCounts + 1) * $this->lineHeight; 428 | } 429 | 430 | protected function generateColorAllocates() 431 | { 432 | $this->generateBackgroundAllocate(); 433 | $this->generateFontAllocate(); 434 | } 435 | 436 | protected function generateBackgroundAllocate() 437 | { 438 | list($red, $green, $blue) = $this->getRGBValues($this->backgroundColor); 439 | $this->backgroundColorAllocate = imagecolorallocate( 440 | $this->imageResource, 441 | $red, 442 | $green, 443 | $blue 444 | ); 445 | } 446 | 447 | protected function generateFontAllocate() 448 | { 449 | list($red, $green, $blue) = $this->getRGBValues($this->fontColor); 450 | $this->fontColorAllocate = imagecolorallocate( 451 | $this->imageResource, 452 | $red, 453 | $green, 454 | $blue 455 | ); 456 | } 457 | 458 | /** 459 | * Get RGB value of a hexadecimal color code. 460 | * 461 | * @param string $hexColor 462 | * 463 | * @throws PersianGDException 464 | * 465 | * @return array 466 | */ 467 | protected function getRGBValues($hexColor) 468 | { 469 | if (substr($hexColor, 0, 1) != '#') { 470 | throw new PersianGDException('Invalid hexadecimal color code provided'); 471 | } 472 | 473 | $hexColor = substr($hexColor, 1); 474 | 475 | if (strlen($hexColor) == 3) { 476 | $red = str_repeat(substr($hexColor, 0, 1), 2); 477 | $green = str_repeat(substr($hexColor, 1, 1), 2); 478 | $blue = str_repeat(substr($hexColor, 2, 1), 2); 479 | } else { 480 | if (strlen($hexColor) == 6) { 481 | $red = substr($hexColor, 0, 2); 482 | $green = substr($hexColor, 2, 2); 483 | $blue = substr($hexColor, 4, 2); 484 | } else { 485 | throw new PersianGDException('Invalid hexadecimal color code provided'); 486 | } 487 | } 488 | 489 | return [ 490 | hexdec($red), 491 | hexdec($green), 492 | hexdec($blue), 493 | ]; 494 | } 495 | 496 | protected function writeLines() 497 | { 498 | $verticalPos = $this->verticalPosition; 499 | foreach ($this->lines as $dir => $line) { 500 | imagettftext( 501 | $this->imageResource, 502 | $this->fontSize, 503 | $this->angle, 504 | $this->horizontalPosition, 505 | $verticalPos, 506 | $this->fontColorAllocate, 507 | $this->font, 508 | $this->decorator->decorate($line, $this->useLocalNumber) 509 | ); 510 | 511 | $verticalPos += $this->lineHeight; 512 | } 513 | } 514 | 515 | protected function generate() 516 | { 517 | if ($this->outputImage) { 518 | ob_start(); 519 | imagepng($this->imageResource); 520 | 521 | return ob_get_clean(); 522 | } 523 | 524 | imagepng($this->imageResource, $this->fileName); 525 | 526 | return $this->fileName; 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /src/PersianStringDecorator.php: -------------------------------------------------------------------------------- 1 | ['ﺂ', 'ﺂ', 'آ'], 16 | 'ا' => ['ﺎ', 'ﺎ', 'ا'], 17 | 'ب' => ['ﺐ', 'ﺒ', 'ﺑ'], 18 | 'پ' => ['ﭗ', 'ﭙ', 'ﭘ'], 19 | 'ت' => ['ﺖ', 'ﺘ', 'ﺗ'], 20 | 'ث' => ['ﺚ', 'ﺜ', 'ﺛ'], 21 | 'ج' => ['ﺞ', 'ﺠ', 'ﺟ'], 22 | 'چ' => ['ﭻ', 'ﭽ', 'ﭼ'], 23 | 'ح' => ['ﺢ', 'ﺤ', 'ﺣ'], 24 | 'خ' => ['ﺦ', 'ﺨ', 'ﺧ'], 25 | 'د' => ['ﺪ', 'ﺪ', 'ﺩ'], 26 | 'ذ' => ['ﺬ', 'ﺬ', 'ﺫ'], 27 | 'ر' => ['ﺮ', 'ﺮ', 'ﺭ'], 28 | 'ز' => ['ﺰ', 'ﺰ', 'ﺯ'], 29 | 'ژ' => ['ﮋ', 'ﮋ', 'ﮊ'], 30 | 'س' => ['ﺲ', 'ﺴ', 'ﺳ'], 31 | 'ش' => ['ﺶ', 'ﺸ', 'ﺷ'], 32 | 'ص' => ['ﺺ', 'ﺼ', 'ﺻ'], 33 | 'ض' => ['ﺾ', 'ﻀ', 'ﺿ'], 34 | 'ط' => ['ﻂ', 'ﻄ', 'ﻃ'], 35 | 'ظ' => ['ﻆ', 'ﻈ', 'ﻇ'], 36 | 'ع' => ['ﻊ', 'ﻌ', 'ﻋ'], 37 | 'غ' => ['ﻎ', 'ﻐ', 'ﻏ'], 38 | 'ف' => ['ﻒ', 'ﻔ', 'ﻓ'], 39 | 'ق' => ['ﻖ', 'ﻘ', 'ﻗ'], 40 | 'ک' => ['ﮏ', 'ﻜ', 'ﻛ'], 41 | 'گ' => ['ﮓ', 'ﮕ', 'ﮔ'], 42 | 'ل' => ['ﻞ', 'ﻠ', 'ﻟ'], 43 | 'م' => ['ﻢ', 'ﻤ', 'ﻣ'], 44 | 'ن' => ['ﻦ', 'ﻨ', 'ﻧ'], 45 | 'و' => ['ﻮ', 'ﻮ', 'ﻭ'], 46 | 'ه' => ['ﻪ', 'ﻬ', 'ﻫ'], 47 | 'ی' => ['ﯽ', 'ﯿ', 'ﯾ'], 48 | 'ك' => ['ﮏ', 'ﻜ', 'ﻛ'], 49 | 'ي' => ['ﻲ', 'ﻴ', 'ﻳ'], 50 | 'أ' => ['ﺄ', 'ﺄ', 'ﺃ'], 51 | 'ؤ' => ['ﺆ', 'ﺆ', 'ﺅ'], 52 | 'إ' => ['ﺈ', 'ﺈ', 'ﺇ'], 53 | 'ئ' => ['ﺊ', 'ﺌ', 'ﺋ'], 54 | 'ة' => ['ﺔ', 'ﺘ', 'ﺗ'], 55 | ]; 56 | 57 | /** 58 | * Character that no other character will joint after them. 59 | * 60 | * @var array 61 | */ 62 | protected $detachedChars = [ 63 | 'آ', 64 | 'ا', 65 | 'د', 66 | 'ذ', 67 | 'ر', 68 | 'ز', 69 | 'ژ', 70 | 'و', 71 | 'أ', 72 | 'إ', 73 | 'ؤ', 74 | ]; 75 | 76 | /** 77 | * Character to be ignore in processing. 78 | * 79 | * @var array 80 | */ 81 | protected $ignores = [ 82 | '', 83 | 'ٌ', 84 | 'ٍ', 85 | 'ً', 86 | 'ُ', 87 | 'ِ', 88 | 'َ', 89 | 'ّ', 90 | 'ٓ', 91 | 'ٰ', 92 | 'ٔ', 93 | 'ﹶ', 94 | 'ﹺ', 95 | 'ﹸ', 96 | 'ﹼ', 97 | 'ﹾ', 98 | 'ﹴ', 99 | 'ﹰ', 100 | 'ﱞ', 101 | 'ﱟ', 102 | 'ﱠ', 103 | 'ﱡ', 104 | 'ﱢ', 105 | 'ﱣ', 106 | ]; 107 | 108 | /** 109 | * Character used for enclosing. 110 | * 111 | * @var array 112 | */ 113 | protected $enclosingChars = [ 114 | '>', 115 | ')', 116 | '}', 117 | ']', 118 | '<', 119 | '(', 120 | '{', 121 | '[', 122 | ]; 123 | 124 | /** 125 | * Enclosing character map for reversing. 126 | * 127 | * @var array 128 | */ 129 | protected $enclosingMap = [ 130 | ')' => '(', 131 | '(' => ')', 132 | '}' => '{', 133 | '{' => '}', 134 | ']' => '[', 135 | '[' => ']', 136 | '>' => '<', 137 | '<' => '>', 138 | ]; 139 | 140 | /** 141 | * English characters. 142 | * 143 | * @var array 144 | */ 145 | protected $englishChars = [ 146 | 'a', 147 | 'b', 148 | 'c', 149 | 'd', 150 | 'e', 151 | 'f', 152 | 'g', 153 | 'h', 154 | 'i', 155 | 'j', 156 | 'k', 157 | 'l', 158 | 'm', 159 | 'n', 160 | 'o', 161 | 'p', 162 | 'q', 163 | 'r', 164 | 's', 165 | 't', 166 | 'u', 167 | 'v', 168 | 'w', 169 | 'x', 170 | 'y', 171 | 'z', 172 | ]; 173 | 174 | /** 175 | * Numbers character. 176 | * 177 | * @var array 178 | */ 179 | protected $numbers = [ 180 | '٠', 181 | '١', 182 | '٢', 183 | '٣', 184 | '۴', 185 | '۵', 186 | '۶', 187 | '٧', 188 | '٨', 189 | '٩', 190 | '۴', 191 | '۵', 192 | '۶', 193 | '٤', 194 | '٥', 195 | '٦', 196 | '0', 197 | '1', 198 | '2', 199 | '3', 200 | '4', 201 | '5', 202 | '6', 203 | '7', 204 | '8', 205 | '9', 206 | ]; 207 | 208 | /** 209 | * Number character map to convert. 210 | * 211 | * @var array 212 | */ 213 | protected $numberMap = [ 214 | '0' => '۰', 215 | '1' => '۱', 216 | '2' => '۲', 217 | '3' => '۳', 218 | '4' => '۴', 219 | '5' => '۵', 220 | '6' => '۶', 221 | '7' => '۷', 222 | '8' => '۸', 223 | '9' => '۹', 224 | ]; 225 | 226 | /** 227 | * Persian symbols. 228 | * 229 | * @var array 230 | */ 231 | protected $persianSymbols = [ 232 | '،', 233 | '؟', 234 | 'ء', 235 | ]; 236 | 237 | /** 238 | * Array of string character to be iterate. 239 | * 240 | * @var array 241 | */ 242 | protected $iterative = []; 243 | 244 | /** 245 | * Current character. 246 | * 247 | * @var string 248 | */ 249 | protected $current = null; 250 | 251 | /** 252 | * Next character. 253 | * 254 | * @var string 255 | */ 256 | protected $next = null; 257 | 258 | /** 259 | * Previous character. 260 | * 261 | * @var string 262 | */ 263 | protected $prev = null; 264 | 265 | /** 266 | * The output result. 267 | * 268 | * @var string 269 | */ 270 | protected $output = ''; 271 | 272 | /** 273 | * The numbers output. 274 | * 275 | * @var string 276 | */ 277 | protected $numberOutput = ''; 278 | 279 | /** 280 | * The english output. 281 | * 282 | * @var string 283 | */ 284 | protected $englishOutput = ''; 285 | 286 | /** 287 | * @var string 288 | */ 289 | protected $eOutput = ''; 290 | 291 | /** 292 | * Decorate given (persian) string and prepare it for gd. 293 | * 294 | * @param string $string 295 | * @param bool $persianNumbers 296 | * @param bool $rtl 297 | * 298 | * @return string 299 | */ 300 | public function decorate($string, $persianNumbers = true, $rtl = true) 301 | { 302 | $this->setStringIterative($string); 303 | 304 | for ($i = 0; $i < $this->stringLength(); $i++) { 305 | $this->setPointers($i); 306 | $this->process($i, $rtl, $persianNumbers); 307 | } 308 | 309 | if ($this->englishOutput != '') { 310 | $this->prepend($this->englishOutput); 311 | } 312 | 313 | return $this->output; 314 | } 315 | 316 | /** 317 | * Set the characters pointer. 318 | * 319 | * @param int $index 320 | */ 321 | protected function setPointers($index) 322 | { 323 | $this->setCurrent($this->getChar($index)); 324 | 325 | // Check if the next character is in ignore list 326 | if ($this->shouldBeIgnored($this->getChar($index + 1))) { 327 | $this->setNext($this->getChar($index + 2)); 328 | 329 | if ($index == 2) { 330 | $this->setPrev($this->getChar($index - 2)); 331 | } 332 | if ($index != 2) { 333 | $this->setPrev($this->getChar($index - 1)); 334 | } 335 | } // Check if previous character not in ignore list 336 | elseif (!$this->shouldBeIgnored($this->getChar($index - 1))) { 337 | $this->setNext($this->getChar($index + 1)); 338 | if ($index != 0) { 339 | $this->setPrev($this->getChar($index - 1)); 340 | } 341 | } else { 342 | if (!is_null($this->getChar($index + 1))) { 343 | $this->setNext($this->getChar($index + 1)); 344 | } else { 345 | $this->setNext($this->getChar($index - 1)); 346 | } 347 | 348 | if ($index != 0) { 349 | $this->setPrev($this->getChar($index - 2)); 350 | } 351 | } 352 | } 353 | 354 | /** 355 | * Process the character in given index. 356 | * 357 | * @param int $index 358 | * @param bool $rtl 359 | * @param bool $persianNumber 360 | */ 361 | protected function process($index, $rtl, $persianNumber) 362 | { 363 | if (!$this->shouldBeIgnored($this->current)) { 364 | if ($this->isPersianChar($this->current)) { 365 | $this->processPersianWord(); 366 | } elseif ($rtl) { 367 | $this->processNonPersianWord($index, $persianNumber); 368 | } else { 369 | $this->processSymbolsAndOtherChars(); 370 | } 371 | } else { 372 | $this->prepend($this->current); 373 | } 374 | 375 | $this->resetPointers(); 376 | } 377 | 378 | /** 379 | * Set current character. 380 | * 381 | * @param string $current 382 | */ 383 | protected function setCurrent($current) 384 | { 385 | $this->current = $current; 386 | } 387 | 388 | /** 389 | * Set the next character. 390 | * 391 | * @param string $next 392 | */ 393 | protected function setNext($next) 394 | { 395 | $this->next = $next; 396 | } 397 | 398 | /** 399 | * Set previous character. 400 | * 401 | * @param string $prev 402 | */ 403 | protected function setPrev($prev) 404 | { 405 | $this->prev = $prev; 406 | } 407 | 408 | protected function processPersianWord() 409 | { 410 | if (is_null($this->prev) || $this->prev == ' ' || !$this->isPersianChar($this->prev)) { 411 | $this->processPersianWordFirstChar(); 412 | } elseif ($this->isPersianChar($this->prev) && $this->isPersianChar($this->next)) { 413 | $this->processPersianWordMiddleChar(); 414 | } elseif ($this->isPersianChar($this->prev) && !$this->isPersianChar($this->next)) { 415 | $this->processPersianWordLastChar(); 416 | } 417 | } 418 | 419 | /** 420 | * @return bool 421 | */ 422 | protected function processPersianWordFirstChar() 423 | { 424 | // Next and previous character are not persian characters 425 | if (!$this->isPersianChar($this->next) && !$this->isPersianChar($this->prev)) { 426 | $this->prepend($this->current); 427 | } else { 428 | $this->prepend($this->getFirstJoint($this->current)); 429 | } 430 | 431 | // continue the parent loop 432 | return true; 433 | } 434 | 435 | /** 436 | * @return bool 437 | */ 438 | protected function processPersianWordMiddleChar() 439 | { 440 | if ($this->isDetachedChar($this->prev) && $this->isPersianChar($this->next)) { 441 | $this->prepend($this->getFirstJoint($this->current)); 442 | } else { 443 | $this->prepend($this->getMiddleJoint($this->current)); 444 | } 445 | 446 | // continue the parent loop 447 | return true; 448 | } 449 | 450 | /** 451 | * @return bool 452 | */ 453 | protected function processPersianWordLastChar() 454 | { 455 | if ($this->isDetachedChar($this->prev)) { 456 | $this->prepend($this->current); 457 | } else { 458 | $this->prepend($this->getLastJoint($this->current)); 459 | } 460 | 461 | // continue the parent loop 462 | return true; 463 | } 464 | 465 | /** 466 | * @param $index 467 | * @param $persianNumber 468 | */ 469 | protected function processNonPersianWord($index, $persianNumber) 470 | { 471 | // Check if current character is an enclosing character 472 | if ($this->isEnclosingChars($this->current)) { 473 | $this->reverseEnclosing($this->current); 474 | } // Check if current character is a number 475 | 476 | if ($this->isNumber($this->current)) { 477 | $this->processNumber($persianNumber); 478 | } 479 | 480 | if (!$this->isNumber($this->next)) { 481 | if ( 482 | $this->isEnglishChar($this->current) || 483 | ($this->isSpaceOrDot($this->current) && $this->englishOutput != '' && 484 | !$this->isPersianChar($this->next)) 485 | ) { 486 | $this->englishOutput .= $this->current.$this->numberOutput; 487 | 488 | $this->setCurrent(''); 489 | } else { 490 | if ($this->englishOutput != '') { 491 | if ($index + 1 == $this->stringLength()) { 492 | $this->setCurrent($this->current.$this->numberOutput); 493 | } else { 494 | $this->englishOutput .= $this->current.$this->numberOutput; 495 | } 496 | } else { 497 | $this->setCurrent($this->current.$this->numberOutput); 498 | } 499 | } 500 | 501 | $this->numberOutput = ''; 502 | } 503 | 504 | if ($this->englishOutput != '' || $this->isFirstLoop($index)) { 505 | if (!$this->isPersianChar($this->current)) { 506 | if ( 507 | !$this->isPersianChar($this->next) && $this->next != ' ' && 508 | !$this->isEnclosingChars($this->next) 509 | ) { 510 | $this->englishOutput .= $this->current; 511 | } else { 512 | if ($this->isEnglishChar($this->getChar($index + 2))) { 513 | $this->englishOutput .= $this->current; 514 | } else { 515 | if ( 516 | $this->next == ' ' && 517 | ($this->isNumber($this->getChar($index + 2)) || 518 | $this->isEnglishChar($this->getChar($index + 2))) 519 | ) { 520 | $this->englishOutput .= $this->current; 521 | } else { 522 | $this->prepend($this->englishOutput); 523 | $this->englishOutput = ''; 524 | } 525 | } 526 | } 527 | } else { 528 | if ($this->numberOutput) { 529 | $this->englishOutput .= $this->numberOutput; 530 | } else { 531 | $this->prepend($this->englishOutput.$this->current); 532 | $this->englishChars = ''; 533 | } 534 | } 535 | } else { 536 | if ( 537 | $this->isNumber($this->current) && $this->next == '.' && 538 | $this->isNumber($this->getChar($index + 2)) 539 | ) { 540 | $this->englishOutput = $this->current; 541 | } else { 542 | $this->prepend($this->current); 543 | } 544 | } 545 | } 546 | 547 | protected function processSymbolsAndOtherChars() 548 | { 549 | if ( 550 | $this->isPersianSymbols($this->current) || 551 | ($this->isPersianChar($this->prev) && $this->isPersianChar($this->next)) || 552 | ($this->current == ' ' && $this->isPersianChar($this->next)) || 553 | ($this->current == ' ' && $this->isPersianChar($this->prev)) 554 | ) { 555 | if ($this->eOutput) { 556 | $this->prepend($this->eOutput); 557 | $this->eOutput = ''; 558 | } 559 | 560 | $this->prepend($this->current); 561 | } else { 562 | $this->eOutput .= $this->current; 563 | 564 | if ($this->isPersianChar($this->next) || $this->next == '') { 565 | $this->prepend($this->eOutput); 566 | $this->eOutput = ''; 567 | } 568 | } 569 | } 570 | 571 | /** 572 | * @param $persianNumber 573 | */ 574 | protected function processNumber($persianNumber) 575 | { 576 | if ($persianNumber) { 577 | $this->numberOutput .= $this->persianizeNumbers($this->current); 578 | } else { 579 | $this->numberOutput = $this->current; 580 | } 581 | 582 | $this->setCurrent(''); 583 | } 584 | 585 | /** 586 | * Set the iterative string. 587 | * 588 | * @param string $string 589 | */ 590 | protected function setStringIterative($string) 591 | { 592 | preg_match_all('/./u', $string, $match); 593 | $this->iterative = $match[0]; 594 | } 595 | 596 | /** 597 | * return count of string characters. 598 | * 599 | * @return int 600 | */ 601 | protected function stringLength() 602 | { 603 | return count($this->iterative); 604 | } 605 | 606 | /** 607 | * Get character with given index. 608 | * 609 | * @param int $index 610 | * 611 | * @return string 612 | */ 613 | protected function getChar($index) 614 | { 615 | if (isset($this->iterative[$index])) { 616 | return $this->iterative[$index]; 617 | } 618 | 619 | return null; 620 | } 621 | 622 | /** 623 | * Check if character is in ignore list. 624 | * 625 | * @param $char 626 | * 627 | * @return bool 628 | */ 629 | protected function shouldBeIgnored($char) 630 | { 631 | return in_array($char, $this->ignores); 632 | } 633 | 634 | /** 635 | * Check if character is a persian character. 636 | * 637 | * @param string $char 638 | * 639 | * @return bool 640 | */ 641 | protected function isPersianChar($char) 642 | { 643 | return array_key_exists($char, $this->persianChars); 644 | } 645 | 646 | /** 647 | * Check if character is a detached character. 648 | * 649 | * @param string $char 650 | * 651 | * @return bool 652 | */ 653 | protected function isDetachedChar($char) 654 | { 655 | return in_array($char, $this->detachedChars); 656 | } 657 | 658 | /** 659 | * Check if character is a enclosing character. 660 | * 661 | * @param string $char 662 | * 663 | * @return bool 664 | */ 665 | protected function isEnclosingChars($char) 666 | { 667 | return in_array($char, $this->enclosingChars); 668 | } 669 | 670 | /** 671 | * Check if character is a number. 672 | * 673 | * @param string $char 674 | * 675 | * @return bool 676 | */ 677 | protected function isNumber($char) 678 | { 679 | return in_array($char, $this->numbers); 680 | } 681 | 682 | /** 683 | * Check if character is an english character. 684 | * 685 | * @param string $char 686 | * 687 | * @return bool 688 | */ 689 | protected function isEnglishChar($char) 690 | { 691 | return in_array(strtolower($char), $this->englishChars); 692 | } 693 | 694 | /** 695 | * Check if character is space or dot. 696 | * 697 | * @param string $char 698 | * 699 | * @return bool 700 | */ 701 | protected function isSpaceOrDot($char) 702 | { 703 | return $char == ' ' || $char == '.'; 704 | } 705 | 706 | /** 707 | * Check if we are in first loop and current character is not empty 708 | * and next character is nor persian neither space. 709 | * 710 | * @param int $index 711 | * 712 | * @return bool 713 | */ 714 | protected function isFirstLoop($index) 715 | { 716 | return $this->current != '' && $index == 0 && (!$this->isPersianChar($this->next) && $this->next != ' '); 717 | } 718 | 719 | /** 720 | * Check if given character. 721 | * 722 | * @param string $char 723 | * 724 | * @return bool 725 | */ 726 | protected function isPersianSymbols($char) 727 | { 728 | return in_array($char, $this->persianSymbols); 729 | } 730 | 731 | /** 732 | * Prepend given string to output. 733 | * 734 | * @param string $char 735 | */ 736 | protected function prepend($char) 737 | { 738 | $this->output = $char.$this->output; 739 | } 740 | 741 | /** 742 | * Get first joint symbol of given character. 743 | * 744 | * @param string $char 745 | * 746 | * @return string 747 | */ 748 | protected function getFirstJoint($char) 749 | { 750 | if (isset($this->persianChars[$char])) { 751 | return $this->persianChars[$char][2]; 752 | } 753 | 754 | return $char; 755 | } 756 | 757 | /** 758 | * Get middle joint symbol of given character. 759 | * 760 | * @param string $char 761 | * 762 | * @return string 763 | */ 764 | protected function getMiddleJoint($char) 765 | { 766 | if (isset($this->persianChars[$char])) { 767 | return $this->persianChars[$char][1]; 768 | } 769 | 770 | return $char; 771 | } 772 | 773 | /** 774 | * Get last joint symbol of given character. 775 | * 776 | * @param string $char 777 | * 778 | * @return string 779 | */ 780 | protected function getLastJoint($char) 781 | { 782 | if (isset($this->persianChars[$char])) { 783 | return $this->persianChars[$char][0]; 784 | } 785 | 786 | return $char; 787 | } 788 | 789 | /** 790 | * Reverse current enclosing character. 791 | * 792 | * @param string $char 793 | */ 794 | protected function reverseEnclosing($char) 795 | { 796 | $this->setCurrent($this->enclosingMap[$char]); 797 | } 798 | 799 | /** 800 | * Convert english numbers to persian one. 801 | * 802 | * @param string $num 803 | * 804 | * @return string 805 | */ 806 | protected function persianizeNumbers($num) 807 | { 808 | if (array_key_exists($num, $this->numberMap)) { 809 | return $this->numberMap[$num]; 810 | } 811 | 812 | return $num; 813 | } 814 | 815 | protected function resetPointers() 816 | { 817 | $this->prev = null; 818 | $this->next = null; 819 | } 820 | } 821 | --------------------------------------------------------------------------------