Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+18
View File
@@ -37,3 +37,21 @@ BaconQrCode comes with multiple back ends for rendering images. Currently includ
- `ImagickImageBackEnd`: renders raster images using the Imagick library
- `SvgImageBackEnd`: renders SVG files using XMLWriter
- `EpsImageBackEnd`: renders EPS files
### GDLib Renderer
GD library has so many limitations, that GD support is not added as backend, but as separated renderer.
Use `GDLibRenderer` instead of `ImageRenderer`. These are the limitations:
- Does not support gradient.
- Does not support any curves, so you QR code is always squared.
Example usage:
```php
use BaconQrCode\Renderer\GDLibRenderer;
use BaconQrCode\Writer;
$renderer = new GDLibRenderer(400);
$writer = new Writer($renderer);
$writer->writeFile('Hello World!', 'qrcode.png');
```
+50
View File
@@ -0,0 +1,50 @@
{
"name": "bacon/bacon-qr-code",
"description": "BaconQrCode is a QR code generator for PHP.",
"license": "BSD-2-Clause",
"homepage": "https://github.com/Bacon/BaconQrCode",
"require": {
"php": "^8.1",
"ext-iconv": "*",
"dasprid/enum": "^1.0.3"
},
"suggest": {
"ext-imagick": "to generate QR code images"
},
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"BaconQrCode\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"BaconQrCodeTest\\": "test/"
}
},
"require-dev": {
"phpunit/phpunit": "^10.5.11 || 11.0.4",
"spatie/phpunit-snapshot-assertions": "^5.1.5",
"squizlabs/php_codesniffer": "^3.9",
"phly/keep-a-changelog": "^2.12"
},
"config": {
"allow-plugins": {
"ocramius/package-versions": true,
"php-http/discovery": true
}
},
"archive": {
"exclude": [
"/test",
"/phpunit.xml.dist"
]
}
}
+4 -12
View File
@@ -16,21 +16,13 @@ final class BitArray
*
* @var SplFixedArray<int>
*/
private $bits;
/**
* Size of the bit array in bits.
*
* @var int
*/
private $size;
private SplFixedArray $bits;
/**
* Creates a new bit array with a given size.
*/
public function __construct(int $size = 0)
public function __construct(private int $size = 0)
{
$this->size = $size;
$this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0));
}
@@ -107,7 +99,7 @@ final class BitArray
}
$result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
return $result > $this->size ? $this->size : $result;
return min($result, $this->size);
}
/**
@@ -133,7 +125,7 @@ final class BitArray
}
$result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
return $result > $this->size ? $this->size : $result;
return min($result, $this->size);
}
/**
+6 -12
View File
@@ -17,36 +17,30 @@ class BitMatrix
{
/**
* Width of the bit matrix.
*
* @var int
*/
private $width;
private int $width;
/**
* Height of the bit matrix.
*
* @var int
*/
private $height;
private ?int $height;
/**
* Size in bits of each individual row.
*
* @var int
*/
private $rowSize;
private int $rowSize;
/**
* Bits representation.
*
* @var SplFixedArray<int>
*/
private $bits;
private SplFixedArray $bits;
/**
* @throws InvalidArgumentException if a dimension is smaller than zero
*/
public function __construct(int $width, int $height = null)
public function __construct(int $width, ?int $height = null)
{
if (null === $height) {
$height = $width;
@@ -138,7 +132,7 @@ class BitMatrix
/**
* A fast method to retrieve one row of data from the matrix as a BitArray.
*/
public function getRow(int $y, BitArray $row = null) : BitArray
public function getRow(int $y, ?BitArray $row = null) : BitArray
{
if (null === $row || $row->getSize() < $this->width) {
$row = new BitArray($this->width);
+7 -10
View File
@@ -69,29 +69,26 @@ final class CharacterSetEci extends AbstractEnum
protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK'];
protected const EUC_KR = [[30], 'EUC-KR'];
/**
* @var int[]
*/
private $values;
/**
* @var string[]
*/
private $otherEncodingNames;
private array $otherEncodingNames;
/**
* @var array<int, self>|null
*/
private static $valueToEci;
private static ?array $valueToEci;
/**
* @var array<string, self>|null
*/
private static $nameToEci;
private static ?array $nameToEci = null;
public function __construct(array $values, string ...$otherEncodingNames)
/**
* @param int[] $values
*/
public function __construct(private readonly array $values, string ...$otherEncodingNames)
{
$this->values = $values;
$this->otherEncodingNames = $otherEncodingNames;
}
+1 -17
View File
@@ -11,24 +11,8 @@ namespace BaconQrCode\Common;
*/
final class EcBlock
{
/**
* How many times the block is used.
*
* @var int
*/
private $count;
/**
* Number of data codewords.
*
* @var int
*/
private $dataCodewords;
public function __construct(int $count, int $dataCodewords)
public function __construct(private readonly int $count, private readonly int $dataCodewords)
{
$this->count = $count;
$this->dataCodewords = $dataCodewords;
}
/**
+2 -10
View File
@@ -12,23 +12,15 @@ namespace BaconQrCode\Common;
*/
final class EcBlocks
{
/**
* Number of EC codewords per block.
*
* @var int
*/
private $ecCodewordsPerBlock;
/**
* List of EC blocks.
*
* @var EcBlock[]
*/
private $ecBlocks;
private array $ecBlocks;
public function __construct(int $ecCodewordsPerBlock, EcBlock ...$ecBlocks)
public function __construct(private readonly int $ecCodewordsPerBlock, EcBlock ...$ecBlocks)
{
$this->ecCodewordsPerBlock = $ecCodewordsPerBlock;
$this->ecBlocks = $ecBlocks;
}
@@ -21,14 +21,8 @@ final class ErrorCorrectionLevel extends AbstractEnum
protected const Q = [0x03];
protected const H = [0x02];
/**
* @var int
*/
private $bits;
protected function __construct(int $bits)
protected function __construct(private readonly int $bits)
{
$this->bits = $bits;
}
/**
@@ -62,23 +62,16 @@ class FormatInformation
/**
* Offset i holds the number of 1 bits in the binary representation of i.
*
* @var array
* @var int[]
*/
private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
/**
* Error correction level.
*
* @var ErrorCorrectionLevel
*/
private $ecLevel;
private ErrorCorrectionLevel $ecLevel;
/**
* Data mask.
*
* @var int
*/
private $dataMask;
private int $dataMask;
protected function __construct(int $formatInfo)
{
+5 -12
View File
@@ -33,19 +33,12 @@ final class Mode extends AbstractEnum
protected const HANZI = [[8, 10, 12], 0x0d];
/**
* @var int[]
* @param int[] $characterCountBitsForVersions
*/
private $characterCountBitsForVersions;
/**
* @var int
*/
private $bits;
protected function __construct(array $characterCountBitsForVersions, int $bits)
{
$this->characterCountBitsForVersions = $characterCountBitsForVersions;
$this->bits = $bits;
protected function __construct(
private readonly array $characterCountBitsForVersions,
private readonly int $bits
) {
}
/**
+11 -25
View File
@@ -16,73 +16,59 @@ final class ReedSolomonCodec
{
/**
* Symbol size in bits.
*
* @var int
*/
private $symbolSize;
private int $symbolSize;
/**
* Block size in symbols.
*
* @var int
*/
private $blockSize;
private int $blockSize;
/**
* First root of RS code generator polynomial, index form.
*
* @var int
*/
private $firstRoot;
private int $firstRoot;
/**
* Primitive element to generate polynomial roots, index form.
*
* @var int
*/
private $primitive;
private int $primitive;
/**
* Prim-th root of 1, index form.
*
* @var int
*/
private $iPrimitive;
private int $iPrimitive;
/**
* RS code generator polynomial degree (number of roots).
*
* @var int
*/
private $numRoots;
private int $numRoots;
/**
* Padding bytes at front of shortened block.
*
* @var int
*/
private $padding;
private int $padding;
/**
* Log lookup table.
*
* @var SplFixedArray
*/
private $alphaTo;
private SplFixedArray $alphaTo;
/**
* Anti-Log lookup table.
*
* @var SplFixedArray
*/
private $indexOf;
private SplFixedArray $indexOf;
/**
* Generator polynomial.
*
* @var SplFixedArray
*/
private $generatorPoly;
private SplFixedArray $generatorPoly;
/**
* @throws InvalidArgumentException if symbol size ist not between 0 and 8
@@ -222,7 +208,7 @@ final class ReedSolomonCodec
/**
* Decodes received data.
*/
public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int
public function decode(SplFixedArray $data, ?SplFixedArray $erasures = null) : ?int
{
// This speeds up the initialization a bit.
$numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
+7 -11
View File
@@ -50,38 +50,34 @@ final class Version
/**
* Version number of this version.
*
* @var int
*/
private $versionNumber;
private int $versionNumber;
/**
* Alignment pattern centers.
*
* @var SplFixedArray
* @var SplFixedArray|array
*/
private $alignmentPatternCenters;
private SplFixedArray|array $alignmentPatternCenters;
/**
* Error correction blocks.
*
* @var EcBlocks[]
*/
private $ecBlocks;
private array $ecBlocks;
/**
* Total number of codewords.
*
* @var int
*/
private $totalCodewords;
private null|int|float $totalCodewords;
/**
* Cached version instances.
*
* @var array<int, self>|null
*/
private static $versions;
private static ?array $versions = null;
/**
* @param int[] $alignmentPatternCenters
@@ -300,7 +296,7 @@ final class Version
[6, 26],
new EcBlocks(20, new EcBlock(1, 80)),
new EcBlocks(18, new EcBlock(2, 32)),
new EcBlocks(26, new EcBlock(3, 24)),
new EcBlocks(26, new EcBlock(2, 24)),
new EcBlocks(16, new EcBlock(4, 9))
),
new self(
+6 -20
View File
@@ -10,30 +10,16 @@ use SplFixedArray;
*/
final class BlockPair
{
/**
* Data bytes in the block.
*
* @var SplFixedArray<int>
*/
private $dataBytes;
/**
* Error correction bytes in the block.
*
* @var SplFixedArray<int>
*/
private $errorCorrectionBytes;
/**
* Creates a new block pair.
*
* @param SplFixedArray<int> $data
* @param SplFixedArray<int> $errorCorrection
* @param SplFixedArray<int> $dataBytes Data bytes in the block.
* @param SplFixedArray<int> $errorCorrectionBytes Error correction bytes in the block.
*/
public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection)
{
$this->dataBytes = $data;
$this->errorCorrectionBytes = $errorCorrection;
public function __construct(
private readonly SplFixedArray $dataBytes,
private readonly SplFixedArray $errorCorrectionBytes
) {
}
/**
+2 -18
View File
@@ -16,26 +16,10 @@ final class ByteMatrix
*
* @var SplFixedArray<SplFixedArray<int>>
*/
private $bytes;
private SplFixedArray $bytes;
/**
* Width of the matrix.
*
* @var int
*/
private $width;
/**
* Height of the matrix.
*
* @var int
*/
private $height;
public function __construct(int $width, int $height)
public function __construct(private readonly int $width, private readonly int $height)
{
$this->height = $height;
$this->width = $width;
$this->bytes = new SplFixedArray($height);
for ($y = 0; $y < $height; ++$y) {
+23 -12
View File
@@ -20,7 +20,10 @@ final class Encoder
/**
* Default byte encoding.
*/
public const DEFAULT_BYTE_MODE_ECODING = 'ISO-8859-1';
public const DEFAULT_BYTE_MODE_ENCODING = 'ISO-8859-1';
/** @deprecated use DEFAULT_BYTE_MODE_ENCODING */
public const DEFAULT_BYTE_MODE_ECODING = self::DEFAULT_BYTE_MODE_ENCODING;
/**
* The original table is defined in the table 5 of JISX0510:2004 (p.19).
@@ -37,9 +40,9 @@ final class Encoder
/**
* Codec cache.
*
* @var array
* @var array<string,ReedSolomonCodec>
*/
private static $codecs = [];
private static array $codecs = [];
/**
* Encodes "content" with the error correction level "ecLevel".
@@ -47,8 +50,10 @@ final class Encoder
public static function encode(
string $content,
ErrorCorrectionLevel $ecLevel,
string $encoding = self::DEFAULT_BYTE_MODE_ECODING,
?Version $forcedVersion = null
string $encoding = self::DEFAULT_BYTE_MODE_ENCODING,
?Version $forcedVersion = null,
// Barcode scanner might not be able to read the encoded message of the QR code with the prefix ECI of UTF-8
bool $prefixEci = true
) : QrCode {
// Pick an encoding mode appropriate for the content. Note that this
// will not attempt to use multiple modes / segments even if that were
@@ -60,7 +65,7 @@ final class Encoder
$headerBits = new BitArray();
// Append ECI segment if applicable
if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) {
if ($prefixEci && Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ENCODING !== $encoding) {
$eci = CharacterSetEci::getCharacterSetEciByName($encoding);
if (null !== $eci) {
@@ -155,7 +160,7 @@ final class Encoder
/**
* Chooses the best mode for a given content.
*/
private static function chooseMode(string $content, string $encoding = null) : Mode
private static function chooseMode(string $content, ?string $encoding = null) : Mode
{
if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) {
return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE();
@@ -217,7 +222,7 @@ final class Encoder
}
for ($i = 0; $i < $length; $i += 2) {
$byte = $bytes[$i] & 0xff;
$byte = ord($bytes[$i]) & 0xff;
if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) {
return false;
@@ -629,17 +634,23 @@ final class Encoder
*/
private static function appendKanjiBytes(string $content, BitArray $bits) : void
{
if (strlen($content) % 2 > 0) {
$bytes = @iconv('utf-8', 'SHIFT-JIS', $content);
if (false === $bytes) {
throw new WriterException('Content could not be converted to SHIFT-JIS');
}
if (strlen($bytes) % 2 > 0) {
// We just do a simple length check here. The for loop will check
// individual characters.
throw new WriterException('Content does not seem to be encoded in SHIFT-JIS');
}
$length = strlen($content);
$length = strlen($bytes);
for ($i = 0; $i < $length; $i += 2) {
$byte1 = ord($content[$i]) & 0xff;
$byte2 = ord($content[$i + 1]) & 0xff;
$byte1 = ord($bytes[$i]) & 0xff;
$byte2 = ord($bytes[$i + 1]) & 0xff;
$code = ($byte1 << 8) | $byte2;
if ($code >= 0x8140 && $code <= 0x9ffc) {
+4 -4
View File
@@ -14,10 +14,10 @@ final class MaskUtil
/**#@+
* Penalty weights from section 6.8.2.1
*/
const N1 = 3;
const N2 = 3;
const N3 = 40;
const N4 = 10;
public const N1 = 3;
public const N2 = 3;
public const N3 = 40;
public const N4 = 10;
/**#@-*/
private function __construct()
+8 -41
View File
@@ -17,51 +17,23 @@ final class QrCode
*/
public const NUM_MASK_PATTERNS = 8;
/**
* Mode of the QR code.
*
* @var Mode
*/
private $mode;
/**
* EC level of the QR code.
*
* @var ErrorCorrectionLevel
*/
private $errorCorrectionLevel;
/**
* Version of the QR code.
*
* @var Version
*/
private $version;
/**
* Mask pattern of the QR code.
*
* @var int
*/
private $maskPattern = -1;
private int $maskPattern = -1;
/**
* Matrix of the QR code.
*
* @var ByteMatrix
*/
private $matrix;
private ByteMatrix $matrix;
public function __construct(
Mode $mode,
ErrorCorrectionLevel $errorCorrectionLevel,
Version $version,
int $maskPattern,
ByteMatrix $matrix
private readonly Mode $mode,
private readonly ErrorCorrectionLevel $errorCorrectionLevel,
private readonly Version $version,
int $maskPattern,
ByteMatrix $matrix
) {
$this->mode = $mode;
$this->errorCorrectionLevel = $errorCorrectionLevel;
$this->version = $version;
$this->maskPattern = $maskPattern;
$this->matrix = $matrix;
}
@@ -98,12 +70,7 @@ final class QrCode
return $this->maskPattern;
}
/**
* Gets the matrix.
*
* @return ByteMatrix
*/
public function getMatrix()
public function getMatrix(): ByteMatrix
{
return $this->matrix;
}
+1 -14
View File
@@ -7,27 +7,14 @@ use BaconQrCode\Exception;
final class Alpha implements ColorInterface
{
/**
* @var int
*/
private $alpha;
/**
* @var ColorInterface
*/
private $baseColor;
/**
* @param int $alpha the alpha value, 0 to 100
*/
public function __construct(int $alpha, ColorInterface $baseColor)
public function __construct(private readonly int $alpha, private readonly ColorInterface $baseColor)
{
if ($alpha < 0 || $alpha > 100) {
throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100');
}
$this->alpha = $alpha;
$this->baseColor = $baseColor;
}
public function getAlpha() : int
+6 -27
View File
@@ -7,34 +7,18 @@ use BaconQrCode\Exception;
final class Cmyk implements ColorInterface
{
/**
* @var int
*/
private $cyan;
/**
* @var int
*/
private $magenta;
/**
* @var int
*/
private $yellow;
/**
* @var int
*/
private $black;
/**
* @param int $cyan the cyan amount, 0 to 100
* @param int $magenta the magenta amount, 0 to 100
* @param int $yellow the yellow amount, 0 to 100
* @param int $black the black amount, 0 to 100
*/
public function __construct(int $cyan, int $magenta, int $yellow, int $black)
{
public function __construct(
private readonly int $cyan,
private readonly int $magenta,
private readonly int $yellow,
private readonly int $black
) {
if ($cyan < 0 || $cyan > 100) {
throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100');
}
@@ -50,11 +34,6 @@ final class Cmyk implements ColorInterface
if ($black < 0 || $black > 100) {
throw new Exception\InvalidArgumentException('Black must be between 0 and 100');
}
$this->cyan = $cyan;
$this->magenta = $magenta;
$this->yellow = $yellow;
$this->black = $black;
}
public function getCyan() : int
+1 -8
View File
@@ -7,21 +7,14 @@ use BaconQrCode\Exception;
final class Gray implements ColorInterface
{
/**
* @var int
*/
private $gray;
/**
* @param int $gray the gray value between 0 (black) and 100 (white)
*/
public function __construct(int $gray)
public function __construct(private readonly int $gray)
{
if ($gray < 0 || $gray > 100) {
throw new Exception\InvalidArgumentException('Gray must be between 0 and 100');
}
$this->gray = (int) $gray;
}
public function getGray() : int
+5 -20
View File
@@ -7,27 +7,12 @@ use BaconQrCode\Exception;
final class Rgb implements ColorInterface
{
/**
* @var int
*/
private $red;
/**
* @var int
*/
private $green;
/**
* @var int
*/
private $blue;
/**
* @param int $red the red amount of the color, 0 to 255
* @param int $green the green amount of the color, 0 to 255
* @param int $blue the blue amount of the color, 0 to 255
*/
public function __construct(int $red, int $green, int $blue)
public function __construct(private readonly int $red, private readonly int $green, private readonly int $blue)
{
if ($red < 0 || $red > 255) {
throw new Exception\InvalidArgumentException('Red must be between 0 and 255');
@@ -40,10 +25,6 @@ final class Rgb implements ColorInterface
if ($blue < 0 || $blue > 255) {
throw new Exception\InvalidArgumentException('Blue must be between 0 and 255');
}
$this->red = $red;
$this->green = $green;
$this->blue = $blue;
}
public function getRed() : int
@@ -73,6 +54,10 @@ final class Rgb implements ColorInterface
$y = 1 - ($this->blue / 255);
$k = min($c, $m, $y);
if ($k === 0) {
return new Cmyk(0, 0, 0, 0);
}
return new Cmyk(
(int) (100 * ($c - $k) / (1 - $k)),
(int) (100 * ($m - $k) / (1 - $k)),
@@ -10,20 +10,8 @@ use BaconQrCode\Renderer\Path\Path;
*/
final class CompositeEye implements EyeInterface
{
/**
* @var EyeInterface
*/
private $externalEye;
/**
* @var EyeInterface
*/
private $internalEye;
public function __construct(EyeInterface $externalEye, EyeInterface $internalEye)
public function __construct(private readonly EyeInterface $externalEye, private readonly EyeInterface $internalEye)
{
$this->externalEye = $externalEye;
$this->internalEye = $internalEye;
}
public function getExternalPath() : Path
@@ -12,14 +12,8 @@ use BaconQrCode\Renderer\Path\Path;
*/
final class ModuleEye implements EyeInterface
{
/**
* @var ModuleInterface
*/
private $module;
public function __construct(ModuleInterface $module)
public function __construct(private readonly ModuleInterface $module)
{
$this->module = $module;
}
public function getExternalPath() : Path
@@ -0,0 +1,56 @@
<?php
declare(strict_types = 1);
namespace BaconQrCode\Renderer\Eye;
use BaconQrCode\Renderer\Path\Path;
/**
* Renders the outer eye as solid with a curved corner and inner eye as a circle.
*/
final class PointyEye implements EyeInterface
{
/**
* @var self|null
*/
private static $instance;
private function __construct()
{
}
public static function instance() : self
{
return self::$instance ?: self::$instance = new self();
}
public function getExternalPath() : Path
{
return (new Path())
->move(-3.5, 3.5)
->line(-3.5, 0)
->ellipticArc(3.5, 3.5, 0, false, true, 0, -3.5)
->line(3.5, -3.5)
->line(3.5, 3.5)
->close()
->move(2.5, 0)
->ellipticArc(2.5, 2.5, 0, false, true, 0, 2.5)
->ellipticArc(2.5, 2.5, 0, false, true, -2.5, 0)
->ellipticArc(2.5, 2.5, 0, false, true, 0, -2.5)
->ellipticArc(2.5, 2.5, 0, false, true, 2.5, 0)
->close()
;
}
public function getInternalPath() : Path
{
return (new Path())
->move(1.5, 0)
->ellipticArc(1.5, 1.5, 0., false, true, 0., 1.5)
->ellipticArc(1.5, 1.5, 0., false, true, -1.5, 0.)
->ellipticArc(1.5, 1.5, 0., false, true, 0., -1.5)
->ellipticArc(1.5, 1.5, 0., false, true, 1.5, 0.)
->close()
;
}
}
@@ -10,10 +10,7 @@ use BaconQrCode\Renderer\Path\Path;
*/
final class SimpleCircleEye implements EyeInterface
{
/**
* @var self|null
*/
private static $instance;
private static ?SimpleCircleEye $instance = null;
private function __construct()
{
@@ -10,10 +10,7 @@ use BaconQrCode\Renderer\Path\Path;
*/
final class SquareEye implements EyeInterface
{
/**
* @var self|null
*/
private static $instance;
private static ?SquareEye $instance = null;
private function __construct()
{
@@ -0,0 +1,238 @@
<?php
declare(strict_types=1);
namespace BaconQrCode\Renderer;
use BaconQrCode\Encoder\ByteMatrix;
use BaconQrCode\Encoder\MatrixUtil;
use BaconQrCode\Encoder\QrCode;
use BaconQrCode\Exception\InvalidArgumentException;
use BaconQrCode\Exception\RuntimeException;
use BaconQrCode\Renderer\Color\Alpha;
use BaconQrCode\Renderer\Color\ColorInterface;
use BaconQrCode\Renderer\RendererStyle\EyeFill;
use BaconQrCode\Renderer\RendererStyle\Fill;
use GdImage;
final class GDLibRenderer implements RendererInterface
{
private ?GdImage $image;
/**
* @var array<string, int>
*/
private array $colors;
public function __construct(
private int $size,
private int $margin = 4,
private string $imageFormat = 'png',
private int $compressionQuality = 9,
private ?Fill $fill = null
) {
if (! extension_loaded('gd') || ! function_exists('gd_info')) {
throw new RuntimeException('You need to install the GD extension to use this back end');
}
if ($this->fill === null) {
$this->fill = Fill::default();
}
if ($this->fill->hasGradientFill()) {
throw new InvalidArgumentException('GDLibRenderer does not support gradients');
}
}
/**
* @throws InvalidArgumentException if matrix width doesn't match height
*/
public function render(QrCode $qrCode): string
{
$matrix = $qrCode->getMatrix();
$matrixSize = $matrix->getWidth();
if ($matrixSize !== $matrix->getHeight()) {
throw new InvalidArgumentException('Matrix must have the same width and height');
}
MatrixUtil::removePositionDetectionPatterns($matrix);
$this->newImage();
$this->draw($matrix);
return $this->renderImage();
}
private function newImage(): void
{
$img = imagecreatetruecolor($this->size, $this->size);
if ($img === false) {
throw new RuntimeException('Failed to create image of that size');
}
$this->image = $img;
imagealphablending($this->image, false);
imagesavealpha($this->image, true);
$bg = $this->getColor($this->fill->getBackgroundColor());
imagefilledrectangle($this->image, 0, 0, $this->size, $this->size, $bg);
imagealphablending($this->image, true);
}
private function draw(ByteMatrix $matrix): void
{
$matrixSize = $matrix->getWidth();
$pointsOnSide = $matrix->getWidth() + $this->margin * 2;
$pointInPx = $this->size / $pointsOnSide;
$this->drawEye(0, 0, $pointInPx, $this->fill->getTopLeftEyeFill());
$this->drawEye($matrixSize - 7, 0, $pointInPx, $this->fill->getTopRightEyeFill());
$this->drawEye(0, $matrixSize - 7, $pointInPx, $this->fill->getBottomLeftEyeFill());
$rows = $matrix->getArray()->toArray();
$color = $this->getColor($this->fill->getForegroundColor());
for ($y = 0; $y < $matrixSize; $y += 1) {
for ($x = 0; $x < $matrixSize; $x += 1) {
if (! $rows[$y][$x]) {
continue;
}
$points = $this->normalizePoints([
($this->margin + $x) * $pointInPx, ($this->margin + $y) * $pointInPx,
($this->margin + $x + 1) * $pointInPx, ($this->margin + $y) * $pointInPx,
($this->margin + $x + 1) * $pointInPx, ($this->margin + $y + 1) * $pointInPx,
($this->margin + $x) * $pointInPx, ($this->margin + $y + 1) * $pointInPx,
]);
imagefilledpolygon($this->image, $points, $color);
}
}
}
private function drawEye(int $xOffset, int $yOffset, float $pointInPx, EyeFill $eyeFill): void
{
$internalColor = $this->getColor($eyeFill->inheritsInternalColor()
? $this->fill->getForegroundColor()
: $eyeFill->getInternalColor());
$externalColor = $this->getColor($eyeFill->inheritsExternalColor()
? $this->fill->getForegroundColor()
: $eyeFill->getExternalColor());
for ($y = 0; $y < 7; $y += 1) {
for ($x = 0; $x < 7; $x += 1) {
if ((($y === 1 || $y === 5) && $x > 0 && $x < 6) || (($x === 1 || $x === 5) && $y > 0 && $y < 6)) {
continue;
}
$points = $this->normalizePoints([
($this->margin + $x + $xOffset) * $pointInPx, ($this->margin + $y + $yOffset) * $pointInPx,
($this->margin + $x + $xOffset + 1) * $pointInPx, ($this->margin + $y + $yOffset) * $pointInPx,
($this->margin + $x + $xOffset + 1) * $pointInPx, ($this->margin + $y + $yOffset + 1) * $pointInPx,
($this->margin + $x + $xOffset) * $pointInPx, ($this->margin + $y + $yOffset + 1) * $pointInPx,
]);
if ($y > 1 && $y < 5 && $x > 1 && $x < 5) {
imagefilledpolygon($this->image, $points, $internalColor);
} else {
imagefilledpolygon($this->image, $points, $externalColor);
}
}
}
}
/**
* Normalize points will trim right and bottom line by 1 pixel.
* Otherwise pixels of neighbors are overlapping which leads to issue with transparency and small QR codes.
*/
private function normalizePoints(array $points): array
{
$maxX = $maxY = 0;
for ($i = 0; $i < count($points); $i += 2) {
// Do manual round as GD just removes decimal part
$points[$i] = $newX = round($points[$i]);
$points[$i + 1] = $newY = round($points[$i + 1]);
$maxX = max($maxX, $newX);
$maxY = max($maxY, $newY);
}
// Do trimming only if there are 4 points (8 coordinates), assumes this is square.
for ($i = 0; $i < count($points); $i += 2) {
$points[$i] = min($points[$i], $maxX - 1);
$points[$i + 1] = min($points[$i + 1], $maxY - 1);
}
return $points;
}
private function renderImage(): string
{
ob_start();
$quality = $this->compressionQuality;
switch ($this->imageFormat) {
case 'png':
if ($quality > 9 || $quality < 0) {
$quality = 9;
}
imagepng($this->image, null, $quality);
break;
case 'gif':
imagegif($this->image, null);
break;
case 'jpeg':
case 'jpg':
if ($quality > 100 || $quality < 0) {
$quality = 85;
}
imagejpeg($this->image, null, $quality);
break;
default:
ob_end_clean();
throw new InvalidArgumentException(
'Supported image formats are jpeg, png and gif, got: ' . $this->imageFormat
);
}
imagedestroy($this->image);
$this->colors = [];
$this->image = null;
return ob_get_clean();
}
private function getColor(ColorInterface $color): int
{
$alpha = 100;
if ($color instanceof Alpha) {
$alpha = $color->getAlpha();
$color = $color->getBaseColor();
}
$rgb = $color->toRgb();
$colorKey = sprintf('%02X%02X%02X%02X', $rgb->getRed(), $rgb->getGreen(), $rgb->getBlue(), $alpha);
if (! isset($this->colors[$colorKey])) {
$colorId = imagecolorallocatealpha(
$this->image,
$rgb->getRed(),
$rgb->getGreen(),
$rgb->getBlue(),
(int)((100 - $alpha) / 100 * 127) // Alpha for GD is in range 0 (opaque) - 127 (transparent)
);
if ($colorId === false) {
throw new RuntimeException('Failed to create color: #' . $colorKey);
}
$this->colors[$colorKey] = $colorId;
}
return $this->colors[$colorKey];
}
}
@@ -22,10 +22,7 @@ final class EpsImageBackEnd implements ImageBackEndInterface
{
private const PRECISION = 3;
/**
* @var string|null
*/
private $eps;
private ?string $eps;
public function new(int $size, ColorInterface $backgroundColor) : void
{
@@ -23,40 +23,22 @@ use ImagickPixel;
final class ImagickImageBackEnd implements ImageBackEndInterface
{
/**
* @var string
*/
private $imageFormat;
private string $imageFormat;
/**
* @var int
*/
private $compressionQuality;
private int $compressionQuality;
/**
* @var Imagick|null
*/
private $image;
private ?Imagick $image;
/**
* @var ImagickDraw|null
*/
private $draw;
private ?ImagickDraw $draw;
/**
* @var int|null
*/
private $gradientCount;
private ?int $gradientCount;
/**
* @var TransformationMatrix[]|null
*/
private $matrices;
private ?array $matrices;
/**
* @var int|null
*/
private $matrixIndex;
private ?int $matrixIndex;
public function __construct(string $imageFormat = 'png', int $compressionQuality = 100)
{
@@ -19,28 +19,16 @@ use XMLWriter;
final class SvgImageBackEnd implements ImageBackEndInterface
{
private const PRECISION = 3;
private const SCALE_FORMAT = 'scale(%.' . self::PRECISION . 'F)';
private const TRANSLATE_FORMAT = 'translate(%.' . self::PRECISION . 'F,%.' . self::PRECISION . 'F)';
private const SCALE_FORMAT = 'scale(%.' . self::PRECISION . 'F)';
private const TRANSLATE_FORMAT = 'translate(%.' . self::PRECISION . 'F,%.' . self::PRECISION . 'F)';
/**
* @var XMLWriter|null
*/
private $xmlWriter;
private ?XMLWriter $xmlWriter;
/**
* @var int[]|null
*/
private $stack;
private ?array $stack;
/**
* @var int|null
*/
private $currentStack;
private ?int $currentStack;
/**
* @var int|null
*/
private $gradientCount;
private ?int $gradientCount;
public function __construct()
{
@@ -99,7 +87,7 @@ final class SvgImageBackEnd implements ImageBackEndInterface
$this->xmlWriter->startElement('g');
$this->xmlWriter->writeAttribute(
'transform',
sprintf(self::SCALE_FORMAT, round($size, self::PRECISION))
sprintf(self::SCALE_FORMAT, round($size, self::PRECISION))
);
++$this->stack[$this->currentStack];
}
@@ -113,7 +101,7 @@ final class SvgImageBackEnd implements ImageBackEndInterface
$this->xmlWriter->startElement('g');
$this->xmlWriter->writeAttribute(
'transform',
sprintf(self::TRANSLATE_FORMAT, round($x, self::PRECISION), round($y, self::PRECISION))
sprintf(self::TRANSLATE_FORMAT, round($x, self::PRECISION), round($y, self::PRECISION))
);
++$this->stack[$this->currentStack];
}
@@ -336,7 +324,7 @@ final class SvgImageBackEnd implements ImageBackEndInterface
$this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor));
if ($startColor instanceof Alpha) {
$this->xmlWriter->writeAttribute('stop-opacity', $startColor->getAlpha());
$this->xmlWriter->writeAttribute('stop-opacity', (string) $startColor->getAlpha());
}
$this->xmlWriter->endElement();
@@ -346,7 +334,7 @@ final class SvgImageBackEnd implements ImageBackEndInterface
$this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor));
if ($endColor instanceof Alpha) {
$this->xmlWriter->writeAttribute('stop-opacity', $endColor->getAlpha());
$this->xmlWriter->writeAttribute('stop-opacity', (string) $endColor->getAlpha());
}
$this->xmlWriter->endElement();
@@ -8,7 +8,7 @@ final class TransformationMatrix
/**
* @var float[]
*/
private $values;
private array $values;
public function __construct()
{
+16 -18
View File
@@ -13,20 +13,10 @@ use BaconQrCode\Renderer\RendererStyle\RendererStyle;
final class ImageRenderer implements RendererInterface
{
/**
* @var RendererStyle
*/
private $rendererStyle;
/**
* @var ImageBackEndInterface
*/
private $imageBackEnd;
public function __construct(RendererStyle $rendererStyle, ImageBackEndInterface $imageBackEnd)
{
$this->rendererStyle = $rendererStyle;
$this->imageBackEnd = $imageBackEnd;
public function __construct(
private readonly RendererStyle $rendererStyle,
private readonly ImageBackEndInterface $imageBackEnd
) {
}
/**
@@ -122,8 +112,12 @@ final class ImageRenderer implements RendererInterface
) : Path {
if ($fill->inheritsBothColors()) {
return $modulePath
->append($externalPath->translate($xTranslation, $yTranslation))
->append($internalPath->translate($xTranslation, $yTranslation));
->append(
$externalPath->rotate($rotation)->translate($xTranslation, $yTranslation)
)
->append(
$internalPath->rotate($rotation)->translate($xTranslation, $yTranslation)
);
}
$this->imageBackEnd->push();
@@ -134,13 +128,17 @@ final class ImageRenderer implements RendererInterface
}
if ($fill->inheritsExternalColor()) {
$modulePath = $modulePath->append($externalPath->translate($xTranslation, $yTranslation));
$modulePath = $modulePath->append(
$externalPath->rotate($rotation)->translate($xTranslation, $yTranslation)
);
} else {
$this->imageBackEnd->drawPathWithColor($externalPath, $fill->getExternalColor());
}
if ($fill->inheritsInternalColor()) {
$modulePath = $modulePath->append($internalPath->translate($xTranslation, $yTranslation));
$modulePath = $modulePath->append(
$internalPath->rotate($rotation)->translate($xTranslation, $yTranslation)
);
} else {
$this->imageBackEnd->drawPathWithColor($internalPath, $fill->getInternalColor());
}
@@ -16,18 +16,11 @@ final class DotsModule implements ModuleInterface
public const MEDIUM = .8;
public const SMALL = .6;
/**
* @var float
*/
private $size;
public function __construct(float $size)
public function __construct(private readonly float $size)
{
if ($size <= 0 || $size > 1) {
throw new InvalidArgumentException('Size must between 0 (exclusive) and 1 (inclusive)');
}
$this->size = $size;
}
public function createPath(ByteMatrix $matrix) : Path
@@ -5,44 +5,26 @@ namespace BaconQrCode\Renderer\Module\EdgeIterator;
final class Edge
{
/**
* @var bool
*/
private $positive;
/**
* @var array<int[]>
*/
private $points = [];
private array $points = [];
/**
* @var array<int[]>|null
*/
private $simplifiedPoints;
private ?array $simplifiedPoints = null;
/**
* @var int
*/
private $minX = PHP_INT_MAX;
private int $minX = PHP_INT_MAX;
/**
* @var int
*/
private $minY = PHP_INT_MAX;
private int $minY = PHP_INT_MAX;
/**
* @var int
*/
private $maxX = -1;
private int $maxX = -1;
/**
* @var int
*/
private $maxY = -1;
private int $maxY = -1;
public function __construct(bool $positive)
public function __construct(private readonly bool $positive)
{
$this->positive = $positive;
}
public function addPoint(int $x, int $y) : void
@@ -15,22 +15,13 @@ final class EdgeIterator implements IteratorAggregate
/**
* @var int[]
*/
private $bytes = [];
private array $bytes = [];
/**
* @var int
*/
private $size;
private ?int $size;
/**
* @var int
*/
private $width;
private int $width;
/**
* @var int
*/
private $height;
private int $height;
public function __construct(ByteMatrix $matrix)
{
@@ -41,7 +32,7 @@ final class EdgeIterator implements IteratorAggregate
}
/**
* @return Edge[]
* @return Traversable<Edge>
*/
public function getIterator() : Traversable
{
@@ -17,12 +17,7 @@ final class RoundnessModule implements ModuleInterface
public const MEDIUM = .5;
public const SOFT = .25;
/**
* @var float
*/
private $intensity;
public function __construct(float $intensity)
public function __construct(private float $intensity)
{
if ($intensity <= 0 || $intensity > 1) {
throw new InvalidArgumentException('Intensity must between 0 (exclusive) and 1 (inclusive)');
@@ -12,10 +12,7 @@ use BaconQrCode\Renderer\Path\Path;
*/
final class SquareModule implements ModuleInterface
{
/**
* @var self|null
*/
private static $instance;
private static ?SquareModule $instance = null;
private function __construct()
{
+9 -4
View File
@@ -5,10 +5,7 @@ namespace BaconQrCode\Renderer\Path;
final class Close implements OperationInterface
{
/**
* @var self|null
*/
private static $instance;
private static ?Close $instance = null;
private function __construct()
{
@@ -26,4 +23,12 @@ final class Close implements OperationInterface
{
return $this;
}
/**
* @return self
*/
public function rotate(int $degrees) : OperationInterface
{
return $this;
}
}
+32 -38
View File
@@ -5,44 +5,14 @@ namespace BaconQrCode\Renderer\Path;
final class Curve implements OperationInterface
{
/**
* @var float
*/
private $x1;
/**
* @var float
*/
private $y1;
/**
* @var float
*/
private $x2;
/**
* @var float
*/
private $y2;
/**
* @var float
*/
private $x3;
/**
* @var float
*/
private $y3;
public function __construct(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3)
{
$this->x1 = $x1;
$this->y1 = $y1;
$this->x2 = $x2;
$this->y2 = $y2;
$this->x3 = $x3;
$this->y3 = $y3;
public function __construct(
private readonly float $x1,
private readonly float $y1,
private readonly float $x2,
private readonly float $y2,
private readonly float $x3,
private readonly float $y3
) {
}
public function getX1() : float
@@ -89,4 +59,28 @@ final class Curve implements OperationInterface
$this->y3 + $y
);
}
/**
* @return self
*/
public function rotate(int $degrees) : OperationInterface
{
$radians = deg2rad($degrees);
$sin = sin($radians);
$cos = cos($radians);
$x1r = $this->x1 * $cos - $this->y1 * $sin;
$y1r = $this->x1 * $sin + $this->y1 * $cos;
$x2r = $this->x2 * $cos - $this->y2 * $sin;
$y2r = $this->x2 * $sin + $this->y2 * $cos;
$x3r = $this->x3 * $cos - $this->y3 * $sin;
$y3r = $this->x3 * $sin + $this->y3 * $cos;
return new self(
$x1r,
$y1r,
$x2r,
$y2r,
$x3r,
$y3r
);
}
}
@@ -7,57 +7,22 @@ final class EllipticArc implements OperationInterface
{
private const ZERO_TOLERANCE = 1e-05;
/**
* @var float
*/
private $xRadius;
/**
* @var float
*/
private $yRadius;
/**
* @var float
*/
private $xAxisAngle;
/**
* @var bool
*/
private $largeArc;
/**
* @var bool
*/
private $sweep;
/**
* @var float
*/
private $x;
/**
* @var float
*/
private $y;
private float $xRadius;
private float $yRadius;
private float $xAxisAngle;
public function __construct(
float $xRadius,
float $yRadius,
float $xAxisAngle,
bool $largeArc,
bool $sweep,
float $x,
float $y
float $xRadius,
float $yRadius,
float $xAxisAngle,
private readonly bool $largeArc,
private readonly bool $sweep,
private readonly float $x,
private readonly float $y
) {
$this->xRadius = abs($xRadius);
$this->yRadius = abs($yRadius);
$this->xAxisAngle = $xAxisAngle % 360;
$this->largeArc = $largeArc;
$this->sweep = $sweep;
$this->x = $x;
$this->y = $y;
}
public function getXRadius() : float
@@ -111,6 +76,27 @@ final class EllipticArc implements OperationInterface
);
}
/**
* @return self
*/
public function rotate(int $degrees) : OperationInterface
{
$radians = deg2rad($degrees);
$sin = sin($radians);
$cos = cos($radians);
$xr = $this->x * $cos - $this->y * $sin;
$yr = $this->x * $sin + $this->y * $cos;
return new self(
$this->xRadius,
$this->yRadius,
$this->xAxisAngle,
$this->largeArc,
$this->sweep,
$xr,
$yr
);
}
/**
* Converts the elliptic arc to multiple curves.
*
@@ -136,7 +122,7 @@ final class EllipticArc implements OperationInterface
/**
* @return Curve[]
*/
private function createCurves(float $fromX, $fromY) : array
private function createCurves(float $fromX, float $fromY) : array
{
$xAngle = deg2rad($this->xAxisAngle);
list($centerX, $centerY, $radiusX, $radiusY, $startAngle, $deltaAngle) =
@@ -179,7 +165,7 @@ final class EllipticArc implements OperationInterface
/**
* @return float[]
*/
private function calculateCenterPointParameters(float $fromX, float $fromY, float $xAngle)
private function calculateCenterPointParameters(float $fromX, float $fromY, float $xAngle): array
{
$rX = $this->xRadius;
$rY = $this->yRadius;
+14 -13
View File
@@ -5,20 +5,8 @@ namespace BaconQrCode\Renderer\Path;
final class Line implements OperationInterface
{
/**
* @var float
*/
private $x;
/**
* @var float
*/
private $y;
public function __construct(float $x, float $y)
public function __construct(private readonly float $x, private readonly float $y)
{
$this->x = $x;
$this->y = $y;
}
public function getX() : float
@@ -38,4 +26,17 @@ final class Line implements OperationInterface
{
return new self($this->x + $x, $this->y + $y);
}
/**
* @return self
*/
public function rotate(int $degrees) : OperationInterface
{
$radians = deg2rad($degrees);
$sin = sin($radians);
$cos = cos($radians);
$xr = $this->x * $cos - $this->y * $sin;
$yr = $this->x * $sin + $this->y * $cos;
return new self($xr, $yr);
}
}
+14 -13
View File
@@ -5,20 +5,8 @@ namespace BaconQrCode\Renderer\Path;
final class Move implements OperationInterface
{
/**
* @var float
*/
private $x;
/**
* @var float
*/
private $y;
public function __construct(float $x, float $y)
public function __construct(private readonly float $x, private readonly float $y)
{
$this->x = $x;
$this->y = $y;
}
public function getX() : float
@@ -38,4 +26,17 @@ final class Move implements OperationInterface
{
return new self($this->x + $x, $this->y + $y);
}
/**
* @return self
*/
public function rotate(int $degrees) : OperationInterface
{
$radians = deg2rad($degrees);
$sin = sin($radians);
$cos = cos($radians);
$xr = $this->x * $cos - $this->y * $sin;
$yr = $this->x * $sin + $this->y * $cos;
return new self($xr, $yr);
}
}
@@ -9,4 +9,9 @@ interface OperationInterface
* Translates the operation's coordinates.
*/
public function translate(float $x, float $y) : self;
/**
* Rotates the operation's coordinates.
*/
public function rotate(int $degrees) : self;
}
+13 -2
View File
@@ -14,7 +14,7 @@ final class Path implements IteratorAggregate
/**
* @var OperationInterface[]
*/
private $operations = [];
private array $operations = [];
/**
* Moves the drawing operation to a certain position.
@@ -94,8 +94,19 @@ final class Path implements IteratorAggregate
return $path;
}
public function rotate(int $degrees) : self
{
$path = new self();
foreach ($this->operations as $operation) {
$path->operations[] = $operation->rotate($degrees);
}
return $path;
}
/**
* @return OperationInterface[]|Traversable
* @return Traversable<int, OperationInterface>
*/
public function getIterator() : Traversable
{
@@ -28,14 +28,8 @@ final class PlainTextRenderer implements RendererInterface
*/
private const EMPTY_BLOCK = "\xc2\xa0";
/**
* @var int
*/
private $margin;
public function __construct(int $margin = 2)
public function __construct(private readonly int $margin = 2)
{
$this->margin = $margin;
}
/**
@@ -8,25 +8,12 @@ use BaconQrCode\Renderer\Color\ColorInterface;
final class EyeFill
{
/**
* @var ColorInterface|null
*/
private $externalColor;
private static ?EyeFill $inherit = null;
/**
* @var ColorInterface|null
*/
private $internalColor;
/**
* @var self|null
*/
private static $inherit;
public function __construct(?ColorInterface $externalColor, ?ColorInterface $internalColor)
{
$this->externalColor = $externalColor;
$this->internalColor = $internalColor;
public function __construct(
private readonly ?ColorInterface $externalColor,
private readonly ?ColorInterface $internalColor
) {
}
public static function uniform(ColorInterface $color) : self
@@ -9,55 +9,16 @@ use BaconQrCode\Renderer\Color\Gray;
final class Fill
{
/**
* @var ColorInterface
*/
private $backgroundColor;
/**
* @var ColorInterface|null
*/
private $foregroundColor;
/**
* @var Gradient|null
*/
private $foregroundGradient;
/**
* @var EyeFill
*/
private $topLeftEyeFill;
/**
* @var EyeFill
*/
private $topRightEyeFill;
/**
* @var EyeFill
*/
private $bottomLeftEyeFill;
/**
* @var self|null
*/
private static $default;
private static ?Fill $default = null;
private function __construct(
ColorInterface $backgroundColor,
?ColorInterface $foregroundColor,
?Gradient $foregroundGradient,
EyeFill $topLeftEyeFill,
EyeFill $topRightEyeFill,
EyeFill $bottomLeftEyeFill
private readonly ColorInterface $backgroundColor,
private readonly ?ColorInterface $foregroundColor,
private readonly ?Gradient $foregroundGradient,
private readonly EyeFill $topLeftEyeFill,
private readonly EyeFill $topRightEyeFill,
private readonly EyeFill $bottomLeftEyeFill
) {
$this->backgroundColor = $backgroundColor;
$this->foregroundColor = $foregroundColor;
$this->foregroundGradient = $foregroundGradient;
$this->topLeftEyeFill = $topLeftEyeFill;
$this->topRightEyeFill = $topRightEyeFill;
$this->bottomLeftEyeFill = $bottomLeftEyeFill;
}
public static function default() : self
@@ -7,26 +7,11 @@ use BaconQrCode\Renderer\Color\ColorInterface;
final class Gradient
{
/**
* @var ColorInterface
*/
private $startColor;
/**
* @var ColorInterface
*/
private $endColor;
/**
* @var GradientType
*/
private $type;
public function __construct(ColorInterface $startColor, ColorInterface $endColor, GradientType $type)
{
$this->startColor = $startColor;
$this->endColor = $endColor;
$this->type = $type;
public function __construct(
private readonly ColorInterface $startColor,
private readonly ColorInterface $endColor,
private readonly GradientType $type
) {
}
public function getStartColor() : ColorInterface
@@ -10,40 +10,19 @@ use BaconQrCode\Renderer\Module\SquareModule;
final class RendererStyle
{
/**
* @var int
*/
private $size;
private ModuleInterface $module;
/**
* @var int
*/
private $margin;
private EyeInterface|null $eye;
/**
* @var ModuleInterface
*/
private $module;
/**
* @var EyeInterface|null
*/
private $eye;
/**
* @var Fill
*/
private $fill;
private Fill $fill;
public function __construct(
int $size,
int $margin = 4,
private int $size,
private int $margin = 4,
?ModuleInterface $module = null,
?EyeInterface $eye = null,
?Fill $fill = null
) {
$this->margin = $margin;
$this->size = $size;
$this->module = $module ?: SquareModule::instance();
$this->eye = $eye ?: new ModuleEye($this->module);
$this->fill = $fill ?: Fill::default();
+3 -11
View File
@@ -14,19 +14,11 @@ use BaconQrCode\Renderer\RendererInterface;
*/
final class Writer
{
/**
* Renderer instance.
*
* @var RendererInterface
*/
private $renderer;
/**
* Creates a new writer with a specific renderer.
*/
public function __construct(RendererInterface $renderer)
public function __construct(private readonly RendererInterface $renderer)
{
$this->renderer = $renderer;
}
/**
@@ -39,7 +31,7 @@ final class Writer
*/
public function writeString(
string $content,
string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING,
string $encoding = Encoder::DEFAULT_BYTE_MODE_ENCODING,
?ErrorCorrectionLevel $ecLevel = null,
?Version $forcedVersion = null
) : string {
@@ -62,7 +54,7 @@ final class Writer
public function writeFile(
string $content,
string $filename,
string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING,
string $encoding = Encoder::DEFAULT_BYTE_MODE_ENCODING,
?ErrorCorrectionLevel $ecLevel = null,
?Version $forcedVersion = null
) : void {