accounts/common/components/Qr/ElyDecorator.php
Octol1ttle 57d492da8a
Upgrade project to PHP 8.3, add PHPStan, upgrade almost every dependency (#36)
* start updating to PHP 8.3

* taking off!

Co-authored-by: ErickSkrauch <erickskrauch@yandex.ru>
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* dropped this

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* migrate to symfonymailer

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* this is so stupid 😭

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* ah, free, at last.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* oh, Gabriel.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* now dawns thy reckoning.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* and thy gore shall GLISTEN before the temples of man.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* creature of steel.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* my gratitude upon thee for my freedom.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* but the crimes thy kind has committed against humanity

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* Upgrade PHP-CS-Fixer and do fix the codebase

* First review round (maybe I have broken something)

* are NOT forgotten.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* Enable parallel PHP-CS-Fixer runner

* PHPStan level 1

* PHPStan level 2

* PHPStan level 3

* PHPStan level 4

* PHPStan level 5

* Levels 6 and 7 takes too much effort. Generate a baseline and fix them eventually

* Resolve TODO's related to the php-mock

* Drastically reduce baseline size with the Rector

* More code modernization with help of the Rector

* Update GitLab CI

---------

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
Co-authored-by: ErickSkrauch <erickskrauch@yandex.ru>
2024-12-02 11:10:55 +01:00

122 lines
3.9 KiB
PHP

<?php
namespace common\components\Qr;
use BaconQrCode\Common\ErrorCorrectionLevel;
use BaconQrCode\Encoder\QrCode;
use BaconQrCode\Renderer\Image\Decorator\DecoratorInterface;
use BaconQrCode\Renderer\Image\RendererInterface;
use BaconQrCode\Renderer\Image\Svg;
use Imagick;
use InvalidArgumentException;
use ReflectionClass;
class ElyDecorator implements DecoratorInterface {
private const string LOGO = __DIR__ . '/resources/logo.svg';
private const array CORRECTION_MAP = [
ErrorCorrectionLevel::L => 7,
ErrorCorrectionLevel::M => 15,
ErrorCorrectionLevel::Q => 25,
ErrorCorrectionLevel::H => 30,
];
/**
* @throws \ImagickException
* @throws \ImagickPixelException
* @throws \ImagickPixelIteratorException
*/
public function preProcess(
QrCode $qrCode,
RendererInterface $renderer,
$outputWidth,
$outputHeight,
$leftPadding,
$topPadding,
$multiple,
): void {
if (!$renderer instanceof Svg) {
throw new InvalidArgumentException('$renderer must by instance of ' . Svg::class);
}
$correctionLevel = self::CORRECTION_MAP[$qrCode->getErrorCorrectionLevel()->get()];
$sizeMultiplier = $correctionLevel + floor($correctionLevel / 3);
$count = $qrCode->getMatrix()->getWidth();
$countToRemoveX = (int)floor($count * $sizeMultiplier / 100);
$countToRemoveY = (int)floor($count * $sizeMultiplier / 100);
$startX = (int)($leftPadding + round(($count - $countToRemoveX) / 2 * $multiple));
$startY = (int)($topPadding + round(($count - $countToRemoveY) / 2 * $multiple));
$width = $countToRemoveX * $multiple;
$height = $countToRemoveY * $multiple;
$reflection = new ReflectionClass($renderer);
$property = $reflection->getProperty('svg');
$property->setAccessible(true);
/** @var \SimpleXMLElement $svg */
$svg = $property->getValue($renderer);
/** @var \SimpleXMLElement $image */
$image = $svg->addChild('image');
$image->addAttribute('x', (string)$startX);
$image->addAttribute('y', (string)$startY);
$image->addAttribute('width', (string)$width);
$image->addAttribute('height', (string)$height);
$image->addAttribute('xlink:href', $this->encodeSvgToBase64(self::LOGO));
$logo = new Imagick();
$logo->readImage(self::LOGO);
$logo->scaleImage($width, $height);
$foundedPixels = [];
foreach ($logo->getPixelIterator() as $row => $pixels) {
/** @var \ImagickPixel[] $pixels */
foreach ($pixels as $column => $pixel) {
$color = $pixel->getColorAsString();
if ($color !== 'srgb(255,255,255)') {
$foundedPixels[] = [(int)($startX + $column), (int)($startY + $row)];
}
}
}
$logo->clear();
$logo->destroy();
$padding = $multiple - 2;
if ($padding < 0) {
$padding = 1;
}
foreach ($foundedPixels as $coordinates) {
[$x, $y] = $coordinates;
$x -= $leftPadding;
$y -= $topPadding;
for ($i = $x - $padding; $i <= $x + $padding; $i++) {
for ($j = $y - $padding; $j <= $y + $padding; $j++) {
$matrixX = (int)floor($i / $multiple);
$matrixY = (int)floor($j / $multiple);
$qrCode->getMatrix()->set($matrixX, $matrixY, 0);
}
}
}
}
public function postProcess(
QrCode $qrCode,
RendererInterface $renderer,
$outputWidth,
$outputHeight,
$leftPadding,
$topPadding,
$multiple,
) {
}
private function encodeSvgToBase64(string $filePath): string {
return 'data:image/svg+xml;base64,' . base64_encode(file_get_contents($filePath));
}
}