first commit

This commit is contained in:
Your Name
2026-01-19 14:19:22 +08:00
commit fe2d9c1868
4777 changed files with 665503 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Bacon;
use BaconQrCode\Common\ErrorCorrectionLevel;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelMedium;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelQuartile;
final class ErrorCorrectionLevelConverter
{
public static function convertToBaconErrorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): ErrorCorrectionLevel
{
if ($errorCorrectionLevel instanceof ErrorCorrectionLevelLow) {
return ErrorCorrectionLevel::valueOf('L');
} elseif ($errorCorrectionLevel instanceof ErrorCorrectionLevelMedium) {
return ErrorCorrectionLevel::valueOf('M');
} elseif ($errorCorrectionLevel instanceof ErrorCorrectionLevelQuartile) {
return ErrorCorrectionLevel::valueOf('Q');
} elseif ($errorCorrectionLevel instanceof ErrorCorrectionLevelHigh) {
return ErrorCorrectionLevel::valueOf('H');
}
throw new \Exception('Error correction level could not be converted');
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Bacon;
use BaconQrCode\Encoder\Encoder;
use Endroid\QrCode\Matrix\Matrix;
use Endroid\QrCode\Matrix\MatrixFactoryInterface;
use Endroid\QrCode\Matrix\MatrixInterface;
use Endroid\QrCode\QrCodeInterface;
final class MatrixFactory implements MatrixFactoryInterface
{
public function create(QrCodeInterface $qrCode): MatrixInterface
{
$baconErrorCorrectionLevel = ErrorCorrectionLevelConverter::convertToBaconErrorCorrectionLevel($qrCode->getErrorCorrectionLevel());
$baconMatrix = Encoder::encode($qrCode->getData(), $baconErrorCorrectionLevel, strval($qrCode->getEncoding()))->getMatrix();
$blockValues = [];
$columnCount = $baconMatrix->getWidth();
$rowCount = $baconMatrix->getHeight();
for ($rowIndex = 0; $rowIndex < $rowCount; ++$rowIndex) {
$blockValues[$rowIndex] = [];
for ($columnIndex = 0; $columnIndex < $columnCount; ++$columnIndex) {
$blockValues[$rowIndex][$columnIndex] = $baconMatrix->get($columnIndex, $rowIndex);
}
}
return new Matrix($blockValues, $qrCode->getSize(), $qrCode->getMargin(), $qrCode->getRoundBlockSizeMode());
}
}

View File

@@ -0,0 +1,279 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Builder;
use Endroid\QrCode\Color\ColorInterface;
use Endroid\QrCode\Encoding\EncodingInterface;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
use Endroid\QrCode\Exception\ValidationException;
use Endroid\QrCode\Label\Alignment\LabelAlignmentInterface;
use Endroid\QrCode\Label\Font\FontInterface;
use Endroid\QrCode\Label\Label;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Label\Margin\MarginInterface;
use Endroid\QrCode\Logo\Logo;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCode;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
use Endroid\QrCode\Writer\PngWriter;
use Endroid\QrCode\Writer\Result\ResultInterface;
use Endroid\QrCode\Writer\ValidatingWriterInterface;
use Endroid\QrCode\Writer\WriterInterface;
class Builder implements BuilderInterface
{
/**
* @var array<string, mixed>{
* data: string,
* writer: WriterInterface,
* writerOptions: array,
* qrCodeClass: class-string,
* logoClass: class-string,
* labelClass: class-string,
* validateResult: bool,
* size?: int,
* encoding?: EncodingInterface,
* errorCorrectionLevel?: ErrorCorrectionLevelInterface,
* roundBlockSizeMode?: RoundBlockSizeModeInterface,
* margin?: int,
* backgroundColor?: ColorInterface,
* foregroundColor?: ColorInterface,
* labelText?: string,
* labelFont?: FontInterface,
* labelAlignment?: LabelAlignmentInterface,
* labelMargin?: MarginInterface,
* labelTextColor?: ColorInterface,
* logoPath?: string,
* logoResizeToWidth?: int,
* logoResizeToHeight?: int,
* logoPunchoutBackground?: bool
* }
*/
private array $options;
public function __construct()
{
$this->options = [
'data' => '',
'writer' => new PngWriter(),
'writerOptions' => [],
'qrCodeClass' => QrCode::class,
'logoClass' => Logo::class,
'labelClass' => Label::class,
'validateResult' => false,
];
}
public static function create(): BuilderInterface
{
return new self();
}
public function writer(WriterInterface $writer): BuilderInterface
{
$this->options['writer'] = $writer;
return $this;
}
/** @param array<string, mixed> $writerOptions */
public function writerOptions(array $writerOptions): BuilderInterface
{
$this->options['writerOptions'] = $writerOptions;
return $this;
}
public function data(string $data): BuilderInterface
{
$this->options['data'] = $data;
return $this;
}
public function encoding(EncodingInterface $encoding): BuilderInterface
{
$this->options['encoding'] = $encoding;
return $this;
}
public function errorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): BuilderInterface
{
$this->options['errorCorrectionLevel'] = $errorCorrectionLevel;
return $this;
}
public function size(int $size): BuilderInterface
{
$this->options['size'] = $size;
return $this;
}
public function margin(int $margin): BuilderInterface
{
$this->options['margin'] = $margin;
return $this;
}
public function roundBlockSizeMode(RoundBlockSizeModeInterface $roundBlockSizeMode): BuilderInterface
{
$this->options['roundBlockSizeMode'] = $roundBlockSizeMode;
return $this;
}
public function foregroundColor(ColorInterface $foregroundColor): BuilderInterface
{
$this->options['foregroundColor'] = $foregroundColor;
return $this;
}
public function backgroundColor(ColorInterface $backgroundColor): BuilderInterface
{
$this->options['backgroundColor'] = $backgroundColor;
return $this;
}
public function logoPath(string $logoPath): BuilderInterface
{
$this->options['logoPath'] = $logoPath;
return $this;
}
public function logoResizeToWidth(int $logoResizeToWidth): BuilderInterface
{
$this->options['logoResizeToWidth'] = $logoResizeToWidth;
return $this;
}
public function logoResizeToHeight(int $logoResizeToHeight): BuilderInterface
{
$this->options['logoResizeToHeight'] = $logoResizeToHeight;
return $this;
}
public function logoPunchoutBackground(bool $logoPunchoutBackground): BuilderInterface
{
$this->options['logoPunchoutBackground'] = $logoPunchoutBackground;
return $this;
}
public function labelText(string $labelText): BuilderInterface
{
$this->options['labelText'] = $labelText;
return $this;
}
public function labelFont(FontInterface $labelFont): BuilderInterface
{
$this->options['labelFont'] = $labelFont;
return $this;
}
public function labelAlignment(LabelAlignmentInterface $labelAlignment): BuilderInterface
{
$this->options['labelAlignment'] = $labelAlignment;
return $this;
}
public function labelMargin(MarginInterface $labelMargin): BuilderInterface
{
$this->options['labelMargin'] = $labelMargin;
return $this;
}
public function labelTextColor(ColorInterface $labelTextColor): BuilderInterface
{
$this->options['labelTextColor'] = $labelTextColor;
return $this;
}
public function validateResult(bool $validateResult): BuilderInterface
{
$this->options['validateResult'] = $validateResult;
return $this;
}
public function build(): ResultInterface
{
$writer = $this->options['writer'];
if ($this->options['validateResult'] && !$writer instanceof ValidatingWriterInterface) {
throw ValidationException::createForUnsupportedWriter(strval(get_class($writer)));
}
/** @var QrCode $qrCode */
$qrCode = $this->buildObject($this->options['qrCodeClass']);
/** @var LogoInterface|null $logo */
$logo = $this->buildObject($this->options['logoClass'], 'logo');
/** @var LabelInterface|null $label */
$label = $this->buildObject($this->options['labelClass'], 'label');
$result = $writer->write($qrCode, $logo, $label, $this->options['writerOptions']);
if ($this->options['validateResult'] && $writer instanceof ValidatingWriterInterface) {
$writer->validateResult($result, $qrCode->getData());
}
return $result;
}
/**
* @param class-string $class
*
* @return mixed
*/
private function buildObject(string $class, string|null $optionsPrefix = null)
{
/** @var \ReflectionClass<object> $reflectionClass */
$reflectionClass = new \ReflectionClass($class);
$arguments = [];
$hasBuilderOptions = false;
$missingRequiredArguments = [];
/** @var \ReflectionMethod $constructor */
$constructor = $reflectionClass->getConstructor();
$constructorParameters = $constructor->getParameters();
foreach ($constructorParameters as $parameter) {
$optionName = null === $optionsPrefix ? $parameter->getName() : $optionsPrefix.ucfirst($parameter->getName());
if (isset($this->options[$optionName])) {
$hasBuilderOptions = true;
$arguments[] = $this->options[$optionName];
} elseif ($parameter->isDefaultValueAvailable()) {
$arguments[] = $parameter->getDefaultValue();
} else {
$missingRequiredArguments[] = $optionName;
}
}
if (!$hasBuilderOptions) {
return null;
}
if (count($missingRequiredArguments) > 0) {
throw new \Exception(sprintf('Missing required arguments: %s', implode(', ', $missingRequiredArguments)));
}
return $reflectionClass->newInstanceArgs($arguments);
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Builder;
use Endroid\QrCode\Color\ColorInterface;
use Endroid\QrCode\Encoding\EncodingInterface;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
use Endroid\QrCode\Label\Alignment\LabelAlignmentInterface;
use Endroid\QrCode\Label\Font\FontInterface;
use Endroid\QrCode\Label\Margin\MarginInterface;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
use Endroid\QrCode\Writer\Result\ResultInterface;
use Endroid\QrCode\Writer\WriterInterface;
interface BuilderInterface
{
public static function create(): BuilderInterface;
public function writer(WriterInterface $writer): BuilderInterface;
/** @param array<string, mixed> $writerOptions */
public function writerOptions(array $writerOptions): BuilderInterface;
public function data(string $data): BuilderInterface;
public function encoding(EncodingInterface $encoding): BuilderInterface;
public function errorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): BuilderInterface;
public function size(int $size): BuilderInterface;
public function margin(int $margin): BuilderInterface;
public function roundBlockSizeMode(RoundBlockSizeModeInterface $roundBlockSizeMode): BuilderInterface;
public function foregroundColor(ColorInterface $foregroundColor): BuilderInterface;
public function backgroundColor(ColorInterface $backgroundColor): BuilderInterface;
public function logoPath(string $logoPath): BuilderInterface;
public function logoResizeToWidth(int $logoResizeToWidth): BuilderInterface;
public function logoResizeToHeight(int $logoResizeToHeight): BuilderInterface;
public function logoPunchoutBackground(bool $logoPunchoutBackground): BuilderInterface;
public function labelText(string $labelText): BuilderInterface;
public function labelFont(FontInterface $labelFont): BuilderInterface;
public function labelAlignment(LabelAlignmentInterface $labelAlignment): BuilderInterface;
public function labelMargin(MarginInterface $labelMargin): BuilderInterface;
public function labelTextColor(ColorInterface $labelTextColor): BuilderInterface;
public function validateResult(bool $validateResult): BuilderInterface;
public function build(): ResultInterface;
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Builder;
final class BuilderRegistry implements BuilderRegistryInterface
{
/** @var array<BuilderInterface> */
private array $builders = [];
public function getBuilder(string $name): BuilderInterface
{
if (!isset($this->builders[$name])) {
throw new \Exception(sprintf('Builder with name "%s" not available from registry', $name));
}
return $this->builders[$name];
}
public function addBuilder(string $name, BuilderInterface $builder): void
{
$this->builders[$name] = $builder;
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Builder;
interface BuilderRegistryInterface
{
public function getBuilder(string $name): BuilderInterface;
public function addBuilder(string $name, BuilderInterface $builder): void;
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Color;
final class Color implements ColorInterface
{
public function __construct(
private int $red,
private int $green,
private int $blue,
private int $alpha = 0
) {
}
public function getRed(): int
{
return $this->red;
}
public function getGreen(): int
{
return $this->green;
}
public function getBlue(): int
{
return $this->blue;
}
public function getAlpha(): int
{
return $this->alpha;
}
public function getOpacity(): float
{
return 1 - $this->alpha / 127;
}
public function getHex(): string
{
return sprintf('#%02x%02x%02x', $this->red, $this->green, $this->blue);
}
public function toArray(): array
{
return [
'red' => $this->red,
'green' => $this->green,
'blue' => $this->blue,
'alpha' => $this->alpha,
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Color;
interface ColorInterface
{
public function getRed(): int;
public function getGreen(): int;
public function getBlue(): int;
public function getAlpha(): int;
public function getOpacity(): float;
public function getHex(): string;
/** @return array<string, int> */
public function toArray(): array;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Encoding;
final class Encoding implements EncodingInterface
{
public function __construct(
private string $value
) {
if (!in_array($value, mb_list_encodings())) {
throw new \Exception(sprintf('Invalid encoding "%s"', $value));
}
}
public function __toString(): string
{
return $this->value;
}
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Encoding;
interface EncodingInterface
{
public function __toString(): string;
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\ErrorCorrectionLevel;
final class ErrorCorrectionLevelHigh implements ErrorCorrectionLevelInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\ErrorCorrectionLevel;
interface ErrorCorrectionLevelInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\ErrorCorrectionLevel;
final class ErrorCorrectionLevelLow implements ErrorCorrectionLevelInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\ErrorCorrectionLevel;
final class ErrorCorrectionLevelMedium implements ErrorCorrectionLevelInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\ErrorCorrectionLevel;
final class ErrorCorrectionLevelQuartile implements ErrorCorrectionLevelInterface
{
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Exception;
final class ValidationException extends \Exception
{
public static function createForUnsupportedWriter(string $writerClass): self
{
return new self(sprintf('Unable to validate the result: "%s" does not support validation', $writerClass));
}
public static function createForMissingPackage(string $packageName): self
{
return new self(sprintf('Please install "%s" or disable image validation', $packageName));
}
public static function createForInvalidData(string $expectedData, string $actualData): self
{
return new self('The validation reader read "'.$actualData.'" instead of "'.$expectedData.'". Adjust your parameters to increase readability or disable validation.');
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\ImageData;
use Endroid\QrCode\Label\LabelInterface;
class LabelImageData
{
private function __construct(
private int $width,
private int $height
) {
}
public static function createForLabel(LabelInterface $label): self
{
if (false !== strpos($label->getText(), "\n")) {
throw new \Exception('Label does not support line breaks');
}
if (!function_exists('imagettfbbox')) {
throw new \Exception('Function "imagettfbbox" does not exist: check your FreeType installation');
}
$labelBox = imagettfbbox($label->getFont()->getSize(), 0, $label->getFont()->getPath(), $label->getText());
if (!is_array($labelBox)) {
throw new \Exception('Unable to generate label image box: check your FreeType installation');
}
return new self(
intval($labelBox[2] - $labelBox[0]),
intval($labelBox[0] - $labelBox[7])
);
}
public function getWidth(): int
{
return $this->width;
}
public function getHeight(): int
{
return $this->height;
}
}

View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\ImageData;
use Endroid\QrCode\Logo\LogoInterface;
class LogoImageData
{
private function __construct(
private string $data,
private \GdImage|null $image,
private string $mimeType,
private int $width,
private int $height,
private bool $punchoutBackground
) {
}
public static function createForLogo(LogoInterface $logo): self
{
$data = @file_get_contents($logo->getPath());
if (!is_string($data)) {
throw new \Exception(sprintf('Invalid data at path "%s"', $logo->getPath()));
}
if (false !== filter_var($logo->getPath(), FILTER_VALIDATE_URL)) {
$mimeType = self::detectMimeTypeFromUrl($logo->getPath());
} else {
$mimeType = self::detectMimeTypeFromPath($logo->getPath());
}
$width = $logo->getResizeToWidth();
$height = $logo->getResizeToHeight();
if ('image/svg+xml' === $mimeType) {
if (null === $width || null === $height) {
throw new \Exception('SVG Logos require an explicitly set resize width and height');
}
return new self($data, null, $mimeType, $width, $height, $logo->getPunchoutBackground());
}
$image = @imagecreatefromstring($data);
if (!$image) {
throw new \Exception(sprintf('Unable to parse image data at path "%s"', $logo->getPath()));
}
// No target width and height specified: use from original image
if (null !== $width && null !== $height) {
return new self($data, $image, $mimeType, $width, $height, $logo->getPunchoutBackground());
}
// Only target width specified: calculate height
if (null !== $width && null === $height) {
return new self($data, $image, $mimeType, $width, intval(imagesy($image) * $width / imagesx($image)), $logo->getPunchoutBackground());
}
// Only target height specified: calculate width
if (null === $width && null !== $height) {
return new self($data, $image, $mimeType, intval(imagesx($image) * $height / imagesy($image)), $height, $logo->getPunchoutBackground());
}
return new self($data, $image, $mimeType, imagesx($image), imagesy($image), $logo->getPunchoutBackground());
}
public function getData(): string
{
return $this->data;
}
public function getImage(): \GdImage
{
if (!$this->image instanceof \GdImage) {
throw new \Exception('SVG Images have no image resource');
}
return $this->image;
}
public function getMimeType(): string
{
return $this->mimeType;
}
public function getWidth(): int
{
return $this->width;
}
public function getHeight(): int
{
return $this->height;
}
public function getPunchoutBackground(): bool
{
return $this->punchoutBackground;
}
public function createDataUri(): string
{
return 'data:'.$this->mimeType.';base64,'.base64_encode($this->data);
}
private static function detectMimeTypeFromUrl(string $url): string
{
$headers = get_headers($url, true);
if (!is_array($headers) || !isset($headers['Content-Type'])) {
throw new \Exception(sprintf('Content type could not be determined for logo URL "%s"', $url));
}
return is_array($headers['Content-Type']) ? $headers['Content-Type'][1] : $headers['Content-Type'];
}
private static function detectMimeTypeFromPath(string $path): string
{
if (!function_exists('mime_content_type')) {
throw new \Exception('You need the ext-fileinfo extension to determine logo mime type');
}
$mimeType = @mime_content_type($path);
if (!is_string($mimeType)) {
throw new \Exception('Could not determine mime type');
}
if (!preg_match('#^image/#', $mimeType)) {
throw new \Exception('Logo path is not an image');
}
// Passing mime type image/svg results in invisible images
if ('image/svg' === $mimeType) {
return 'image/svg+xml';
}
return $mimeType;
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Alignment;
final class LabelAlignmentCenter implements LabelAlignmentInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Alignment;
interface LabelAlignmentInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Alignment;
final class LabelAlignmentLeft implements LabelAlignmentInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Alignment;
final class LabelAlignmentRight implements LabelAlignmentInterface
{
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Font;
final class Font implements FontInterface
{
public function __construct(
private string $path,
private int $size = 16
) {
$this->assertValidPath($path);
}
private function assertValidPath(string $path): void
{
if (!file_exists($path)) {
throw new \Exception(sprintf('Invalid font path "%s"', $path));
}
}
public function getPath(): string
{
return $this->path;
}
public function getSize(): int
{
return $this->size;
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Font;
interface FontInterface
{
public function getPath(): string;
public function getSize(): int;
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Font;
final class NotoSans implements FontInterface
{
public function __construct(
private int $size = 16
) {
}
public function getPath(): string
{
return __DIR__.'/../../../assets/noto_sans.otf';
}
public function getSize(): int
{
return $this->size;
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Font;
final class OpenSans implements FontInterface
{
public function __construct(
private int $size = 16
) {
}
public function getPath(): string
{
return __DIR__.'/../../../assets/open_sans.ttf';
}
public function getSize(): int
{
return $this->size;
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label;
use Endroid\QrCode\Color\Color;
use Endroid\QrCode\Color\ColorInterface;
use Endroid\QrCode\Label\Alignment\LabelAlignmentCenter;
use Endroid\QrCode\Label\Alignment\LabelAlignmentInterface;
use Endroid\QrCode\Label\Font\Font;
use Endroid\QrCode\Label\Font\FontInterface;
use Endroid\QrCode\Label\Margin\Margin;
use Endroid\QrCode\Label\Margin\MarginInterface;
final class Label implements LabelInterface
{
private FontInterface $font;
private LabelAlignmentInterface $alignment;
private MarginInterface $margin;
private ColorInterface $textColor;
public function __construct(
private string $text,
FontInterface|null $font = null,
LabelAlignmentInterface|null $alignment = null,
MarginInterface|null $margin = null,
ColorInterface|null $textColor = null
) {
$this->font = $font ?? new Font(__DIR__.'/../../assets/noto_sans.otf', 16);
$this->alignment = $alignment ?? new LabelAlignmentCenter();
$this->margin = $margin ?? new Margin(0, 10, 10, 10);
$this->textColor = $textColor ?? new Color(0, 0, 0);
}
public static function create(string $text): self
{
return new self($text);
}
public function getText(): string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
public function getFont(): FontInterface
{
return $this->font;
}
public function setFont(FontInterface $font): self
{
$this->font = $font;
return $this;
}
public function getAlignment(): LabelAlignmentInterface
{
return $this->alignment;
}
public function setAlignment(LabelAlignmentInterface $alignment): self
{
$this->alignment = $alignment;
return $this;
}
public function getMargin(): MarginInterface
{
return $this->margin;
}
public function setMargin(MarginInterface $margin): self
{
$this->margin = $margin;
return $this;
}
public function getTextColor(): ColorInterface
{
return $this->textColor;
}
public function setTextColor(ColorInterface $textColor): self
{
$this->textColor = $textColor;
return $this;
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label;
use Endroid\QrCode\Color\ColorInterface;
use Endroid\QrCode\Label\Alignment\LabelAlignmentInterface;
use Endroid\QrCode\Label\Font\FontInterface;
use Endroid\QrCode\Label\Margin\MarginInterface;
interface LabelInterface
{
public function getText(): string;
public function getFont(): FontInterface;
public function getAlignment(): LabelAlignmentInterface;
public function getMargin(): MarginInterface;
public function getTextColor(): ColorInterface;
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Margin;
final class Margin implements MarginInterface
{
public function __construct(
private int $top,
private int $right,
private int $bottom,
private int $left
) {
}
public function getTop(): int
{
return $this->top;
}
public function getRight(): int
{
return $this->right;
}
public function getBottom(): int
{
return $this->bottom;
}
public function getLeft(): int
{
return $this->left;
}
/** @return array<string, int> */
public function toArray(): array
{
return [
'top' => $this->top,
'right' => $this->right,
'bottom' => $this->bottom,
'left' => $this->left,
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Label\Margin;
interface MarginInterface
{
public function getTop(): int;
public function getRight(): int;
public function getBottom(): int;
public function getLeft(): int;
/** @return array<string, int> */
public function toArray(): array;
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Logo;
final class Logo implements LogoInterface
{
public function __construct(
private string $path,
private int|null $resizeToWidth = null,
private int|null $resizeToHeight = null,
private bool $punchoutBackground = false
) {
}
public static function create(string $path): self
{
return new self($path);
}
public function getPath(): string
{
return $this->path;
}
public function setPath(string $path): self
{
$this->path = $path;
return $this;
}
public function getResizeToWidth(): int|null
{
return $this->resizeToWidth;
}
public function setResizeToWidth(int|null $resizeToWidth): self
{
$this->resizeToWidth = $resizeToWidth;
return $this;
}
public function getResizeToHeight(): int|null
{
return $this->resizeToHeight;
}
public function setResizeToHeight(int|null $resizeToHeight): self
{
$this->resizeToHeight = $resizeToHeight;
return $this;
}
public function getPunchoutBackground(): bool
{
return $this->punchoutBackground;
}
public function setPunchoutBackground(bool $punchoutBackground): self
{
$this->punchoutBackground = $punchoutBackground;
return $this;
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Logo;
interface LogoInterface
{
public function getPath(): string;
public function getResizeToWidth(): int|null;
public function getResizeToHeight(): int|null;
public function getPunchoutBackground(): bool;
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Matrix;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeEnlarge;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeShrink;
final class Matrix implements MatrixInterface
{
private float $blockSize;
private int $innerSize;
private int $outerSize;
private int $marginLeft;
private int $marginRight;
/** @param array<array<int>> $blockValues */
public function __construct(
private array $blockValues,
int $size,
int $margin,
RoundBlockSizeModeInterface $roundBlockSizeMode
) {
$this->blockSize = $size / $this->getBlockCount();
$this->innerSize = $size;
$this->outerSize = $size + 2 * $margin;
if ($roundBlockSizeMode instanceof RoundBlockSizeModeEnlarge) {
$this->blockSize = intval(ceil($this->blockSize));
$this->innerSize = intval($this->blockSize * $this->getBlockCount());
$this->outerSize = $this->innerSize + 2 * $margin;
} elseif ($roundBlockSizeMode instanceof RoundBlockSizeModeShrink) {
$this->blockSize = intval(floor($this->blockSize));
$this->innerSize = intval($this->blockSize * $this->getBlockCount());
$this->outerSize = $this->innerSize + 2 * $margin;
} elseif ($roundBlockSizeMode instanceof RoundBlockSizeModeMargin) {
$this->blockSize = intval(floor($this->blockSize));
$this->innerSize = intval($this->blockSize * $this->getBlockCount());
}
if ($this->blockSize < 1) {
throw new \Exception('Too much data: increase image dimensions or lower error correction level');
}
$this->marginLeft = intval(($this->outerSize - $this->innerSize) / 2);
$this->marginRight = $this->outerSize - $this->innerSize - $this->marginLeft;
}
public function getBlockValue(int $rowIndex, int $columnIndex): int
{
return $this->blockValues[$rowIndex][$columnIndex];
}
public function getBlockCount(): int
{
return count($this->blockValues[0]);
}
public function getBlockSize(): float
{
return $this->blockSize;
}
public function getInnerSize(): int
{
return $this->innerSize;
}
public function getOuterSize(): int
{
return $this->outerSize;
}
public function getMarginLeft(): int
{
return $this->marginLeft;
}
public function getMarginRight(): int
{
return $this->marginRight;
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Matrix;
use Endroid\QrCode\QrCodeInterface;
interface MatrixFactoryInterface
{
public function create(QrCodeInterface $qrCode): MatrixInterface;
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Matrix;
interface MatrixInterface
{
public function getBlockValue(int $rowIndex, int $columnIndex): int;
public function getBlockCount(): int;
public function getBlockSize(): float;
public function getInnerSize(): int;
public function getOuterSize(): int;
public function getMarginLeft(): int;
public function getMarginRight(): int;
}

141
vendor/endroid/qr-code/src/QrCode.php vendored Normal file
View File

@@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode;
use Endroid\QrCode\Color\Color;
use Endroid\QrCode\Color\ColorInterface;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\Encoding\EncodingInterface;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
final class QrCode implements QrCodeInterface
{
private EncodingInterface $encoding;
private ErrorCorrectionLevelInterface $errorCorrectionLevel;
private RoundBlockSizeModeInterface $roundBlockSizeMode;
private ColorInterface $foregroundColor;
private ColorInterface $backgroundColor;
public function __construct(
private string $data,
EncodingInterface|null $encoding = null,
ErrorCorrectionLevelInterface|null $errorCorrectionLevel = null,
private int $size = 300,
private int $margin = 10,
RoundBlockSizeModeInterface|null $roundBlockSizeMode = null,
ColorInterface|null $foregroundColor = null,
ColorInterface|null $backgroundColor = null
) {
$this->encoding = $encoding ?? new Encoding('UTF-8');
$this->errorCorrectionLevel = $errorCorrectionLevel ?? new ErrorCorrectionLevelLow();
$this->roundBlockSizeMode = $roundBlockSizeMode ?? new RoundBlockSizeModeMargin();
$this->foregroundColor = $foregroundColor ?? new Color(0, 0, 0);
$this->backgroundColor = $backgroundColor ?? new Color(255, 255, 255);
}
public static function create(string $data): self
{
return new self($data);
}
public function getData(): string
{
return $this->data;
}
public function setData(string $data): self
{
$this->data = $data;
return $this;
}
public function getEncoding(): EncodingInterface
{
return $this->encoding;
}
public function setEncoding(Encoding $encoding): self
{
$this->encoding = $encoding;
return $this;
}
public function getErrorCorrectionLevel(): ErrorCorrectionLevelInterface
{
return $this->errorCorrectionLevel;
}
public function setErrorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): self
{
$this->errorCorrectionLevel = $errorCorrectionLevel;
return $this;
}
public function getSize(): int
{
return $this->size;
}
public function setSize(int $size): self
{
$this->size = $size;
return $this;
}
public function getMargin(): int
{
return $this->margin;
}
public function setMargin(int $margin): self
{
$this->margin = $margin;
return $this;
}
public function getRoundBlockSizeMode(): RoundBlockSizeModeInterface
{
return $this->roundBlockSizeMode;
}
public function setRoundBlockSizeMode(RoundBlockSizeModeInterface $roundBlockSizeMode): self
{
$this->roundBlockSizeMode = $roundBlockSizeMode;
return $this;
}
public function getForegroundColor(): ColorInterface
{
return $this->foregroundColor;
}
public function setForegroundColor(ColorInterface $foregroundColor): self
{
$this->foregroundColor = $foregroundColor;
return $this;
}
public function getBackgroundColor(): ColorInterface
{
return $this->backgroundColor;
}
public function setBackgroundColor(ColorInterface $backgroundColor): self
{
$this->backgroundColor = $backgroundColor;
return $this;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode;
use Endroid\QrCode\Color\ColorInterface;
use Endroid\QrCode\Encoding\EncodingInterface;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
interface QrCodeInterface
{
public function getData(): string;
public function getEncoding(): EncodingInterface;
public function getErrorCorrectionLevel(): ErrorCorrectionLevelInterface;
public function getSize(): int;
public function getMargin(): int;
public function getRoundBlockSizeMode(): RoundBlockSizeModeInterface;
public function getForegroundColor(): ColorInterface;
public function getBackgroundColor(): ColorInterface;
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\RoundBlockSizeMode;
final class RoundBlockSizeModeEnlarge implements RoundBlockSizeModeInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\RoundBlockSizeMode;
interface RoundBlockSizeModeInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\RoundBlockSizeMode;
final class RoundBlockSizeModeMargin implements RoundBlockSizeModeInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\RoundBlockSizeMode;
final class RoundBlockSizeModeNone implements RoundBlockSizeModeInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\RoundBlockSizeMode;
final class RoundBlockSizeModeShrink implements RoundBlockSizeModeInterface
{
}

View File

@@ -0,0 +1,205 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Bacon\MatrixFactory;
use Endroid\QrCode\Exception\ValidationException;
use Endroid\QrCode\ImageData\LabelImageData;
use Endroid\QrCode\ImageData\LogoImageData;
use Endroid\QrCode\Label\Alignment\LabelAlignmentLeft;
use Endroid\QrCode\Label\Alignment\LabelAlignmentRight;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeNone;
use Endroid\QrCode\Writer\Result\GdResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
use Zxing\QrReader;
abstract class AbstractGdWriter implements WriterInterface, ValidatingWriterInterface
{
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
if (!extension_loaded('gd')) {
throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
}
$matrixFactory = new MatrixFactory();
$matrix = $matrixFactory->create($qrCode);
$baseBlockSize = $qrCode->getRoundBlockSizeMode() instanceof RoundBlockSizeModeNone ? 10 : intval($matrix->getBlockSize());
$baseImage = imagecreatetruecolor($matrix->getBlockCount() * $baseBlockSize, $matrix->getBlockCount() * $baseBlockSize);
if (!$baseImage) {
throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
}
/** @var int $foregroundColor */
$foregroundColor = imagecolorallocatealpha(
$baseImage,
$qrCode->getForegroundColor()->getRed(),
$qrCode->getForegroundColor()->getGreen(),
$qrCode->getForegroundColor()->getBlue(),
$qrCode->getForegroundColor()->getAlpha()
);
/** @var int $transparentColor */
$transparentColor = imagecolorallocatealpha($baseImage, 255, 255, 255, 127);
imagefill($baseImage, 0, 0, $transparentColor);
for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
if (1 === $matrix->getBlockValue($rowIndex, $columnIndex)) {
imagefilledrectangle(
$baseImage,
$columnIndex * $baseBlockSize,
$rowIndex * $baseBlockSize,
($columnIndex + 1) * $baseBlockSize - 1,
($rowIndex + 1) * $baseBlockSize - 1,
$foregroundColor
);
}
}
}
$targetWidth = $matrix->getOuterSize();
$targetHeight = $matrix->getOuterSize();
if ($label instanceof LabelInterface) {
$labelImageData = LabelImageData::createForLabel($label);
$targetHeight += $labelImageData->getHeight() + $label->getMargin()->getTop() + $label->getMargin()->getBottom();
}
$targetImage = imagecreatetruecolor($targetWidth, $targetHeight);
if (!$targetImage) {
throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
}
/** @var int $backgroundColor */
$backgroundColor = imagecolorallocatealpha(
$targetImage,
$qrCode->getBackgroundColor()->getRed(),
$qrCode->getBackgroundColor()->getGreen(),
$qrCode->getBackgroundColor()->getBlue(),
$qrCode->getBackgroundColor()->getAlpha()
);
imagefill($targetImage, 0, 0, $backgroundColor);
imagecopyresampled(
$targetImage,
$baseImage,
$matrix->getMarginLeft(),
$matrix->getMarginLeft(),
0,
0,
$matrix->getInnerSize(),
$matrix->getInnerSize(),
imagesx($baseImage),
imagesy($baseImage)
);
if ($qrCode->getBackgroundColor()->getAlpha() > 0) {
imagesavealpha($targetImage, true);
}
$result = new GdResult($matrix, $targetImage);
if ($logo instanceof LogoInterface) {
$result = $this->addLogo($logo, $result);
}
if ($label instanceof LabelInterface) {
$result = $this->addLabel($label, $result);
}
return $result;
}
private function addLogo(LogoInterface $logo, GdResult $result): GdResult
{
$logoImageData = LogoImageData::createForLogo($logo);
if ('image/svg+xml' === $logoImageData->getMimeType()) {
throw new \Exception('PNG Writer does not support SVG logo');
}
$targetImage = $result->getImage();
$matrix = $result->getMatrix();
if ($logoImageData->getPunchoutBackground()) {
/** @var int $transparent */
$transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
imagealphablending($targetImage, false);
$xOffsetStart = intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2);
$yOffsetStart = intval($matrix->getOuterSize() / 2 - $logoImageData->getHeight() / 2);
for ($xOffset = $xOffsetStart; $xOffset < $xOffsetStart + $logoImageData->getWidth(); ++$xOffset) {
for ($yOffset = $yOffsetStart; $yOffset < $yOffsetStart + $logoImageData->getHeight(); ++$yOffset) {
imagesetpixel($targetImage, $xOffset, $yOffset, $transparent);
}
}
}
imagecopyresampled(
$targetImage,
$logoImageData->getImage(),
intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2),
intval($matrix->getOuterSize() / 2 - $logoImageData->getHeight() / 2),
0,
0,
$logoImageData->getWidth(),
$logoImageData->getHeight(),
imagesx($logoImageData->getImage()),
imagesy($logoImageData->getImage())
);
return new GdResult($matrix, $targetImage);
}
private function addLabel(LabelInterface $label, GdResult $result): GdResult
{
$targetImage = $result->getImage();
$labelImageData = LabelImageData::createForLabel($label);
/** @var int $textColor */
$textColor = imagecolorallocatealpha(
$targetImage,
$label->getTextColor()->getRed(),
$label->getTextColor()->getGreen(),
$label->getTextColor()->getBlue(),
$label->getTextColor()->getAlpha()
);
$x = intval(imagesx($targetImage) / 2 - $labelImageData->getWidth() / 2);
$y = imagesy($targetImage) - $label->getMargin()->getBottom();
if ($label->getAlignment() instanceof LabelAlignmentLeft) {
$x = $label->getMargin()->getLeft();
} elseif ($label->getAlignment() instanceof LabelAlignmentRight) {
$x = imagesx($targetImage) - $labelImageData->getWidth() - $label->getMargin()->getRight();
}
imagettftext($targetImage, $label->getFont()->getSize(), 0, $x, $y, $textColor, $label->getFont()->getPath(), $label->getText());
return new GdResult($result->getMatrix(), $targetImage);
}
public function validateResult(ResultInterface $result, string $expectedData): void
{
$string = $result->getString();
if (!class_exists(QrReader::class)) {
throw ValidationException::createForMissingPackage('khanamiryan/qrcode-detector-decoder');
}
$reader = new QrReader($string, QrReader::SOURCE_TYPE_BLOB);
if ($reader->text() !== $expectedData) {
throw ValidationException::createForInvalidData($expectedData, strval($reader->text()));
}
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Bacon\MatrixFactory;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\BinaryResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
final class BinaryWriter implements WriterInterface
{
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
$matrixFactory = new MatrixFactory();
$matrix = $matrixFactory->create($qrCode);
return new BinaryResult($matrix);
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Bacon\MatrixFactory;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\ConsoleResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
final class ConsoleWriter implements WriterInterface
{
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, $options = []): ResultInterface
{
$matrixFactory = new MatrixFactory();
$matrix = $matrixFactory->create($qrCode);
return new ConsoleResult($matrix, $qrCode->getForegroundColor(), $qrCode->getBackgroundColor());
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Bacon\MatrixFactory;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\DebugResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
final class DebugWriter implements WriterInterface, ValidatingWriterInterface
{
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
$matrixFactory = new MatrixFactory();
$matrix = $matrixFactory->create($qrCode);
return new DebugResult($matrix, $qrCode, $logo, $label, $options);
}
public function validateResult(ResultInterface $result, string $expectedData): void
{
if (!$result instanceof DebugResult) {
throw new \Exception('Unable to write logo: instance of DebugResult expected');
}
$result->setValidateResult(true);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Bacon\MatrixFactory;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\EpsResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
final class EpsWriter implements WriterInterface
{
public const DECIMAL_PRECISION = 10;
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
$matrixFactory = new MatrixFactory();
$matrix = $matrixFactory->create($qrCode);
$lines = [
'%!PS-Adobe-3.0 EPSF-3.0',
'%%BoundingBox: 0 0 '.$matrix->getOuterSize().' '.$matrix->getOuterSize(),
'/F { rectfill } def',
number_format($qrCode->getBackgroundColor()->getRed() / 100, 2, '.', ',').' '.number_format($qrCode->getBackgroundColor()->getGreen() / 100, 2, '.', ',').' '.number_format($qrCode->getBackgroundColor()->getBlue() / 100, 2, '.', ',').' setrgbcolor',
'0 0 '.$matrix->getOuterSize().' '.$matrix->getOuterSize().' F',
number_format($qrCode->getForegroundColor()->getRed() / 100, 2, '.', ',').' '.number_format($qrCode->getForegroundColor()->getGreen() / 100, 2, '.', ',').' '.number_format($qrCode->getForegroundColor()->getBlue() / 100, 2, '.', ',').' setrgbcolor',
];
for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
if (1 === $matrix->getBlockValue($matrix->getBlockCount() - 1 - $rowIndex, $columnIndex)) {
$x = $matrix->getMarginLeft() + $matrix->getBlockSize() * $columnIndex;
$y = $matrix->getMarginLeft() + $matrix->getBlockSize() * $rowIndex;
$lines[] = number_format($x, self::DECIMAL_PRECISION, '.', '').' '.number_format($y, self::DECIMAL_PRECISION, '.', '').' '.number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', '').' '.number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', '').' F';
}
}
}
return new EpsResult($matrix, $lines);
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\GdResult;
use Endroid\QrCode\Writer\Result\GifResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
final class GifWriter extends AbstractGdWriter
{
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
/** @var GdResult $gdResult */
$gdResult = parent::write($qrCode, $logo, $label, $options);
return new GifResult($gdResult->getMatrix(), $gdResult->getImage());
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Bacon\MatrixFactory;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\PdfResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
final class PdfWriter implements WriterInterface
{
public const WRITER_OPTION_UNIT = 'unit';
public const WRITER_OPTION_PDF = 'fpdf';
public const WRITER_OPTION_X = 'x';
public const WRITER_OPTION_Y = 'y';
public const WRITER_OPTION_LINK = 'link';
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
$matrixFactory = new MatrixFactory();
$matrix = $matrixFactory->create($qrCode);
$unit = 'mm';
if (isset($options[self::WRITER_OPTION_UNIT])) {
$unit = $options[self::WRITER_OPTION_UNIT];
}
$allowedUnits = ['mm', 'pt', 'cm', 'in'];
if (!in_array($unit, $allowedUnits)) {
throw new \Exception(sprintf('PDF Measure unit should be one of [%s]', implode(', ', $allowedUnits)));
}
$labelSpace = 0;
if ($label instanceof LabelInterface) {
$labelSpace = 30;
}
if (!class_exists(\FPDF::class)) {
throw new \Exception('Unable to find FPDF: check your installation');
}
$foregroundColor = $qrCode->getForegroundColor();
if ($foregroundColor->getAlpha() > 0) {
throw new \Exception('PDF Writer does not support alpha channels');
}
$backgroundColor = $qrCode->getBackgroundColor();
if ($backgroundColor->getAlpha() > 0) {
throw new \Exception('PDF Writer does not support alpha channels');
}
if (isset($options[self::WRITER_OPTION_PDF])) {
$fpdf = $options[self::WRITER_OPTION_PDF];
if (!$fpdf instanceof \FPDF) {
throw new \Exception('pdf option must be an instance of FPDF');
}
} else {
// @todo Check how to add label height later
$fpdf = new \FPDF('P', $unit, [$matrix->getOuterSize(), $matrix->getOuterSize() + $labelSpace]);
$fpdf->AddPage();
}
$x = 0;
if (isset($options[self::WRITER_OPTION_X])) {
$x = $options[self::WRITER_OPTION_X];
}
$y = 0;
if (isset($options[self::WRITER_OPTION_Y])) {
$y = $options[self::WRITER_OPTION_Y];
}
$fpdf->SetFillColor($backgroundColor->getRed(), $backgroundColor->getGreen(), $backgroundColor->getBlue());
$fpdf->Rect($x, $y, $matrix->getOuterSize(), $matrix->getOuterSize(), 'F');
$fpdf->SetFillColor($foregroundColor->getRed(), $foregroundColor->getGreen(), $foregroundColor->getBlue());
for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
if (1 === $matrix->getBlockValue($rowIndex, $columnIndex)) {
$fpdf->Rect(
$x + $matrix->getMarginLeft() + ($columnIndex * $matrix->getBlockSize()),
$y + $matrix->getMarginLeft() + ($rowIndex * $matrix->getBlockSize()),
$matrix->getBlockSize(),
$matrix->getBlockSize(),
'F'
);
}
}
}
if ($logo instanceof LogoInterface) {
$this->addLogo($logo, $fpdf, $x, $y, $matrix->getOuterSize());
}
if ($label instanceof LabelInterface) {
$fpdf->SetXY($x, $y + $matrix->getOuterSize() + $labelSpace - 25);
$fpdf->SetFont('Helvetica', '', $label->getFont()->getSize());
$fpdf->Cell($matrix->getOuterSize(), 0, $label->getText(), 0, 0, 'C');
}
if (isset($options[self::WRITER_OPTION_LINK])) {
$link = $options[self::WRITER_OPTION_LINK];
$fpdf->Link($x, $y, $x + $matrix->getOuterSize(), $y + $matrix->getOuterSize(), $link);
}
return new PdfResult($matrix, $fpdf);
}
private function addLogo(LogoInterface $logo, \FPDF $fpdf, float $x, float $y, float $size): void
{
$logoPath = $logo->getPath();
$logoHeight = $logo->getResizeToHeight();
$logoWidth = $logo->getResizeToWidth();
if (null === $logoHeight || null === $logoWidth) {
$imageSize = \getimagesize($logoPath);
if (!$imageSize) {
throw new \Exception(sprintf('Unable to read image size for logo "%s"', $logoPath));
}
[$logoSourceWidth, $logoSourceHeight] = $imageSize;
if (null === $logoWidth) {
$logoWidth = (int) $logoSourceWidth;
}
if (null === $logoHeight) {
$aspectRatio = $logoWidth / $logoSourceWidth;
$logoHeight = (int) ($logoSourceHeight * $aspectRatio);
}
}
$logoX = $x + $size / 2 - $logoWidth / 2;
$logoY = $y + $size / 2 - $logoHeight / 2;
$fpdf->Image($logoPath, $logoX, $logoY, $logoWidth, $logoHeight);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\GdResult;
use Endroid\QrCode\Writer\Result\PngResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
final class PngWriter extends AbstractGdWriter
{
public const WRITER_OPTION_COMPRESSION_LEVEL = 'compression_level';
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
if (!isset($options[self::WRITER_OPTION_COMPRESSION_LEVEL])) {
$options[self::WRITER_OPTION_COMPRESSION_LEVEL] = -1;
}
/** @var GdResult $gdResult */
$gdResult = parent::write($qrCode, $logo, $label, $options);
return new PngResult($gdResult->getMatrix(), $gdResult->getImage(), $options[self::WRITER_OPTION_COMPRESSION_LEVEL]);
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
abstract class AbstractResult implements ResultInterface
{
public function __construct(
private MatrixInterface $matrix
) {
}
public function getMatrix(): MatrixInterface
{
return $this->matrix;
}
public function getDataUri(): string
{
return 'data:'.$this->getMimeType().';base64,'.base64_encode($this->getString());
}
public function saveToFile(string $path): void
{
$string = $this->getString();
file_put_contents($path, $string);
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
final class BinaryResult extends AbstractResult
{
public function __construct(MatrixInterface $matrix)
{
parent::__construct($matrix);
}
public function getString(): string
{
$matrix = $this->getMatrix();
$binaryString = '';
for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
$binaryString .= $matrix->getBlockValue($rowIndex, $columnIndex);
}
$binaryString .= "\n";
}
return $binaryString;
}
public function getMimeType(): string
{
return 'text/plain';
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Color\ColorInterface;
use Endroid\QrCode\Matrix\MatrixInterface;
class ConsoleResult extends AbstractResult
{
private const TWO_BLOCKS = [
0 => ' ',
1 => "\xe2\x96\x80",
2 => "\xe2\x96\x84",
3 => "\xe2\x96\x88",
];
private string $colorEscapeCode;
public function __construct(
MatrixInterface $matrix,
ColorInterface $foreground,
ColorInterface $background
) {
parent::__construct($matrix);
$this->colorEscapeCode = sprintf(
"\e[38;2;%d;%d;%dm\e[48;2;%d;%d;%dm",
$foreground->getRed(),
$foreground->getGreen(),
$foreground->getBlue(),
$background->getRed(),
$background->getGreen(),
$background->getBlue()
);
}
public function getMimeType(): string
{
return 'text/plain';
}
public function getString(): string
{
$matrix = $this->getMatrix();
$side = $matrix->getBlockCount();
$marginLeft = $this->colorEscapeCode.self::TWO_BLOCKS[0].self::TWO_BLOCKS[0];
$marginRight = self::TWO_BLOCKS[0].self::TWO_BLOCKS[0]."\e[0m".PHP_EOL;
$marginVertical = $marginLeft.str_repeat(self::TWO_BLOCKS[0], $side).$marginRight;
$qrCodeString = $marginVertical;
for ($rowIndex = 0; $rowIndex < $side; $rowIndex += 2) {
$qrCodeString .= $marginLeft;
for ($columnIndex = 0; $columnIndex < $side; ++$columnIndex) {
$combined = $matrix->getBlockValue($rowIndex, $columnIndex);
if ($rowIndex + 1 < $side) {
$combined |= $matrix->getBlockValue($rowIndex + 1, $columnIndex) << 1;
}
$qrCodeString .= self::TWO_BLOCKS[$combined];
}
$qrCodeString .= $marginRight;
}
$qrCodeString .= $marginVertical;
return $qrCodeString;
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\Matrix\MatrixInterface;
use Endroid\QrCode\QrCodeInterface;
final class DebugResult extends AbstractResult
{
private bool $validateResult = false;
public function __construct(
MatrixInterface $matrix,
private QrCodeInterface $qrCode,
private LogoInterface|null $logo = null,
private LabelInterface|null $label = null,
/** @var array<string, mixed> $options */
private array $options = []
) {
parent::__construct($matrix);
}
public function setValidateResult(bool $validateResult): void
{
$this->validateResult = $validateResult;
}
public function getString(): string
{
$debugLines = [];
$debugLines[] = 'Data: '.$this->qrCode->getData();
$debugLines[] = 'Encoding: '.$this->qrCode->getEncoding();
$debugLines[] = 'Error Correction Level: '.get_class($this->qrCode->getErrorCorrectionLevel());
$debugLines[] = 'Size: '.$this->qrCode->getSize();
$debugLines[] = 'Margin: '.$this->qrCode->getMargin();
$debugLines[] = 'Round block size mode: '.get_class($this->qrCode->getRoundBlockSizeMode());
$debugLines[] = 'Foreground color: ['.implode(', ', $this->qrCode->getForegroundColor()->toArray()).']';
$debugLines[] = 'Background color: ['.implode(', ', $this->qrCode->getBackgroundColor()->toArray()).']';
foreach ($this->options as $key => $value) {
$debugLines[] = 'Writer option: '.$key.': '.$value;
}
if (isset($this->logo)) {
$debugLines[] = 'Logo path: '.$this->logo->getPath();
$debugLines[] = 'Logo resize to width: '.$this->logo->getResizeToWidth();
$debugLines[] = 'Logo resize to height: '.$this->logo->getResizeToHeight();
}
if (isset($this->label)) {
$debugLines[] = 'Label text: '.$this->label->getText();
$debugLines[] = 'Label font path: '.$this->label->getFont()->getPath();
$debugLines[] = 'Label font size: '.$this->label->getFont()->getSize();
$debugLines[] = 'Label alignment: '.get_class($this->label->getAlignment());
$debugLines[] = 'Label margin: ['.implode(', ', $this->label->getMargin()->toArray()).']';
$debugLines[] = 'Label text color: ['.implode(', ', $this->label->getTextColor()->toArray()).']';
}
$debugLines[] = 'Validate result: '.($this->validateResult ? 'true' : 'false');
return implode("\n", $debugLines);
}
public function getMimeType(): string
{
return 'text/plain';
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
final class EpsResult extends AbstractResult
{
public function __construct(
MatrixInterface $matrix,
/** @var array<string> $lines */
private array $lines
) {
parent::__construct($matrix);
}
public function getString(): string
{
return implode("\n", $this->lines);
}
public function getMimeType(): string
{
return 'image/eps';
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
class GdResult extends AbstractResult
{
public function __construct(
MatrixInterface $matrix,
protected \GdImage $image
) {
parent::__construct($matrix);
}
public function getImage(): \GdImage
{
return $this->image;
}
public function getString(): string
{
throw new \Exception('You can only use this method in a concrete implementation');
}
public function getMimeType(): string
{
throw new \Exception('You can only use this method in a concrete implementation');
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
final class GifResult extends GdResult
{
public function getString(): string
{
ob_start();
imagegif($this->image);
return strval(ob_get_clean());
}
public function getMimeType(): string
{
return 'image/gif';
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
final class PdfResult extends AbstractResult
{
public function __construct(
MatrixInterface $matrix,
private \FPDF $fpdf
) {
parent::__construct($matrix);
}
public function getPdf(): \FPDF
{
return $this->fpdf;
}
public function getString(): string
{
return $this->fpdf->Output('S');
}
public function getMimeType(): string
{
return 'application/pdf';
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
final class PngResult extends GdResult
{
private int $quality;
public function __construct(MatrixInterface $matrix, \GdImage $image, int $quality = -1)
{
parent::__construct($matrix, $image);
$this->quality = $quality;
}
public function getString(): string
{
ob_start();
imagepng($this->image, quality: $this->quality);
return strval(ob_get_clean());
}
public function getMimeType(): string
{
return 'image/png';
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
interface ResultInterface
{
public function getMatrix(): MatrixInterface;
public function getString(): string;
public function getDataUri(): string;
public function saveToFile(string $path): void;
public function getMimeType(): string;
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
final class SvgResult extends AbstractResult
{
public function __construct(
MatrixInterface $matrix,
private \SimpleXMLElement $xml,
private bool $excludeXmlDeclaration = false
) {
parent::__construct($matrix);
}
public function getXml(): \SimpleXMLElement
{
return $this->xml;
}
public function getString(): string
{
$string = $this->xml->asXML();
if (!is_string($string)) {
throw new \Exception('Could not save SVG XML to string');
}
if ($this->excludeXmlDeclaration) {
$string = str_replace("<?xml version=\"1.0\"?>\n", '', $string);
}
return $string;
}
public function getMimeType(): string
{
return 'image/svg+xml';
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer\Result;
use Endroid\QrCode\Matrix\MatrixInterface;
final class WebPResult extends GdResult
{
private int $quality;
public function __construct(MatrixInterface $matrix, \GdImage $image, int $quality = -1)
{
parent::__construct($matrix, $image);
$this->quality = $quality;
}
public function getString(): string
{
if (!function_exists('imagewebp')) {
throw new \Exception('WebP support is not available in your GD installation');
}
ob_start();
imagewebp($this->image, quality: $this->quality);
return strval(ob_get_clean());
}
public function getMimeType(): string
{
return 'image/webp';
}
}

View File

@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Bacon\MatrixFactory;
use Endroid\QrCode\ImageData\LogoImageData;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\ResultInterface;
use Endroid\QrCode\Writer\Result\SvgResult;
final class SvgWriter implements WriterInterface
{
public const DECIMAL_PRECISION = 10;
public const WRITER_OPTION_BLOCK_ID = 'block_id';
public const WRITER_OPTION_EXCLUDE_XML_DECLARATION = 'exclude_xml_declaration';
public const WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT = 'exclude_svg_width_and_height';
public const WRITER_OPTION_FORCE_XLINK_HREF = 'force_xlink_href';
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
if (!isset($options[self::WRITER_OPTION_BLOCK_ID])) {
$options[self::WRITER_OPTION_BLOCK_ID] = 'block';
}
if (!isset($options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION])) {
$options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION] = false;
}
if (!isset($options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT])) {
$options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT] = false;
}
$matrixFactory = new MatrixFactory();
$matrix = $matrixFactory->create($qrCode);
$xml = new \SimpleXMLElement('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"/>');
$xml->addAttribute('version', '1.1');
if (!$options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT]) {
$xml->addAttribute('width', $matrix->getOuterSize().'px');
$xml->addAttribute('height', $matrix->getOuterSize().'px');
}
$xml->addAttribute('viewBox', '0 0 '.$matrix->getOuterSize().' '.$matrix->getOuterSize());
$xml->addChild('defs');
$blockDefinition = $xml->defs->addChild('rect');
$blockDefinition->addAttribute('id', strval($options[self::WRITER_OPTION_BLOCK_ID]));
$blockDefinition->addAttribute('width', number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', ''));
$blockDefinition->addAttribute('height', number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', ''));
$blockDefinition->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getForegroundColor()->getRed(), $qrCode->getForegroundColor()->getGreen(), $qrCode->getForegroundColor()->getBlue()));
$blockDefinition->addAttribute('fill-opacity', strval($qrCode->getForegroundColor()->getOpacity()));
$background = $xml->addChild('rect');
$background->addAttribute('x', '0');
$background->addAttribute('y', '0');
$background->addAttribute('width', strval($matrix->getOuterSize()));
$background->addAttribute('height', strval($matrix->getOuterSize()));
$background->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getBackgroundColor()->getRed(), $qrCode->getBackgroundColor()->getGreen(), $qrCode->getBackgroundColor()->getBlue()));
$background->addAttribute('fill-opacity', strval($qrCode->getBackgroundColor()->getOpacity()));
for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
if (1 === $matrix->getBlockValue($rowIndex, $columnIndex)) {
$block = $xml->addChild('use');
$block->addAttribute('x', number_format($matrix->getMarginLeft() + $matrix->getBlockSize() * $columnIndex, self::DECIMAL_PRECISION, '.', ''));
$block->addAttribute('y', number_format($matrix->getMarginLeft() + $matrix->getBlockSize() * $rowIndex, self::DECIMAL_PRECISION, '.', ''));
$block->addAttribute('xlink:href', '#'.$options[self::WRITER_OPTION_BLOCK_ID], 'http://www.w3.org/1999/xlink');
}
}
}
$result = new SvgResult($matrix, $xml, boolval($options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION]));
if ($logo instanceof LogoInterface) {
$this->addLogo($logo, $result, $options);
}
return $result;
}
/** @param array<string, mixed> $options */
private function addLogo(LogoInterface $logo, SvgResult $result, array $options): void
{
$logoImageData = LogoImageData::createForLogo($logo);
if (!isset($options[self::WRITER_OPTION_FORCE_XLINK_HREF])) {
$options[self::WRITER_OPTION_FORCE_XLINK_HREF] = false;
}
$xml = $result->getXml();
/** @var \SimpleXMLElement $xmlAttributes */
$xmlAttributes = $xml->attributes();
$x = intval($xmlAttributes->width) / 2 - $logoImageData->getWidth() / 2;
$y = intval($xmlAttributes->height) / 2 - $logoImageData->getHeight() / 2;
$imageDefinition = $xml->addChild('image');
$imageDefinition->addAttribute('x', strval($x));
$imageDefinition->addAttribute('y', strval($y));
$imageDefinition->addAttribute('width', strval($logoImageData->getWidth()));
$imageDefinition->addAttribute('height', strval($logoImageData->getHeight()));
$imageDefinition->addAttribute('preserveAspectRatio', 'none');
if ($options[self::WRITER_OPTION_FORCE_XLINK_HREF]) {
$imageDefinition->addAttribute('xlink:href', $logoImageData->createDataUri(), 'http://www.w3.org/1999/xlink');
} else {
$imageDefinition->addAttribute('href', $logoImageData->createDataUri());
}
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Writer\Result\ResultInterface;
interface ValidatingWriterInterface
{
public function validateResult(ResultInterface $result, string $expectedData): void;
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\GdResult;
use Endroid\QrCode\Writer\Result\ResultInterface;
use Endroid\QrCode\Writer\Result\WebPResult;
final class WebPWriter extends AbstractGdWriter
{
public const WRITER_OPTION_QUALITY = 'quality';
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface
{
if (!isset($options[self::WRITER_OPTION_QUALITY])) {
$options[self::WRITER_OPTION_QUALITY] = -1;
}
/** @var GdResult $gdResult */
$gdResult = parent::write($qrCode, $logo, $label, $options);
return new WebPResult($gdResult->getMatrix(), $gdResult->getImage(), $options[self::WRITER_OPTION_QUALITY]);
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Label\LabelInterface;
use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
use Endroid\QrCode\Writer\Result\ResultInterface;
interface WriterInterface
{
/** @param array<string, mixed> $options */
public function write(QrCodeInterface $qrCode, LogoInterface|null $logo = null, LabelInterface|null $label = null, array $options = []): ResultInterface;
}