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

2
vendor/endroid/qr-code/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
/.github/workflows/ export-ignore
/tests/ export-ignore

View File

@@ -0,0 +1 @@
github: endroid

4
vendor/endroid/qr-code/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/composer
/composer.lock
/tests/coverage/
/vendor/

19
vendor/endroid/qr-code/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright 2022 (c) Jeroen van den Enden
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

210
vendor/endroid/qr-code/README.md vendored Normal file
View File

@@ -0,0 +1,210 @@
# QR Code
*By [endroid](https://endroid.nl/)*
[![Latest Stable Version](http://img.shields.io/packagist/v/endroid/qr-code.svg)](https://packagist.org/packages/endroid/qr-code)
[![Build Status](https://github.com/endroid/qr-code/workflows/CI/badge.svg)](https://github.com/endroid/qr-code/actions)
[![Total Downloads](http://img.shields.io/packagist/dt/endroid/qr-code.svg)](https://packagist.org/packages/endroid/qr-code)
[![Monthly Downloads](http://img.shields.io/packagist/dm/endroid/qr-code.svg)](https://packagist.org/packages/endroid/qr-code)
[![License](http://img.shields.io/packagist/l/endroid/qr-code.svg)](https://packagist.org/packages/endroid/qr-code)
This library helps you generate QR codes in a jiffy. Makes use of [bacon/bacon-qr-code](https://github.com/Bacon/BaconQrCode)
to generate the matrix and [khanamiryan/qrcode-detector-decoder](https://github.com/khanamiryan/php-qrcode-detector-decoder)
for validating generated QR codes. Further extended with Twig extensions, generation routes, a factory and a
Symfony bundle for easy installation and configuration. Different writers are provided to generate the QR code
as PNG, SVG, EPS or in binary format.
## Sponsored by
[![Blackfire.io](assets/blackfire.png)](https://www.blackfire.io)
## Installation
Use [Composer](https://getcomposer.org/) to install the library. Also make sure you have enabled and configured the
[GD extension](https://www.php.net/manual/en/book.image.php) if you want to generate images.
``` bash
composer require endroid/qr-code
```
## Usage: using the builder
```php
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
use Endroid\QrCode\Label\Alignment\LabelAlignmentCenter;
use Endroid\QrCode\Label\Font\NotoSans;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
use Endroid\QrCode\Writer\PngWriter;
$result = Builder::create()
->writer(new PngWriter())
->writerOptions([])
->data('Custom QR code contents')
->encoding(new Encoding('UTF-8'))
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
->size(300)
->margin(10)
->roundBlockSizeMode(new RoundBlockSizeModeMargin())
->logoPath(__DIR__.'/assets/symfony.png')
->labelText('This is the label')
->labelFont(new NotoSans(20))
->labelAlignment(new LabelAlignmentCenter())
->validateResult(false)
->build();
```
## Usage: without using the builder
```php
use Endroid\QrCode\Color\Color;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
use Endroid\QrCode\QrCode;
use Endroid\QrCode\Label\Label;
use Endroid\QrCode\Logo\Logo;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
use Endroid\QrCode\Writer\PngWriter;
use Endroid\QrCode\Writer\ValidationException;
$writer = new PngWriter();
// Create QR code
$qrCode = QrCode::create('Life is too short to be generating QR codes')
->setEncoding(new Encoding('UTF-8'))
->setErrorCorrectionLevel(new ErrorCorrectionLevelLow())
->setSize(300)
->setMargin(10)
->setRoundBlockSizeMode(new RoundBlockSizeModeMargin())
->setForegroundColor(new Color(0, 0, 0))
->setBackgroundColor(new Color(255, 255, 255));
// Create generic logo
$logo = Logo::create(__DIR__.'/assets/symfony.png')
->setResizeToWidth(50);
// Create generic label
$label = Label::create('Label')
->setTextColor(new Color(255, 0, 0));
$result = $writer->write($qrCode, $logo, $label);
// Validate the result
$writer->validateResult($result, 'Life is too short to be generating QR codes');
```
## Usage: working with results
```php
// Directly output the QR code
header('Content-Type: '.$result->getMimeType());
echo $result->getString();
// Save it to a file
$result->saveToFile(__DIR__.'/qrcode.png');
// Generate a data URI to include image data inline (i.e. inside an <img> tag)
$dataUri = $result->getDataUri();
```
![QR Code](https://endroid.nl/qr-code/default/Life%20is%20too%20short%20to%20be%20generating%20QR%20codes)
### Writer options
Some writers provide writer options. Each available writer option is can be
found as a constant prefixed with WRITER_OPTION_ in the writer class.
* `PdfWriter`
* `unit`: unit of measurement (default: mm)
* `fpdf`: PDF to place the image in (default: new PDF)
* `x`: image offset (default: 0)
* `y`: image offset (default: 0)
* `PngWriter`
* `compression_level`: compression level (0-9, default: -1 = zlib default)
* `SvgWriter`
* `block_id`: id of the block element for external reference (default: block)
* `exclude_xml_declaration`: exclude XML declaration (default: false)
* `exclude_svg_width_and_height`: exclude width and height (default: false)
* `force_xlink_href`: forces xlink namespace in case of compatibility issues (default: false)
* `WebPWriter`
* `quality`: image quality (0-100, default: 80)
You can provide any writer options like this.
```php
use Endroid\QrCode\Writer\SvgWriter;
$builder->setWriterOptions([
SvgWriter::WRITER_OPTION_EXCLUDE_XML_DECLARATION => true
]);
```
### Encoding
If you use a barcode scanner you can have some troubles while reading the
generated QR codes. Depending on the encoding you chose you will have an extra
amount of data corresponding to the ECI block. Some barcode scanner are not
programmed to interpret this block of information. To ensure a maximum
compatibility you can use the `ISO-8859-1` encoding that is the default
encoding used by barcode scanners (if your character set supports it,
i.e. no Chinese characters are present).
### Round block size mode
By default block sizes are rounded to guarantee sharp images and improve
readability. However some other rounding variants are available.
* `margin (default)`: the size of the QR code is shrunk if necessary but the size
of the final image remains unchanged due to additional margin being added.
* `enlarge`: the size of the QR code and the final image are enlarged when
rounding differences occur.
* `shrink`: the size of the QR code and the final image are
shrunk when rounding differences occur.
* `none`: No rounding. This mode can be used when blocks don't need to be rounded
to pixels (for instance SVG).
## Readability
The readability of a QR code is primarily determined by the size, the input
length, the error correction level and any possible logo over the image so you
can tweak these parameters if you are looking for optimal results. You can also
check $qrCode->getRoundBlockSize() value to see if block dimensions are rounded
so that the image is more sharp and readable. Please note that rounding block
size can result in additional padding to compensate for the rounding difference.
And finally the encoding (default UTF-8 to support large character sets) can be
set to `ISO-8859-1` if possible to improve readability.
## Validating the generated QR code
If you need to be extra sure the QR code you generated is readable and contains
the exact data you requested you can enable the validation reader, which is
disabled by default. You can do this either via the builder or directly on any
writer that supports validation. See the examples above.
Please note that validation affects performance so only use it in case of problems.
## Symfony integration
The [endroid/qr-code-bundle](https://github.com/endroid/qr-code-bundle)
integrates the QR code library in Symfony for an even better experience.
* Configure your defaults (like image size, default writer etc.)
* Support for multiple configurations and injection via aliases
* Generate QR codes for defined configurations via URL like /qr-code/<config>/Hello
* Generate QR codes or URLs directly from Twig using dedicated functions
Read the [bundle documentation](https://github.com/endroid/qr-code-bundle)
for more information.
## Versioning
Version numbers follow the MAJOR.MINOR.PATCH scheme. Backwards compatibility
breaking changes will be kept to a minimum but be aware that these can occur.
Lock your dependencies for production and test your code when upgrading.
## License
This bundle is under the MIT license. For the full copyright and license
information please view the LICENSE file that was distributed with this source code.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

57
vendor/endroid/qr-code/composer.json vendored Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "endroid/qr-code",
"description": "Endroid QR Code",
"keywords": ["endroid", "qrcode", "qr", "code", "php"],
"homepage": "https://github.com/endroid/qr-code",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Jeroen van den Enden",
"email": "info@endroid.nl"
}
],
"require": {
"php": "^8.0",
"bacon/bacon-qr-code": "^2.0.5"
},
"require-dev": {
"ext-gd": "*",
"endroid/quality": "dev-master",
"khanamiryan/qrcode-detector-decoder": "^1.0.4||^2.0.2",
"setasign/fpdf": "^1.8.2"
},
"conflict": {
"khanamiryan/qrcode-detector-decoder": "^1.0.6"
},
"suggest": {
"ext-gd": "Enables you to write PNG images",
"khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator",
"roave/security-advisories": "Makes sure package versions with known security issues are not installed",
"setasign/fpdf": "Enables you to use the PDF writer"
},
"autoload": {
"psr-4": {
"Endroid\\QrCode\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Endroid\\QrCode\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true,
"preferred-install": {
"endroid/*": "source"
},
"allow-plugins": {
"endroid/installer": true
}
},
"extra": {
"branch-alias": {
"dev-master": "4.x-dev"
}
}
}

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;
}