123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- <?php
- declare(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());
- }
- }
|