Вход Регистрация
Файл: vendor/visavi/captcha/src/CaptchaBuilder.php
Строк: 326
<?php

declare(strict_types=1);

namespace 
VisaviCaptcha;

use 
Exception;
use 
GdImage;
use 
RuntimeException;

class 
CaptchaBuilder
{
    protected ?array 
$params null;
    protected 
string $phrase;
    protected 
int $width 150;
    protected 
int $height 40;
    protected ?
string $font null;
    protected ?array 
$textColor null;
    protected ?array 
$backgroundColor null;
    protected 
int $windowWidth 75;
    protected 
int $pixelPerFrame 15;
    protected 
int $delayBetweenFrames 20;

    
/**
     * @throws Exception
     */
    
public function __construct(?string $phrase null)
    {
        if (
$phrase) {
            
$this->phrase $phrase;
        } else {
            
$phraseBuilder = new PhraseBuilder();
            
$this->phrase $phraseBuilder->getPhrase(random_int(46));
        }
    }

    
/**
     * Get phrase
     */
    
public function getPhrase(): string
    
{
        return 
$this->phrase;
    }

    
/**
     * Set text color
     */
    
public function setTextColor(int $rint $gint $b): self
    
{
        
$this->textColor = [$r$g$b];
        
$this->params null;

        return 
$this;
    }

    
/**
     * Set background color
     */
    
public function setBackgroundColor(int $rint $gint $b): self
    
{
        
$this->backgroundColor = [$r$g$b];
        
$this->params null;

        return 
$this;
    }

    
/**
     * Set image width
     */
    
public function setWidth(int $width): self
    
{
        
$this->width $width;
        
$this->params null;

        return 
$this;
    }

    
/**
     * Set image height
     */
    
public function setHeight(int $height): self
    
{
        
$this->height $height;
        
$this->params null;

        return 
$this;
    }

    
/**
     * Set window width
     */
    
public function setWindowWidth(int $width): self
    
{
        
$this->windowWidth $width;
        
$this->params null;

        return 
$this;
    }

    
/**
     * Set pixel per frame
     */
    
public function setPixelPerFrame(int $pixel): self
    
{
        
$this->pixelPerFrame $pixel;

        return 
$this;
    }

    
/**
     * Set delay between frames (in hundredths of a second, per GIF spec)
     */
    
public function setDelayBetweenFrames(int $delay): self
    
{
        
$this->delayBetweenFrames $delay;

        return 
$this;
    }

    
/**
     * Set font
     */
    
public function setFont(string $path): self
    
{
        
$this->font $path;
        
$this->params null;

        return 
$this;
    }

    
/**
     * Render captcha
     *
     * @throws RuntimeException|Exception
     */
    
public function render(): string
    
{
        
$frames $this->getFrames();
        
$delays array_fill(0count($frames), $this->delayBetweenFrames);

        return (new 
GifEncoder($frames$delays02))->getAnimation();
    }

    
/**
     * Get captcha inline
     *
     * @throws RuntimeException|Exception
     */
    
public function inline(): string
    
{
        return 
'data:image/gif;base64,' base64_encode($this->render());
    }

    
/**
     * Returns gif frames
     *
     * @throws RuntimeException|Exception
     */
    
public function getFrames(): array
    {
        
$frames = [];
        
$params $this->getImageParams();
        
$base $this->getBaseImage();

        
$window $params['window'];

        
// Even out the step so the last frame wraps back onto the first one
        
$count max((int) round($this->width $this->pixelPerFrame), 1);

        for (
$frame 0$frame $count$frame++) {
            
// Start at the left edge of the text, so the first frame is never blank
            
$shift $params['x'] + (int) round($frame $this->width $count);
            
$offset = (($shift $this->width) + $this->width) % $this->width;

            
$image imagecreatetruecolor($this->width$this->height);

            
$backgroundColor $this->createColor($image$params['backgroundColor']);
            
imagefilledrectangle($image00$this->width$this->height$backgroundColor);

            foreach (
$this->getVisibleParts($offset$window) as [$from$to]) {
                
imagecopy($image$base$from0$from0$to $from$this->height);
            }

            
$this->applyEffect($image$params);

            
$frames[] = $this->getImageContent($image);
        }

        return 
$frames;
    }

    
/**
     * Resolve the font path: a custom one if set, otherwise a random bundled font
     *
     * @throws RuntimeException|Exception
     */
    
protected function resolveFont(): string
    
{
        if (
$this->font !== null) {
            if (! 
is_file($this->font)) {
                throw new 
RuntimeException('Font file not found: ' $this->font);
            }

            return 
$this->font;
        }

        
$fonts glob(__DIR__ '/../fonts/*.ttf') ?: [];

        if (! 
$fonts) {
            throw new 
RuntimeException('No bundled fonts found in ' __DIR__ '/../fonts');
        }

        return 
$fonts[random_int(0count($fonts) - 1)];
    }

    
/**
     * Get image params
     *
     * @throws RuntimeException|Exception
     */
    
protected function getImageParams(): array
    {
        if (
$this->params === null) {
            
$params = [];
            
$characters $this->getCharacters();

            
$params['font'] = $this->resolveFont();
            
$params['size'] = $this->width max(count($characters), 5);

            
$box imagettfbbox($params['size'], 0$params['font'], $this->phrase);

            
$params['textWidth'] = $box[2] - $box[0];
            
$params['textHeight'] = abs($box[7] + $box[1]);

            
$params['x'] = (int) (($this->width $params['textWidth']) / 2);
            
$params['y'] = (int) (($this->height $params['textHeight']) / 2);

            
$params['window'] = $this->resolveWindowWidth($params);

            
$params['textColor'] = $this->textColor ?? [random_int(0150), random_int(0150), random_int(0150)];
            
$params['backgroundColor'] = $this->backgroundColor ?? [random_int(200255), random_int(200255), random_int(200255)];

            
$params['negate'] = random_int(01);

            
$this->params $params;
        }

        return 
$this->params;
    }

    
/**
     * Parts of the image the window shows at the given offset, wrapping around the right edge
     */
    
protected function getVisibleParts(int $offsetint $window): array
    {
        
$head min($window$this->width $offset);
        
$parts = [[$offset$offset $head]];

        if (
$window $head) {
            
$parts[] = [0$window $head];
        }

        return 
$parts;
    }

    
/**
     * Split the phrase into characters, multibyte aware
     */
    
protected function getCharacters(): array
    {
        return 
preg_split('//u'$this->phrase, -1PREG_SPLIT_NO_EMPTY) ?: [];
    }

    
/**
     * Horizontal bounds of every character of the phrase
     */
    
protected function getCharacterRanges(array $params): array
    {
        
$characters $this->getCharacters();
        
$ranges = [];
        
$start 0;

        for (
$i 0$length count($characters); $i $length$i++) {
            
$prefix implode(''array_slice($characters0$i 1));
            
$box imagettfbbox($params['size'], 0$params['font'], $prefix);
            
$end $box[2] - $box[0];

            
$ranges[] = [$params['x'] + $start$params['x'] + $end];
            
$start $end;
        }

        return 
$ranges;
    }

    
/**
     * Shrink the window until no frame can show the whole phrase at once
     */
    
protected function resolveWindowWidth(array $params): int
    
{
        
$ranges $this->getCharacterRanges($params);
        
$window min($this->windowWidth$this->width);

        while (
$window && ! $this->hidesCharacter($window$ranges)) {
            
$window--;
        }

        return 
$window;
    }

    
/**
     * Whether a window of the given width keeps at least one character fully hidden in every frame
     */
    
protected function hidesCharacter(int $window, array $ranges): bool
    
{
        for (
$offset 0$offset $this->width$offset++) {
            
$parts $this->getVisibleParts($offset$window);
            
$hidden false;

            foreach (
$ranges as [$start$end]) {
                
$visible false;

                foreach (
$parts as [$from$to]) {
                    if (
$start $to && $end $from) {
                        
$visible true;
                        break;
                    }
                }

                if (! 
$visible) {
                    
$hidden true;
                    break;
                }
            }

            if (! 
$hidden) {
                return 
false;
            }
        }

        return 
true;
    }

    
/**
     * Apply some post effects
     */
    
protected function applyEffect(GdImage $image, array $params): void
    
{
        if (! 
function_exists('imagefilter')) {
            return;
        }

        if (
$this->backgroundColor || $this->textColor) {
            return;
        }

        if (
$params['negate'] === 1) {
            
imagefilter($imageIMG_FILTER_NEGATE);
        }
    }

    
/**
     * Create a base image with the text
     *
     * @throws RuntimeException|Exception
     */
    
protected function getBaseImage(): GdImage
    
{
        
$params $this->getImageParams();
        
$image imagecreatetruecolor($this->width$this->height);

        
// Background
        
$backgroundColor $this->createColor($image$params['backgroundColor']);
        
imagefilledrectangle($image00$this->width$this->height$backgroundColor);

        
// Text
        
$textColor $this->createColor($image$params['textColor']);
        
imagettftext($image$params['size'], 0$params['x'], $params['y'], $textColor$params['font'], $this->phrase);

        return 
$image;
    }

    
/**
     * Create color
     */
    
protected function createColor(GdImage $image, array $color): bool|int
    
{
        return 
imagecolorallocate($image$color[0], $color[1], $color[2]);
    }

    
/**
     * Get image content
     */
    
protected function getImageContent(GdImage $image): bool|string
    
{
        
ob_start();
        
imagegif($image);

        return 
ob_get_clean();
    }
}
Онлайн: 0
Реклама