| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 | <?phpdeclare(strict_types = 1);namespace BaconQrCode\Renderer\Image;use BaconQrCode\Exception\RuntimeException;use BaconQrCode\Renderer\Color\Alpha;use BaconQrCode\Renderer\Color\Cmyk;use BaconQrCode\Renderer\Color\ColorInterface;use BaconQrCode\Renderer\Color\Gray;use BaconQrCode\Renderer\Color\Rgb;use BaconQrCode\Renderer\Path\Close;use BaconQrCode\Renderer\Path\Curve;use BaconQrCode\Renderer\Path\EllipticArc;use BaconQrCode\Renderer\Path\Line;use BaconQrCode\Renderer\Path\Move;use BaconQrCode\Renderer\Path\Path;use BaconQrCode\Renderer\RendererStyle\Gradient;use BaconQrCode\Renderer\RendererStyle\GradientType;final class EpsImageBackEnd implements ImageBackEndInterface{    private const PRECISION = 3;    /**     * @var string|null     */    private $eps;    public function new(int $size, ColorInterface $backgroundColor) : void    {        $this->eps = "%!PS-Adobe-3.0 EPSF-3.0\n"            . "%%Creator: BaconQrCode\n"            . sprintf("%%%%BoundingBox: 0 0 %d %d \n", $size, $size)            . "%%BeginProlog\n"            . "save\n"            . "50 dict begin\n"            . "/q { gsave } bind def\n"            . "/Q { grestore } bind def\n"            . "/s { scale } bind def\n"            . "/t { translate } bind def\n"            . "/r { rotate } bind def\n"            . "/n { newpath } bind def\n"            . "/m { moveto } bind def\n"            . "/l { lineto } bind def\n"            . "/c { curveto } bind def\n"            . "/z { closepath } bind def\n"            . "/f { eofill } bind def\n"            . "/rgb { setrgbcolor } bind def\n"            . "/cmyk { setcmykcolor } bind def\n"            . "/gray { setgray } bind def\n"            . "%%EndProlog\n"            . "1 -1 s\n"            . sprintf("0 -%d t\n", $size);        if ($backgroundColor instanceof Alpha && 0 === $backgroundColor->getAlpha()) {            return;        }        $this->eps .= wordwrap(            '0 0 m'            . sprintf(' %s 0 l', (string) $size)            . sprintf(' %s %s l', (string) $size, (string) $size)            . sprintf(' 0 %s l', (string) $size)            . ' z'            . ' ' .$this->getColorSetString($backgroundColor) . " f\n",            75,            "\n "        );    }    public function scale(float $size) : void    {        if (null === $this->eps) {            throw new RuntimeException('No image has been started');        }        $this->eps .= sprintf("%1\$s %1\$s s\n", round($size, self::PRECISION));    }    public function translate(float $x, float $y) : void    {        if (null === $this->eps) {            throw new RuntimeException('No image has been started');        }        $this->eps .= sprintf("%s %s t\n", round($x, self::PRECISION), round($y, self::PRECISION));    }    public function rotate(int $degrees) : void    {        if (null === $this->eps) {            throw new RuntimeException('No image has been started');        }        $this->eps .= sprintf("%d r\n", $degrees);    }    public function push() : void    {        if (null === $this->eps) {            throw new RuntimeException('No image has been started');        }        $this->eps .= "q\n";    }    public function pop() : void    {        if (null === $this->eps) {            throw new RuntimeException('No image has been started');        }        $this->eps .= "Q\n";    }    public function drawPathWithColor(Path $path, ColorInterface $color) : void    {        if (null === $this->eps) {            throw new RuntimeException('No image has been started');        }        $fromX = 0;        $fromY = 0;        $this->eps .= wordwrap(            'n '            . $this->drawPathOperations($path, $fromX, $fromY)            . ' ' . $this->getColorSetString($color) . " f\n",            75,            "\n "        );    }    public function drawPathWithGradient(        Path $path,        Gradient $gradient,        float $x,        float $y,        float $width,        float $height    ) : void {        if (null === $this->eps) {            throw new RuntimeException('No image has been started');        }        $fromX = 0;        $fromY = 0;        $this->eps .= wordwrap(            'q n ' . $this->drawPathOperations($path, $fromX, $fromY) . "\n",            75,            "\n "        );        $this->createGradientFill($gradient, $x, $y, $width, $height);    }    public function done() : string    {        if (null === $this->eps) {            throw new RuntimeException('No image has been started');        }        $this->eps .= "%%TRAILER\nend restore\n%%EOF";        $blob = $this->eps;        $this->eps = null;        return $blob;    }    private function drawPathOperations(Iterable $ops, &$fromX, &$fromY) : string    {        $pathData = [];        foreach ($ops as $op) {            switch (true) {                case $op instanceof Move:                    $fromX = $toX = round($op->getX(), self::PRECISION);                    $fromY = $toY = round($op->getY(), self::PRECISION);                    $pathData[] = sprintf('%s %s m', $toX, $toY);                    break;                case $op instanceof Line:                    $fromX = $toX = round($op->getX(), self::PRECISION);                    $fromY = $toY = round($op->getY(), self::PRECISION);                    $pathData[] = sprintf('%s %s l', $toX, $toY);                    break;                case $op instanceof EllipticArc:                    $pathData[] = $this->drawPathOperations($op->toCurves($fromX, $fromY), $fromX, $fromY);                    break;                case $op instanceof Curve:                    $x1 = round($op->getX1(), self::PRECISION);                    $y1 = round($op->getY1(), self::PRECISION);                    $x2 = round($op->getX2(), self::PRECISION);                    $y2 = round($op->getY2(), self::PRECISION);                    $fromX = $x3 = round($op->getX3(), self::PRECISION);                    $fromY = $y3 = round($op->getY3(), self::PRECISION);                    $pathData[] = sprintf('%s %s %s %s %s %s c', $x1, $y1, $x2, $y2, $x3, $y3);                    break;                case $op instanceof Close:                    $pathData[] = 'z';                    break;                default:                    throw new RuntimeException('Unexpected draw operation: ' . get_class($op));            }        }        return implode(' ', $pathData);    }    private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : void    {        $startColor = $gradient->getStartColor();        $endColor = $gradient->getEndColor();        if ($startColor instanceof Alpha) {            $startColor = $startColor->getBaseColor();        }        $startColorType = get_class($startColor);        if (! in_array($startColorType, [Rgb::class, Cmyk::class, Gray::class])) {            $startColorType = Cmyk::class;            $startColor = $startColor->toCmyk();        }        if (get_class($endColor) !== $startColorType) {            switch ($startColorType) {                case Cmyk::class:                    $endColor = $endColor->toCmyk();                    break;                case Rgb::class:                    $endColor = $endColor->toRgb();                    break;                case Gray::class:                    $endColor = $endColor->toGray();                    break;            }        }        $this->eps .= "eoclip\n<<\n";        if ($gradient->getType() === GradientType::RADIAL()) {            $this->eps .= " /ShadingType 3\n";        } else {            $this->eps .= " /ShadingType 2\n";        }        $this->eps .= " /Extend [ true true ]\n"            . " /AntiAlias true\n";        switch ($startColorType) {            case Cmyk::class:                $this->eps .= " /ColorSpace /DeviceCMYK\n";                break;            case Rgb::class:                $this->eps .= " /ColorSpace /DeviceRGB\n";                break;            case Gray::class:                $this->eps .= " /ColorSpace /DeviceGray\n";                break;        }        switch ($gradient->getType()) {            case GradientType::HORIZONTAL():                $this->eps .= sprintf(                    " /Coords [ %s %s %s %s ]\n",                    round($x, self::PRECISION),                    round($y, self::PRECISION),                    round($x + $width, self::PRECISION),                    round($y, self::PRECISION)                );                break;            case GradientType::VERTICAL():                $this->eps .= sprintf(                    " /Coords [ %s %s %s %s ]\n",                    round($x, self::PRECISION),                    round($y, self::PRECISION),                    round($x, self::PRECISION),                    round($y + $height, self::PRECISION)                );                break;            case GradientType::DIAGONAL():                $this->eps .= sprintf(                    " /Coords [ %s %s %s %s ]\n",                    round($x, self::PRECISION),                    round($y, self::PRECISION),                    round($x + $width, self::PRECISION),                    round($y + $height, self::PRECISION)                );                break;            case GradientType::INVERSE_DIAGONAL():                $this->eps .= sprintf(                    " /Coords [ %s %s %s %s ]\n",                    round($x, self::PRECISION),                    round($y + $height, self::PRECISION),                    round($x + $width, self::PRECISION),                    round($y, self::PRECISION)                );                break;            case GradientType::RADIAL():                $centerX = ($x + $width) / 2;                $centerY = ($y + $height) / 2;                $this->eps .= sprintf(                    " /Coords [ %s %s 0 %s %s %s ]\n",                    round($centerX, self::PRECISION),                    round($centerY, self::PRECISION),                    round($centerX, self::PRECISION),                    round($centerY, self::PRECISION),                    round(max($width, $height) / 2, self::PRECISION)                );                break;        }        $this->eps .= " /Function\n"            . " <<\n"            . "  /FunctionType 2\n"            . "  /Domain [ 0 1 ]\n"            . sprintf("  /C0 [ %s ]\n", $this->getColorString($startColor))            . sprintf("  /C1 [ %s ]\n", $this->getColorString($endColor))            . "  /N 1\n"            . " >>\n>>\nshfill\nQ\n";    }    private function getColorSetString(ColorInterface $color) : string    {        if ($color instanceof Rgb) {            return $this->getColorString($color) . ' rgb';        }        if ($color instanceof Cmyk) {            return $this->getColorString($color) . ' cmyk';        }        if ($color instanceof Gray) {            return $this->getColorString($color) . ' gray';        }        return $this->getColorSetString($color->toCmyk());    }    private function getColorString(ColorInterface $color) : string    {        if ($color instanceof Rgb) {            return sprintf('%s %s %s', $color->getRed() / 255, $color->getGreen() / 255, $color->getBlue() / 255);        }        if ($color instanceof Cmyk) {            return sprintf(                '%s %s %s %s',                $color->getCyan() / 100,                $color->getMagenta() / 100,                $color->getYellow() / 100,                $color->getBlack() / 100            );        }        if ($color instanceof Gray) {            return sprintf('%s', $color->getGray() / 100);        }        return $this->getColorString($color->toCmyk());    }}
 |