diff options
Diffstat (limited to 'vendor/chillerlan/php-qrcode/src/Output')
19 files changed, 1915 insertions, 516 deletions
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php b/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php index 639bdd111..bf30f1bb0 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php @@ -2,9 +2,7 @@ /** * Class QRCodeOutputException * - * @filesource QRCodeOutputException.php * @created 09.12.2015 - * @package chillerlan\QRCode\Output * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT @@ -14,4 +12,9 @@ namespace chillerlan\QRCode\Output; use chillerlan\QRCode\QRCodeException; -class QRCodeOutputException extends QRCodeException{} +/** + * An exception container + */ +final class QRCodeOutputException extends QRCodeException{ + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QREps.php b/vendor/chillerlan/php-qrcode/src/Output/QREps.php new file mode 100644 index 000000000..76dd50b37 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QREps.php @@ -0,0 +1,173 @@ +<?php +/** + * Class QREps + * + * @created 09.05.2022 + * @author smiley <smiley@chillerlan.net> + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_values, count, date, implode, is_array, is_numeric, max, min, round, sprintf; + +/** + * Encapsulated Postscript (EPS) output + * + * @see https://github.com/t0k4rt/phpqrcode/blob/bb29e6eb77e0a2a85bb0eb62725e0adc11ff5a90/qrvect.php#L52-L137 + * @see https://web.archive.org/web/20170818010030/http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/postscript/pdfs/5002.EPSF_Spec.pdf + * @see https://web.archive.org/web/20210419003859/https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/PLRM.pdf + * @see https://github.com/chillerlan/php-qrcode/discussions/148 + */ +class QREps extends QROutputAbstract{ + + public const MIME_TYPE = 'application/postscript'; + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_array($value) || count($value) < 3){ + return false; + } + + // check the first values of the array + foreach(array_values($value) as $i => $val){ + + if($i > 3){ + break; + } + + if(!is_numeric($val)){ + return false; + } + + } + + return true; + } + + /** + * @param array $value + * + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 3){ + break; + } + + // clamp value and convert from int 0-255 to float 0-1 RGB/CMYK range + $values[] = round((max(0, min(255, intval($val))) / 255), 6); + } + + return $this->formatColor($values); + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return $this->formatColor(($isDark) ? [0.0, 0.0, 0.0] : [1.0, 1.0, 1.0]); + } + + /** + * Set the color format string + * + * 4 values in the color array will be interpreted as CMYK, 3 as RGB + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function formatColor(array $values):string{ + $count = count($values); + + if($count < 3){ + throw new QRCodeOutputException('invalid color value'); + } + + $format = ($count === 4) + // CMYK + ? '%f %f %f %f C' + // RGB + : '%f %f %f R'; + + return sprintf($format, ...$values); + } + + /** + * @inheritDoc + */ + public function dump(?string $file = null):string{ + [$width, $height] = $this->getOutputDimensions(); + + $eps = [ + // main header + '%!PS-Adobe-3.0 EPSF-3.0', + '%%Creator: php-qrcode (https://github.com/chillerlan/php-qrcode)', + '%%Title: QR Code', + sprintf('%%%%CreationDate: %1$s', date('c')), + '%%DocumentData: Clean7Bit', + '%%LanguageLevel: 3', + sprintf('%%%%BoundingBox: 0 0 %s %s', $width, $height), + '%%EndComments', + // function definitions + '%%BeginProlog', + '/F { rectfill } def', + '/R { setrgbcolor } def', + '/C { setcmykcolor } def', + '%%EndProlog', + ]; + + if($this::moduleValueIsValid($this->options->bgColor)){ + $eps[] = $this->prepareModuleValue($this->options->bgColor); + $eps[] = sprintf('0 0 %s %s F', $width, $height); + } + + // create the path elements + $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE)); + + foreach($paths as $M_TYPE => $path){ + + if(empty($path)){ + continue; + } + + $eps[] = $this->getModuleValue($M_TYPE); + $eps[] = implode("\n", $path); + } + + // end file + $eps[] = '%%EOF'; + + $data = implode("\n", $eps); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * Returns a path segment for a single module + */ + protected function module(int $x, int $y, int $M_TYPE):string{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return ''; + } + + $outputX = ($x * $this->scale); + // Actual size - one block = Topmost y pos. + $top = ($this->length - $this->scale); + // Apparently y-axis is inverted (y0 is at bottom and not top) in EPS, so we have to switch the y-axis here + $outputY = ($top - ($y * $this->scale)); + + return sprintf('%d %d %d %d F', $outputX, $outputY, $this->scale, $this->scale); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php b/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php index a15ae9ff3..8f2482cba 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php @@ -2,24 +2,20 @@ /** * Class QRFpdf * - * https://github.com/chillerlan/php-qrcode/pull/49 - * - * @filesource QRFpdf.php * @created 03.06.2020 - * @package chillerlan\QRCode\Output * @author Maximilian Kresse - * * @license MIT + * + * @see https://github.com/chillerlan/php-qrcode/pull/49 */ namespace chillerlan\QRCode\Output; use chillerlan\QRCode\Data\QRMatrix; -use chillerlan\QRCode\QRCodeException; use chillerlan\Settings\SettingsContainerInterface; use FPDF; -use function array_values, class_exists, count, is_array; +use function array_values, class_exists, count, intval, is_array, is_numeric, max, min; /** * QRFpdf output module (requires fpdf) @@ -29,12 +25,23 @@ use function array_values, class_exists, count, is_array; */ class QRFpdf extends QROutputAbstract{ + public const MIME_TYPE = 'application/pdf'; + + protected FPDF $fpdf; + protected ?array $prevColor = null; + + /** + * QRFpdf constructor. + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ if(!class_exists(FPDF::class)){ // @codeCoverageIgnoreStart - throw new QRCodeException( - 'The QRFpdf output requires FPDF as dependency but the class "\FPDF" couldn\'t be found.' + throw new QRCodeOutputException( + 'The QRFpdf output requires FPDF (https://github.com/Setasign/FPDF)'. + ' as dependency but the class "\\FPDF" couldn\'t be found.' ); // @codeCoverageIgnoreEnd } @@ -45,69 +52,128 @@ class QRFpdf extends QROutputAbstract{ /** * @inheritDoc */ - protected function setModuleValues():void{ + public static function moduleValueIsValid($value):bool{ + + if(!is_array($value) || count($value) < 3){ + return false; + } - foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){ - $v = $this->options->moduleValues[$M_TYPE] ?? null; + // check the first 3 values of the array + foreach(array_values($value) as $i => $val){ - if(!is_array($v) || count($v) < 3){ - $this->moduleValues[$M_TYPE] = $defaultValue - ? [0, 0, 0] - : [255, 255, 255]; + if($i > 2){ + break; } - else{ - $this->moduleValues[$M_TYPE] = array_values($v); + + if(!is_numeric($val)){ + return false; } } + return true; } /** - * @inheritDoc + * @param array $value * - * @return string|\FPDF + * @inheritDoc + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function prepareModuleValue($value):array{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + $values[] = max(0, min(255, intval($val))); + } + + if(count($values) !== 3){ + throw new QRCodeOutputException('invalid color value'); + } + + return $values; + } + + /** + * @inheritDoc */ - public function dump(string $file = null){ - $file ??= $this->options->cachefile; + protected function getDefaultModuleValue(bool $isDark):array{ + return ($isDark) ? [0, 0, 0] : [255, 255, 255]; + } - $fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, [$this->length, $this->length]); + /** + * Initializes an FPDF instance + */ + protected function initFPDF():FPDF{ + $fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, $this->getOutputDimensions()); $fpdf->AddPage(); - $prevColor = null; + return $fpdf; + } - foreach($this->matrix->matrix() as $y => $row){ + /** + * @inheritDoc + * + * @return string|\FPDF + */ + public function dump(?string $file = null, ?FPDF $fpdf = null){ + $this->fpdf = ($fpdf ?? $this->initFPDF()); - foreach($row as $x => $M_TYPE){ - /** @var int $M_TYPE */ - $color = $this->moduleValues[$M_TYPE]; + if($this::moduleValueIsValid($this->options->bgColor)){ + $bgColor = $this->prepareModuleValue($this->options->bgColor); + [$width, $height] = $this->getOutputDimensions(); - if($prevColor === null || $prevColor !== $color){ - /** @phan-suppress-next-line PhanParamTooFewUnpack */ - $fpdf->SetFillColor(...$color); - $prevColor = $color; - } + /** @phan-suppress-next-line PhanParamTooFewUnpack */ + $this->fpdf->SetFillColor(...$bgColor); + $this->fpdf->Rect(0, 0, $width, $height, 'F'); + } - $fpdf->Rect($x * $this->scale, $y * $this->scale, 1 * $this->scale, 1 * $this->scale, 'F'); - } + $this->prevColor = null; + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $this->module($x, $y, $M_TYPE); + } } if($this->options->returnResource){ - return $fpdf; + return $this->fpdf; } - $pdfData = $fpdf->Output('S'); + $pdfData = $this->fpdf->Output('S'); - if($file !== null){ - $this->saveToFile($pdfData, $file); - } + $this->saveToFile($pdfData, $file); - if($this->options->imageBase64){ - $pdfData = sprintf('data:application/pdf;base64,%s', base64_encode($pdfData)); + if($this->options->outputBase64){ + $pdfData = $this->toBase64DataURI($pdfData); } return $pdfData; } + /** + * Renders a single module + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $color = $this->getModuleValue($M_TYPE); + + if($color !== null && $color !== $this->prevColor){ + /** @phan-suppress-next-line PhanParamTooFewUnpack */ + $this->fpdf->SetFillColor(...$color); + $this->prevColor = $color; + } + + $this->fpdf->Rect(($x * $this->scale), ($y * $this->scale), $this->scale, $this->scale, 'F'); + } + } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php new file mode 100644 index 000000000..25db1c902 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php @@ -0,0 +1,400 @@ +<?php +/** + * Class QRGdImage + * + * @created 05.12.2015 + * @author Smiley <smiley@chillerlan.net> + * @copyright 2015 Smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use chillerlan\QRCode\Data\QRMatrix; +use chillerlan\Settings\SettingsContainerInterface; +use ErrorException; +use Throwable; +use function array_values, count, extension_loaded, imagebmp, imagecolorallocate, imagecolortransparent, + imagecreatetruecolor, imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, + imagescale, imagetypes, imagewebp, intdiv, intval, is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start, + restore_error_handler, set_error_handler, sprintf; +use const IMG_BMP, IMG_GIF, IMG_JPG, IMG_PNG, IMG_WEBP; + +/** + * Converts the matrix into GD images, raw or base64 output (requires ext-gd) + * + * @see https://php.net/manual/book.image.php + * + * @deprecated 5.0.0 this class will be made abstract in future versions, + * calling it directly is deprecated - use one of the child classes instead + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ +class QRGdImage extends QROutputAbstract{ + + /** + * The GD image resource + * + * @see imagecreatetruecolor() + * @var resource|\GdImage + * + * @todo: add \GdImage type in v6 + */ + protected $image; + + /** + * The allocated background color + * + * @see \imagecolorallocate() + */ + protected int $background; + + /** + * Whether we're running in upscale mode (scale < 20) + * + * @see \chillerlan\QRCode\QROptions::$drawCircularModules + */ + protected bool $upscaled = false; + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ + $this->options = $options; + $this->matrix = $matrix; + + $this->checkGD(); + + if($this->options->invertMatrix){ + $this->matrix->invert(); + } + + $this->copyVars(); + $this->setMatrixDimensions(); + } + + /** + * Checks whether GD is installed and if the given mode is supported + * + * @return void + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + * @codeCoverageIgnore + */ + protected function checkGD():void{ + + if(!extension_loaded('gd')){ + throw new QRCodeOutputException('ext-gd not loaded'); + } + + $modes = [ + self::GDIMAGE_BMP => IMG_BMP, + self::GDIMAGE_GIF => IMG_GIF, + self::GDIMAGE_JPG => IMG_JPG, + self::GDIMAGE_PNG => IMG_PNG, + self::GDIMAGE_WEBP => IMG_WEBP, + ]; + + // likely using default or custom output + if(!isset($modes[$this->options->outputType])){ + return; + } + + $mode = $modes[$this->options->outputType]; + + if((imagetypes() & $mode) !== $mode){ + throw new QRCodeOutputException(sprintf('output mode "%s" not supported', $this->options->outputType)); + } + + } + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_array($value) || count($value) < 3){ + return false; + } + + // check the first 3 values of the array + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + if(!is_numeric($val)){ + return false; + } + + } + + return true; + } + + /** + * @param array $value + * + * @inheritDoc + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function prepareModuleValue($value):int{ + $values = []; + + foreach(array_values($value) as $i => $val){ + + if($i > 2){ + break; + } + + $values[] = max(0, min(255, intval($val))); + } + + /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ + $color = imagecolorallocate($this->image, ...$values); + + if($color === false){ + throw new QRCodeOutputException('could not set color: imagecolorallocate() error'); + } + + return $color; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):int{ + return $this->prepareModuleValue(($isDark) ? [0, 0, 0] : [255, 255, 255]); + } + + /** + * @inheritDoc + * + * @return string|resource|\GdImage + * + * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn + * @throws \ErrorException + */ + public function dump(?string $file = null){ + + set_error_handler(function(int $errno, string $errstr):bool{ + throw new ErrorException($errstr, $errno); + }); + + $this->image = $this->createImage(); + // set module values after image creation because we need the GdImage instance + $this->setModuleValues(); + $this->setBgColor(); + + imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $this->background); + + $this->drawImage(); + + if($this->upscaled){ + // scale down to the expected size + $this->image = imagescale($this->image, ($this->length / 10), ($this->length / 10)); + $this->upscaled = false; + } + + // set transparency after scaling, otherwise it would be undone + // @see https://www.php.net/manual/en/function.imagecolortransparent.php#77099 + $this->setTransparencyColor(); + + if($this->options->returnResource){ + restore_error_handler(); + + return $this->image; + } + + $imageData = $this->dumpImage(); + + $this->saveToFile($imageData, $file); + + if($this->options->outputBase64){ + // @todo: remove mime parameter in v6 + $imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType); + } + + restore_error_handler(); + + return $imageData; + } + + /** + * Creates a new GdImage resource and scales it if necessary + * + * we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales + * + * @see https://github.com/chillerlan/php-qrcode/issues/23 + * + * @return \GdImage|resource + */ + protected function createImage(){ + + if($this->drawCircularModules && $this->options->gdImageUseUpscale && $this->options->scale < 20){ + // increase the initial image size by 10 + $this->length *= 10; + $this->scale *= 10; + $this->upscaled = true; + } + + return imagecreatetruecolor($this->length, $this->length); + } + + /** + * Sets the background color + */ + protected function setBgColor():void{ + + if(isset($this->background)){ + return; + } + + if($this::moduleValueIsValid($this->options->bgColor)){ + $this->background = $this->prepareModuleValue($this->options->bgColor); + + return; + } + + $this->background = $this->prepareModuleValue([255, 255, 255]); + } + + /** + * Sets the transparency color + */ + protected function setTransparencyColor():void{ + + // @todo: the jpg skip can be removed in v6 + if($this->options->outputType === QROutputInterface::GDIMAGE_JPG || !$this->options->imageTransparent){ + return; + } + + $transparencyColor = $this->background; + + if($this::moduleValueIsValid($this->options->transparencyColor)){ + $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor); + } + + imagecolortransparent($this->image, $transparencyColor); + } + + /** + * Draws the QR image + */ + protected function drawImage():void{ + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $this->module($x, $y, $M_TYPE); + } + } + } + + /** + * Creates a single QR pixel with the given settings + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $color = $this->getModuleValue($M_TYPE); + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + imagefilledellipse( + $this->image, + (($x * $this->scale) + intdiv($this->scale, 2)), + (($y * $this->scale) + intdiv($this->scale, 2)), + (int)($this->circleDiameter * $this->scale), + (int)($this->circleDiameter * $this->scale), + $color + ); + + return; + } + + imagefilledrectangle( + $this->image, + ($x * $this->scale), + ($y * $this->scale), + (($x + 1) * $this->scale), + (($y + 1) * $this->scale), + $color + ); + } + + /** + * Renders the image with the gdimage function for the desired output + * + * @see \imagebmp() + * @see \imagegif() + * @see \imagejpeg() + * @see \imagepng() + * @see \imagewebp() + * + * @todo: v6.0: make abstract and call from child classes + * @see https://github.com/chillerlan/php-qrcode/issues/223 + * @codeCoverageIgnore + */ + protected function renderImage():void{ + + switch($this->options->outputType){ + case QROutputInterface::GDIMAGE_BMP: + imagebmp($this->image, null, ($this->options->quality > 0)); + break; + case QROutputInterface::GDIMAGE_GIF: + imagegif($this->image); + break; + case QROutputInterface::GDIMAGE_JPG: + imagejpeg($this->image, null, max(-1, min(100, $this->options->quality))); + break; + case QROutputInterface::GDIMAGE_WEBP: + imagewebp($this->image, null, max(-1, min(100, $this->options->quality))); + break; + // silently default to png output + case QROutputInterface::GDIMAGE_PNG: + default: + imagepng($this->image, null, max(-1, min(9, $this->options->quality))); + } + + } + + /** + * Creates the final image by calling the desired GD output function + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException + */ + protected function dumpImage():string{ + $exception = null; + $imageData = null; + + ob_start(); + + try{ + $this->renderImage(); + + $imageData = ob_get_contents(); + imagedestroy($this->image); + } + // not going to cover edge cases + // @codeCoverageIgnoreStart + catch(Throwable $e){ + $exception = $e; + } + // @codeCoverageIgnoreEnd + + ob_end_clean(); + + // throw here in case an exception happened within the output buffer + if($exception instanceof Throwable){ + throw new QRCodeOutputException($exception->getMessage()); + } + + return $imageData; + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php new file mode 100644 index 000000000..268ebe7c2 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php @@ -0,0 +1,33 @@ +<?php +/** + * Class QRGdImageBMP + * + * @created 25.10.2023 + * @author smiley <smiley@chillerlan.net> + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagebmp; + +/** + * GdImage bmp output + * + * @see \imagebmp() + */ +class QRGdImageBMP extends QRGdImage{ + + public const MIME_TYPE = 'image/bmp'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagebmp($this->image, null, ($this->options->quality > 0)); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php new file mode 100644 index 000000000..a02130907 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php @@ -0,0 +1,33 @@ +<?php +/** + * Class QRGdImageGIF + * + * @created 25.10.2023 + * @author smiley <smiley@chillerlan.net> + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagegif; + +/** + * GdImage gif output + * + * @see \imagegif() + */ +class QRGdImageGIF extends QRGdImage{ + + public const MIME_TYPE = 'image/gif'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagegif($this->image); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php new file mode 100644 index 000000000..6be36e2fe --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php @@ -0,0 +1,40 @@ +<?php +/** + * Class QRGdImageJPEG + * + * @created 25.10.2023 + * @author smiley <smiley@chillerlan.net> + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagejpeg, max, min; + +/** + * GdImage jpeg output + * + * @see \imagejpeg() + */ +class QRGdImageJPEG extends QRGdImage{ + + public const MIME_TYPE = 'image/jpg'; + + /** + * @inheritDoc + */ + protected function setTransparencyColor():void{ + // noop - transparency is not supported + } + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagejpeg($this->image, null, max(-1, min(100, $this->options->quality))); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php new file mode 100644 index 000000000..2db3fd5b4 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php @@ -0,0 +1,33 @@ +<?php +/** + * Class QRGdImagePNG + * + * @created 25.10.2023 + * @author smiley <smiley@chillerlan.net> + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagepng, max, min; + +/** + * GdImage png output + * + * @see \imagepng() + */ +class QRGdImagePNG extends QRGdImage{ + + public const MIME_TYPE = 'image/png'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagepng($this->image, null, max(-1, min(9, $this->options->quality))); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php new file mode 100644 index 000000000..cf8dfa9a5 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php @@ -0,0 +1,33 @@ +<?php +/** + * Class QRGdImageWEBP + * + * @created 25.10.2023 + * @author smiley <smiley@chillerlan.net> + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function imagewebp, max, min; + +/** + * GdImage webp output + * + * @see \imagewebp() + */ +class QRGdImageWEBP extends QRGdImage{ + + public const MIME_TYPE = 'image/webp'; + + /** + * @inheritDoc + */ + protected function renderImage():void{ + imagewebp($this->image, null, max(-1, min(100, $this->options->quality))); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRImage.php b/vendor/chillerlan/php-qrcode/src/Output/QRImage.php index 8f533d341..cda496d36 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QRImage.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QRImage.php @@ -2,216 +2,18 @@ /** * Class QRImage * - * @filesource QRImage.php - * @created 05.12.2015 - * @package chillerlan\QRCode\Output - * @author Smiley <smiley@chillerlan.net> - * @copyright 2015 Smiley + * @created 14.12.2021 + * @author smiley <smiley@chillerlan.net> + * @copyright 2021 smiley * @license MIT - * - * @noinspection PhpComposerExtensionStubsInspection */ namespace chillerlan\QRCode\Output; -use chillerlan\QRCode\Data\QRMatrix; -use chillerlan\QRCode\{QRCode, QRCodeException}; -use chillerlan\Settings\SettingsContainerInterface; -use Exception; - -use function array_values, base64_encode, call_user_func, count, extension_loaded, imagecolorallocate, imagecolortransparent, - imagecreatetruecolor, imagedestroy, imagefilledrectangle, imagegif, imagejpeg, imagepng, in_array, - is_array, ob_end_clean, ob_get_contents, ob_start, range, sprintf; - /** - * Converts the matrix into GD images, raw or base64 output (requires ext-gd) - * - * @see http://php.net/manual/book.image.php + * @deprecated 5.0.0 backward compatibility, use QRGdImage instead + * @see \chillerlan\QRCode\Output\QRGdImage */ -class QRImage extends QROutputAbstract{ - - /** - * GD image types that support transparency - * - * @var string[] - */ - protected const TRANSPARENCY_TYPES = [ - QRCode::OUTPUT_IMAGE_PNG, - QRCode::OUTPUT_IMAGE_GIF, - ]; - - protected string $defaultMode = QRCode::OUTPUT_IMAGE_PNG; - - /** - * The GD image resource - * - * @see imagecreatetruecolor() - * @var resource|\GdImage - * - * @phan-suppress PhanUndeclaredTypeProperty - */ - protected $image; - - /** - * @inheritDoc - * - * @throws \chillerlan\QRCode\QRCodeException - */ - public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ - - if(!extension_loaded('gd')){ - throw new QRCodeException('ext-gd not loaded'); // @codeCoverageIgnore - } - - parent::__construct($options, $matrix); - } - - /** - * @inheritDoc - */ - protected function setModuleValues():void{ - - foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){ - $v = $this->options->moduleValues[$M_TYPE] ?? null; - - if(!is_array($v) || count($v) < 3){ - $this->moduleValues[$M_TYPE] = $defaultValue - ? [0, 0, 0] - : [255, 255, 255]; - } - else{ - $this->moduleValues[$M_TYPE] = array_values($v); - } - - } - - } - - /** - * @inheritDoc - * - * @return string|resource|\GdImage - * - * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn - */ - public function dump(string $file = null){ - $file ??= $this->options->cachefile; - - $this->image = imagecreatetruecolor($this->length, $this->length); - - // avoid: Indirect modification of overloaded property $imageTransparencyBG has no effect - // https://stackoverflow.com/a/10455217 - $tbg = $this->options->imageTransparencyBG; - /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ - $background = imagecolorallocate($this->image, ...$tbg); - - if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){ - imagecolortransparent($this->image, $background); - } - - imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background); - - foreach($this->matrix->matrix() as $y => $row){ - foreach($row as $x => $M_TYPE){ - $this->setPixel($x, $y, $this->moduleValues[$M_TYPE]); - } - } - - if($this->options->returnResource){ - return $this->image; - } - - $imageData = $this->dumpImage(); - - if($file !== null){ - $this->saveToFile($imageData, $file); - } - - if($this->options->imageBase64){ - $imageData = sprintf('data:image/%s;base64,%s', $this->options->outputType, base64_encode($imageData)); - } - - return $imageData; - } - - /** - * Creates a single QR pixel with the given settings - */ - protected function setPixel(int $x, int $y, array $rgb):void{ - imagefilledrectangle( - $this->image, - $x * $this->scale, - $y * $this->scale, - ($x + 1) * $this->scale, - ($y + 1) * $this->scale, - /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ - imagecolorallocate($this->image, ...$rgb) - ); - } - - /** - * Creates the final image by calling the desired GD output function - * - * @throws \chillerlan\QRCode\Output\QRCodeOutputException - */ - protected function dumpImage():string{ - ob_start(); - - try{ - call_user_func([$this, $this->outputMode ?? $this->defaultMode]); - } - // not going to cover edge cases - // @codeCoverageIgnoreStart - catch(Exception $e){ - throw new QRCodeOutputException($e->getMessage()); - } - // @codeCoverageIgnoreEnd - - $imageData = ob_get_contents(); - imagedestroy($this->image); - - ob_end_clean(); - - return $imageData; - } - - /** - * PNG output - * - * @return void - */ - protected function png():void{ - imagepng( - $this->image, - null, - in_array($this->options->pngCompression, range(-1, 9), true) - ? $this->options->pngCompression - : -1 - ); - } - - /** - * Jiff - like... JitHub! - * - * @return void - */ - protected function gif():void{ - imagegif($this->image); - } - - /** - * JPG output - * - * @return void - */ - protected function jpg():void{ - imagejpeg( - $this->image, - null, - in_array($this->options->jpegQuality, range(0, 100), true) - ? $this->options->jpegQuality - : 85 - ); - } +class QRImage extends QRGdImage{ } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php b/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php index 49516d30e..214311a93 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php @@ -2,9 +2,7 @@ /** * Class QRImagick * - * @filesource QRImagick.php * @created 04.07.2018 - * @package chillerlan\QRCode\Output * @author smiley <smiley@chillerlan.net> * @copyright 2018 smiley * @license MIT @@ -15,51 +13,98 @@ namespace chillerlan\QRCode\Output; use chillerlan\QRCode\Data\QRMatrix; -use chillerlan\QRCode\QRCodeException; use chillerlan\Settings\SettingsContainerInterface; -use Imagick, ImagickDraw, ImagickPixel; - -use function extension_loaded, is_string; +use finfo, Imagick, ImagickDraw, ImagickPixel; +use function extension_loaded, in_array, is_string, max, min, preg_match, strlen; +use const FILEINFO_MIME_TYPE; /** * ImageMagick output module (requires ext-imagick) * - * @see http://php.net/manual/book.imagick.php - * @see http://phpimagick.com + * @see https://php.net/manual/book.imagick.php + * @see https://phpimagick.com */ class QRImagick extends QROutputAbstract{ + /** + * The main image instance + */ protected Imagick $imagick; /** + * The main draw instance + */ + protected ImagickDraw $imagickDraw; + + /** + * The allocated background color + */ + protected ImagickPixel $backgroundColor; + + /** * @inheritDoc + * + * @throws \chillerlan\QRCode\Output\QRCodeOutputException */ public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ - if(!extension_loaded('imagick')){ - throw new QRCodeException('ext-imagick not loaded'); // @codeCoverageIgnore + foreach(['fileinfo', 'imagick'] as $ext){ + if(!extension_loaded($ext)){ + throw new QRCodeOutputException(sprintf('ext-%s not loaded', $ext)); // @codeCoverageIgnore + } } parent::__construct($options, $matrix); } /** + * note: we're not necessarily validating the several values, just checking the general syntax + * + * @see https://www.php.net/manual/imagickpixel.construct.php * @inheritDoc */ - protected function setModuleValues():void{ + public static function moduleValueIsValid($value):bool{ - foreach($this::DEFAULT_MODULE_VALUES as $type => $defaultValue){ - $v = $this->options->moduleValues[$type] ?? null; + if(!is_string($value)){ + return false; + } - if(!is_string($v)){ - $this->moduleValues[$type] = $defaultValue - ? new ImagickPixel($this->options->markupDark) - : new ImagickPixel($this->options->markupLight); - } - else{ - $this->moduleValues[$type] = new ImagickPixel($v); - } + $value = trim($value); + + // hex notation + // #rgb(a) + // #rrggbb(aa) + // #rrrrggggbbbb(aaaa) + // ... + if(preg_match('/^#[a-f\d]+$/i', $value) && in_array((strlen($value) - 1), [3, 4, 6, 8, 9, 12, 16, 24, 32], true)){ + return true; + } + + // css (-like) func(...values) + if(preg_match('#^(graya?|hs(b|la?)|rgba?)\([\d .,%]+\)$#i', $value)){ + return true; } + + // predefined css color + if(preg_match('/^[a-z]+$/i', $value)){ + return true; + } + + return false; + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):ImagickPixel{ + return new ImagickPixel($value); + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):ImagickPixel{ + return $this->prepareModuleValue(($isDark) ? '#000' : '#fff'); } /** @@ -67,18 +112,14 @@ class QRImagick extends QROutputAbstract{ * * @return string|\Imagick */ - public function dump(string $file = null){ - $file ??= $this->options->cachefile; - $this->imagick = new Imagick; - - $this->imagick->newImage( - $this->length, - $this->length, - new ImagickPixel($this->options->imagickBG ?? 'transparent'), - $this->options->imagickFormat - ); + public function dump(?string $file = null){ + $this->setBgColor(); + + $this->imagick = $this->createImage(); $this->drawImage(); + // set transparency color after all operations + $this->setTransparencyColor(); if($this->options->returnResource){ return $this->imagick; @@ -86,34 +127,109 @@ class QRImagick extends QROutputAbstract{ $imageData = $this->imagick->getImageBlob(); - if($file !== null){ - $this->saveToFile($imageData, $file); + $this->imagick->destroy(); + + $this->saveToFile($imageData, $file); + + if($this->options->outputBase64){ + $imageData = $this->toBase64DataURI($imageData, (new finfo(FILEINFO_MIME_TYPE))->buffer($imageData)); } return $imageData; } /** + * Sets the background color + */ + protected function setBgColor():void{ + + if($this::moduleValueIsValid($this->options->bgColor)){ + $this->backgroundColor = $this->prepareModuleValue($this->options->bgColor); + + return; + } + + $this->backgroundColor = $this->prepareModuleValue('white'); + } + + /** + * Creates a new Imagick instance + */ + protected function createImage():Imagick{ + $imagick = new Imagick; + [$width, $height] = $this->getOutputDimensions(); + + $imagick->newImage($width, $height, $this->backgroundColor, $this->options->imagickFormat); + + if($this->options->quality > -1){ + $imagick->setImageCompressionQuality(max(0, min(100, $this->options->quality))); + } + + return $imagick; + } + + /** + * Sets the transparency color + */ + protected function setTransparencyColor():void{ + + if(!$this->options->imageTransparent){ + return; + } + + $transparencyColor = $this->backgroundColor; + + if($this::moduleValueIsValid($this->options->transparencyColor)){ + $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor); + } + + $this->imagick->transparentPaintImage($transparencyColor, 0.0, 10, false); + } + + /** * Creates the QR image via ImagickDraw */ protected function drawImage():void{ - $draw = new ImagickDraw; + $this->imagickDraw = new ImagickDraw; + $this->imagickDraw->setStrokeWidth(0); - foreach($this->matrix->matrix() as $y => $row){ + foreach($this->matrix->getMatrix() as $y => $row){ foreach($row as $x => $M_TYPE){ - $draw->setStrokeColor($this->moduleValues[$M_TYPE]); - $draw->setFillColor($this->moduleValues[$M_TYPE]); - $draw->rectangle( - $x * $this->scale, - $y * $this->scale, - ($x + 1) * $this->scale, - ($y + 1) * $this->scale - ); - + $this->module($x, $y, $M_TYPE); } } - $this->imagick->drawImage($draw); + $this->imagick->drawImage($this->imagickDraw); + } + + /** + * draws a single pixel at the given position + */ + protected function module(int $x, int $y, int $M_TYPE):void{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return; + } + + $this->imagickDraw->setFillColor($this->getModuleValue($M_TYPE)); + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + $this->imagickDraw->circle( + (($x + 0.5) * $this->scale), + (($y + 0.5) * $this->scale), + (($x + 0.5 + $this->circleRadius) * $this->scale), + (($y + 0.5) * $this->scale) + ); + + return; + } + + $this->imagickDraw->rectangle( + ($x * $this->scale), + ($y * $this->scale), + ((($x + 1) * $this->scale) - 1), + ((($y + 1) * $this->scale) - 1) + ); } } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php index 06d6e88cb..240bd45ad 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php @@ -2,9 +2,7 @@ /** * Class QRMarkup * - * @filesource QRMarkup.php * @created 17.12.2016 - * @package chillerlan\QRCode\Output * @author Smiley <smiley@chillerlan.net> * @copyright 2016 Smiley * @license MIT @@ -12,149 +10,85 @@ namespace chillerlan\QRCode\Output; -use chillerlan\QRCode\QRCode; - -use function is_string, sprintf, strip_tags, trim; +use function is_string, preg_match, strip_tags, trim; /** - * Converts the matrix into markup types: HTML, SVG, ... + * Abstract for markup types: HTML, SVG, ... XML anyone? */ -class QRMarkup extends QROutputAbstract{ - - protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG; - - /** - * @see \sprintf() - */ - protected string $svgHeader = '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" '. - 'style="width: 100%%; height: auto;" viewBox="0 0 %2$d %2$d">'; +abstract class QRMarkup extends QROutputAbstract{ /** + * note: we're not necessarily validating the several values, just checking the general syntax + * note: css4 colors are not included + * + * @todo: XSS proof + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value * @inheritDoc */ - protected function setModuleValues():void{ - - foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){ - $v = $this->options->moduleValues[$M_TYPE] ?? null; - - if(!is_string($v)){ - $this->moduleValues[$M_TYPE] = $defaultValue - ? $this->options->markupDark - : $this->options->markupLight; - } - else{ - $this->moduleValues[$M_TYPE] = trim(strip_tags($v), '\'"'); - } + public static function moduleValueIsValid($value):bool{ + if(!is_string($value)){ + return false; } - } - - /** - * HTML output - */ - protected function html(string $file = null):string{ + $value = trim(strip_tags($value), " '\"\r\n\t"); - $html = empty($this->options->cssClass) - ? '<div>' - : '<div class="'.$this->options->cssClass.'">'; - - $html .= $this->options->eol; - - foreach($this->matrix->matrix() as $row){ - $html .= '<div>'; - - foreach($row as $M_TYPE){ - $html .= '<span style="background: '.$this->moduleValues[$M_TYPE].';"></span>'; - } - - $html .= '</div>'.$this->options->eol; + // hex notation + // #rgb(a) + // #rrggbb(aa) + if(preg_match('/^#([\da-f]{3}){1,2}$|^#([\da-f]{4}){1,2}$/i', $value)){ + return true; } - $html .= '</div>'.$this->options->eol; + // css: hsla/rgba(...values) + if(preg_match('#^(hsla?|rgba?)\([\d .,%/]+\)$#i', $value)){ + return true; + } - if($file !== null){ - return '<!DOCTYPE html>'. - '<head><meta charset="UTF-8"><title>QR Code</title></head>'. - '<body>'.$this->options->eol.$html.'</body>'; + // predefined css color + if(preg_match('/^[a-z]+$/i', $value)){ + return true; } - return $html; + return false; } /** - * SVG output - * - * @see https://github.com/codemasher/php-qrcode/pull/5 + * @inheritDoc */ - protected function svg(string $file = null):string{ - $matrix = $this->matrix->matrix(); - - $svg = sprintf($this->svgHeader, $this->options->cssClass, $this->options->svgViewBoxSize ?? $this->moduleCount) - .$this->options->eol - .'<defs>'.$this->options->svgDefs.'</defs>' - .$this->options->eol; - - foreach($this->moduleValues as $M_TYPE => $value){ - $path = ''; - - foreach($matrix as $y => $row){ - //we'll combine active blocks within a single row as a lightweight compression technique - $start = null; - $count = 0; - - foreach($row as $x => $module){ - - if($module === $M_TYPE){ - $count++; - - if($start === null){ - $start = $x; - } - - if(isset($row[$x + 1])){ - continue; - } - } - - if($count > 0){ - $len = $count; - $start ??= 0; // avoid type coercion in sprintf() - phan happy - - $path .= sprintf('M%s %s h%s v1 h-%sZ ', $start, $y, $len, $len); - - // reset count - $count = 0; - $start = null; - } - - } - - } - - if(!empty($path)){ - $svg .= sprintf( - '<path class="qr-%s %s" stroke="transparent" fill="%s" fill-opacity="%s" d="%s" />', - $M_TYPE, $this->options->cssClass, $value, $this->options->svgOpacity, $path - ); - } + protected function prepareModuleValue($value):string{ + return trim(strip_tags($value), " '\"\r\n\t"); + } - } + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '#000' : '#fff'; + } - // close svg - $svg .= '</svg>'.$this->options->eol; + /** + * @inheritDoc + */ + public function dump(?string $file = null):string{ + $data = $this->createMarkup($file !== null); - // if saving to file, append the correct headers - if($file !== null){ - return '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'. - $this->options->eol.$svg; - } + $this->saveToFile($data, $file); - if($this->options->imageBase64){ - $svg = sprintf('data:image/svg+xml;base64,%s', base64_encode($svg)); - } + return $data; + } - return $svg; + /** + * returns a string with all css classes for the current element + */ + protected function getCssClass(int $M_TYPE = 0):string{ + return $this->options->cssClass; } + /** + * returns the fully parsed and rendered markup string for the given input + */ + abstract protected function createMarkup(bool $saveToFile):string; + } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php new file mode 100644 index 000000000..65dc49a8a --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php @@ -0,0 +1,51 @@ +<?php +/** + * Class QRMarkupHTML + * + * @created 06.06.2022 + * @author smiley <smiley@chillerlan.net> + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function implode, sprintf; + +/** + * HTML output (a cheap markup substitute when SVG is not available or not an option) + */ +class QRMarkupHTML extends QRMarkup{ + + public const MIME_TYPE = 'text/html'; + + /** + * @inheritDoc + */ + protected function createMarkup(bool $saveToFile):string{ + $rows = []; + $cssClass = $this->getCssClass(); + + foreach($this->matrix->getMatrix() as $row){ + $element = '<span style="background: %s;"></span>'; + $modules = array_map(fn(int $M_TYPE):string => sprintf($element, $this->getModuleValue($M_TYPE)), $row); + + $rows[] = sprintf('<div>%s</div>%s', implode('', $modules), $this->eol); + } + + $html = sprintf('<div class="%1$s">%3$s%2$s</div>%3$s', $cssClass, implode('', $rows), $this->eol); + + // wrap the snippet into a body when saving to file + if($saveToFile){ + $html = sprintf( + '<!DOCTYPE html><html lang="none">%2$s<head>%2$s<meta charset="UTF-8">%2$s'. + '<title>QR Code</title></head>%2$s<body>%1$s</body>%2$s</html>', + $html, + $this->eol + ); + } + + return $html; + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php new file mode 100644 index 000000000..735c4180b --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php @@ -0,0 +1,200 @@ +<?php +/** + * Class QRMarkupSVG + * + * @created 06.06.2022 + * @author smiley <smiley@chillerlan.net> + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_chunk, implode, is_string, preg_match, sprintf, trim; + +/** + * SVG output + * + * @see https://github.com/codemasher/php-qrcode/pull/5 + * @see https://developer.mozilla.org/en-US/docs/Web/SVG + * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/ + * @see https://lea.verou.me/blog/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/ + * @see https://codepen.io/leaverou/full/RmwzKv + * @see https://jakearchibald.github.io/svgomg/ + * @see https://web.archive.org/web/20200220211445/http://apex.infogridpacific.com/SVG/svg-tutorial-contents.html + */ +class QRMarkupSVG extends QRMarkup{ + + public const MIME_TYPE = 'image/svg+xml'; + + /** + * @todo: XSS proof + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + + if(!is_string($value)){ + return false; + } + + $value = trim($value); + + // url(...) + if(preg_match('~^url\([-/#a-z\d]+\)$~i', $value)){ + return true; + } + + // otherwise check for standard css notation + return parent::moduleValueIsValid($value); + } + + /** + * @inheritDoc + */ + protected function getOutputDimensions():array{ + return [$this->moduleCount, $this->moduleCount]; + } + + /** + * @inheritDoc + */ + protected function getCssClass(int $M_TYPE = 0):string{ + return implode(' ', [ + 'qr-'.($this::LAYERNAMES[$M_TYPE] ?? $M_TYPE), + $this->matrix->isDark($M_TYPE) ? 'dark' : 'light', + $this->options->cssClass, + ]); + } + + /** + * @inheritDoc + */ + protected function createMarkup(bool $saveToFile):string{ + $svg = $this->header(); + + if(!empty($this->options->svgDefs)){ + $svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->eol); + } + + $svg .= $this->paths(); + + // close svg + $svg .= sprintf('%1$s</svg>%1$s', $this->eol); + + // transform to data URI only when not saving to file + if(!$saveToFile && $this->options->outputBase64){ + $svg = $this->toBase64DataURI($svg); + } + + return $svg; + } + + /** + * returns the value for the SVG viewBox attribute + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox + * @see https://css-tricks.com/scale-svg/#article-header-id-3 + */ + protected function getViewBox():string{ + [$width, $height] = $this->getOutputDimensions(); + + return sprintf('0 0 %s %s', $width, $height); + } + + /** + * returns the <svg> header with the given options parsed + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg + */ + protected function header():string{ + + $header = sprintf( + '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" viewBox="%2$s" preserveAspectRatio="%3$s">%4$s', + $this->options->cssClass, + $this->getViewBox(), + $this->options->svgPreserveAspectRatio, + $this->eol + ); + + if($this->options->svgAddXmlHeader){ + $header = sprintf('<?xml version="1.0" encoding="UTF-8"?>%s%s', $this->eol, $header); + } + + return $header; + } + + /** + * returns one or more SVG <path> elements + */ + protected function paths():string{ + $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE)); + $svg = []; + + // create the path elements + foreach($paths as $M_TYPE => $modules){ + // limit the total line length + $chunks = array_chunk($modules, 100); + $chonks = []; + + foreach($chunks as $chunk){ + $chonks[] = implode(' ', $chunk); + } + + $path = implode($this->eol, $chonks); + + if(empty($path)){ + continue; + } + + $svg[] = $this->path($path, $M_TYPE); + } + + return implode($this->eol, $svg); + } + + /** + * renders and returns a single <path> element + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path + */ + protected function path(string $path, int $M_TYPE):string{ + + if($this->options->svgUseFillAttributes){ + return sprintf( + '<path class="%s" fill="%s" d="%s"/>', + $this->getCssClass($M_TYPE), + $this->getModuleValue($M_TYPE), + $path + ); + } + + return sprintf('<path class="%s" d="%s"/>', $this->getCssClass($M_TYPE), $path); + } + + /** + * returns a path segment for a single module + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d + */ + protected function module(int $x, int $y, int $M_TYPE):string{ + + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ + return ''; + } + + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + // string interpolation: ugly and fast + $ix = ($x + 0.5 - $this->circleRadius); + $iy = ($y + 0.5); + + // phpcs:ignore + return "M$ix $iy a$this->circleRadius $this->circleRadius 0 1 0 $this->circleDiameter 0 a$this->circleRadius $this->circleRadius 0 1 0 -$this->circleDiameter 0Z"; + } + + // phpcs:ignore + return "M$x $y h1 v1 h-1Z"; + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php b/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php index d4ed3d0c9..a2757ac8c 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php @@ -2,9 +2,7 @@ /** * Class QROutputAbstract * - * @filesource QROutputAbstract.php * @created 09.12.2015 - * @package chillerlan\QRCode\Output * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT @@ -12,10 +10,10 @@ namespace chillerlan\QRCode\Output; -use chillerlan\QRCode\{Data\QRMatrix, QRCode}; +use chillerlan\QRCode\Data\QRMatrix; use chillerlan\Settings\SettingsContainerInterface; - -use function call_user_func_array, dirname, file_put_contents, get_called_class, in_array, is_writable, sprintf; +use Closure; +use function base64_encode, dirname, file_put_contents, is_writable, ksort, sprintf; /** * common output abstract @@ -25,30 +23,11 @@ abstract class QROutputAbstract implements QROutputInterface{ /** * the current size of the QR matrix * - * @see \chillerlan\QRCode\Data\QRMatrix::size() + * @see \chillerlan\QRCode\Data\QRMatrix::getSize() */ protected int $moduleCount; /** - * the current output mode - * - * @see \chillerlan\QRCode\QROptions::$outputType - */ - protected string $outputMode; - - /** - * the default output mode of the current output module - */ - protected string $defaultMode; - - /** - * the current scaling for a QR pixel - * - * @see \chillerlan\QRCode\QROptions::$scale - */ - protected int $scale; - - /** * the side length of the QR image (modules * scale) */ protected int $length; @@ -68,62 +47,215 @@ abstract class QROutputAbstract implements QROutputInterface{ */ protected SettingsContainerInterface $options; + /** @see \chillerlan\QRCode\QROptions::$scale */ + protected int $scale; + /** @see \chillerlan\QRCode\QROptions::$connectPaths */ + protected bool $connectPaths; + /** @see \chillerlan\QRCode\QROptions::$excludeFromConnect */ + protected array $excludeFromConnect; + /** @see \chillerlan\QRCode\QROptions::$eol */ + protected string $eol; + /** @see \chillerlan\QRCode\QROptions::$drawLightModules */ + protected bool $drawLightModules; + /** @see \chillerlan\QRCode\QROptions::$drawCircularModules */ + protected bool $drawCircularModules; + /** @see \chillerlan\QRCode\QROptions::$keepAsSquare */ + protected array $keepAsSquare; + /** @see \chillerlan\QRCode\QROptions::$circleRadius */ + protected float $circleRadius; + protected float $circleDiameter; + /** * QROutputAbstract constructor. */ public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ - $this->options = $options; - $this->matrix = $matrix; - $this->moduleCount = $this->matrix->size(); + $this->options = $options; + $this->matrix = $matrix; + + if($this->options->invertMatrix){ + $this->matrix->invert(); + } + + $this->copyVars(); + $this->setMatrixDimensions(); + $this->setModuleValues(); + } + + /** + * Creates copies of several QROptions values to avoid calling the magic getters + * in long loops for a significant performance increase. + * + * These variables are usually used in the "module" methods and are called up to 31329 times (at version 40). + */ + protected function copyVars():void{ + + $vars = [ + 'connectPaths', + 'excludeFromConnect', + 'eol', + 'drawLightModules', + 'drawCircularModules', + 'keepAsSquare', + 'circleRadius', + ]; + + foreach($vars as $property){ + $this->{$property} = $this->options->{$property}; + } + + $this->circleDiameter = ($this->circleRadius * 2); + } + + /** + * Sets/updates the matrix dimensions + * + * Call this method if you modify the matrix from within your custom module in case the dimensions have been changed + */ + protected function setMatrixDimensions():void{ + $this->moduleCount = $this->matrix->getSize(); $this->scale = $this->options->scale; - $this->length = $this->moduleCount * $this->scale; + $this->length = ($this->moduleCount * $this->scale); + } + + /** + * Returns a 2 element array with the current output width and height + * + * The type and units of the values depend on the output class. The default value is the current module count * scale. + */ + protected function getOutputDimensions():array{ + return [$this->length, $this->length]; + } - $class = get_called_class(); + /** + * Sets the initial module values + */ + protected function setModuleValues():void{ - if(isset(QRCode::OUTPUT_MODES[$class]) && in_array($this->options->outputType, QRCode::OUTPUT_MODES[$class])){ - $this->outputMode = $this->options->outputType; + // first fill the map with the default values + foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){ + $this->moduleValues[$M_TYPE] = $this->getDefaultModuleValue($defaultValue); } - $this->setModuleValues(); + // now loop over the options values to replace defaults and add extra values + foreach($this->options->moduleValues as $M_TYPE => $value){ + if($this::moduleValueIsValid($value)){ + $this->moduleValues[$M_TYPE] = $this->prepareModuleValue($value); + } + } + + } + + /** + * Prepares the value for the given input (return value depends on the output class) + * + * @param mixed $value + * + * @return mixed|null + */ + abstract protected function prepareModuleValue($value); + + /** + * Returns a default value for either dark or light modules (return value depends on the output class) + * + * @return mixed|null + */ + abstract protected function getDefaultModuleValue(bool $isDark); + + /** + * Returns the prepared value for the given $M_TYPE + * + * @return mixed return value depends on the output class + * @throws \chillerlan\QRCode\Output\QRCodeOutputException if $moduleValues[$M_TYPE] doesn't exist + */ + protected function getModuleValue(int $M_TYPE){ + + if(!isset($this->moduleValues[$M_TYPE])){ + throw new QRCodeOutputException(sprintf('$M_TYPE %012b not found in module values map', $M_TYPE)); + } + + return $this->moduleValues[$M_TYPE]; + } + + /** + * Returns the prepared module value at the given coordinate [$x, $y] (convenience) + * + * @return mixed|null + */ + protected function getModuleValueAt(int $x, int $y){ + return $this->getModuleValue($this->matrix->get($x, $y)); } /** - * Sets the initial module values (clean-up & defaults) + * Returns a base64 data URI for the given string and mime type */ - abstract protected function setModuleValues():void; + protected function toBase64DataURI(string $data, ?string $mime = null):string{ + return sprintf('data:%s;base64,%s', ($mime ?? $this::MIME_TYPE), base64_encode($data)); + } /** - * saves the qr data to a file + * Saves the qr $data to a $file. If $file is null, nothing happens. * * @see file_put_contents() - * @see \chillerlan\QRCode\QROptions::cachefile + * @see \chillerlan\QRCode\QROptions::$cachefile * * @throws \chillerlan\QRCode\Output\QRCodeOutputException */ - protected function saveToFile(string $data, string $file):bool{ + protected function saveToFile(string $data, ?string $file = null):void{ + + if($file === null){ + return; + } if(!is_writable(dirname($file))){ - throw new QRCodeOutputException(sprintf('Could not write data to cache file: %s', $file)); + throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s', $file)); } - return (bool)file_put_contents($file, $data); + if(file_put_contents($file, $data) === false){ + throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s (file_put_contents error)', $file)); + } } /** - * @inheritDoc + * collects the modules per QRMatrix::M_* type and runs a $transform function on each module and + * returns an array with the transformed modules + * + * The transform callback is called with the following parameters: + * + * $x - current column + * $y - current row + * $M_TYPE - field value + * $M_TYPE_LAYER - (possibly modified) field value that acts as layer id */ - public function dump(string $file = null){ - $file ??= $this->options->cachefile; + protected function collectModules(Closure $transform):array{ + $paths = []; - // call the built-in output method with the optional file path as parameter - // to make the called method aware if a cache file was given - $data = call_user_func_array([$this, $this->outputMode ?? $this->defaultMode], [$file]); + // collect the modules for each type + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ + $M_TYPE_LAYER = $M_TYPE; - if($file !== null){ - $this->saveToFile($data, $file); + if($this->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->excludeFromConnect)){ + // to connect paths we'll redeclare the $M_TYPE_LAYER to data only + $M_TYPE_LAYER = QRMatrix::M_DATA; + + if($this->matrix->isDark($M_TYPE)){ + $M_TYPE_LAYER = QRMatrix::M_DATA_DARK; + } + } + + // collect the modules per $M_TYPE + $module = $transform($x, $y, $M_TYPE, $M_TYPE_LAYER); + + if(!empty($module)){ + $paths[$M_TYPE_LAYER][] = $module; + } + } } - return $data; + // beautify output + ksort($paths); + + return $paths; } } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php b/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php index b07b8e7a5..7d2315180 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php @@ -2,9 +2,7 @@ /** * Interface QROutputInterface, * - * @filesource QROutputInterface.php * @created 02.12.2015 - * @package chillerlan\QRCode\Output * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT @@ -19,36 +17,210 @@ use chillerlan\QRCode\Data\QRMatrix; */ interface QROutputInterface{ - const DEFAULT_MODULE_VALUES = [ + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MARKUP_HTML = 'html'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MARKUP_SVG = 'svg'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_BMP = 'bmp'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_GIF = 'gif'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_JPG = 'jpg'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_PNG = 'png'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const GDIMAGE_WEBP = 'webp'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const STRING_JSON = 'json'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const STRING_TEXT = 'text'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const IMAGICK = 'imagick'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const FPDF = 'fpdf'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const EPS = 'eps'; + + /** + * @var string + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const CUSTOM = 'custom'; + + /** + * Map of built-in output modes => class FQN + * + * @var string[] + * @deprecated 5.0.0 <no replacement> + * @see https://github.com/chillerlan/php-qrcode/issues/223 + */ + public const MODES = [ + self::MARKUP_SVG => QRMarkupSVG::class, + self::MARKUP_HTML => QRMarkupHTML::class, + self::GDIMAGE_BMP => QRGdImageBMP::class, + self::GDIMAGE_GIF => QRGdImageGIF::class, + self::GDIMAGE_JPG => QRGdImageJPEG::class, + self::GDIMAGE_PNG => QRGdImagePNG::class, + self::GDIMAGE_WEBP => QRGdImageWEBP::class, + self::STRING_JSON => QRStringJSON::class, + self::STRING_TEXT => QRStringText::class, + self::IMAGICK => QRImagick::class, + self::FPDF => QRFpdf::class, + self::EPS => QREps::class, + ]; + + /** + * Map of module type => default value + * + * @var bool[] + */ + public const DEFAULT_MODULE_VALUES = [ // light - QRMatrix::M_NULL => false, // 0 - QRMatrix::M_DATA => false, // 4 - QRMatrix::M_FINDER => false, // 6 - QRMatrix::M_SEPARATOR => false, // 8 - QRMatrix::M_ALIGNMENT => false, // 10 - QRMatrix::M_TIMING => false, // 12 - QRMatrix::M_FORMAT => false, // 14 - QRMatrix::M_VERSION => false, // 16 - QRMatrix::M_QUIETZONE => false, // 18 - QRMatrix::M_LOGO => false, // 20 - QRMatrix::M_TEST => false, // 255 + QRMatrix::M_NULL => false, + QRMatrix::M_DARKMODULE_LIGHT => false, + QRMatrix::M_DATA => false, + QRMatrix::M_FINDER => false, + QRMatrix::M_SEPARATOR => false, + QRMatrix::M_ALIGNMENT => false, + QRMatrix::M_TIMING => false, + QRMatrix::M_FORMAT => false, + QRMatrix::M_VERSION => false, + QRMatrix::M_QUIETZONE => false, + QRMatrix::M_LOGO => false, + QRMatrix::M_FINDER_DOT_LIGHT => false, // dark - QRMatrix::M_DARKMODULE << 8 => true, // 512 - QRMatrix::M_DATA << 8 => true, // 1024 - QRMatrix::M_FINDER << 8 => true, // 1536 - QRMatrix::M_ALIGNMENT << 8 => true, // 2560 - QRMatrix::M_TIMING << 8 => true, // 3072 - QRMatrix::M_FORMAT << 8 => true, // 3584 - QRMatrix::M_VERSION << 8 => true, // 4096 - QRMatrix::M_FINDER_DOT << 8 => true, // 5632 - QRMatrix::M_TEST << 8 => true, // 65280 + QRMatrix::M_DARKMODULE => true, + QRMatrix::M_DATA_DARK => true, + QRMatrix::M_FINDER_DARK => true, + QRMatrix::M_SEPARATOR_DARK => true, + QRMatrix::M_ALIGNMENT_DARK => true, + QRMatrix::M_TIMING_DARK => true, + QRMatrix::M_FORMAT_DARK => true, + QRMatrix::M_VERSION_DARK => true, + QRMatrix::M_QUIETZONE_DARK => true, + QRMatrix::M_LOGO_DARK => true, + QRMatrix::M_FINDER_DOT => true, ]; /** - * generates the output, optionally dumps it to a file, and returns it + * Map of module type => readable name (for CSS etc.) + * + * @var string[] + */ + public const LAYERNAMES = [ + // light + QRMatrix::M_NULL => 'null', + QRMatrix::M_DARKMODULE_LIGHT => 'darkmodule-light', + QRMatrix::M_DATA => 'data', + QRMatrix::M_FINDER => 'finder', + QRMatrix::M_SEPARATOR => 'separator', + QRMatrix::M_ALIGNMENT => 'alignment', + QRMatrix::M_TIMING => 'timing', + QRMatrix::M_FORMAT => 'format', + QRMatrix::M_VERSION => 'version', + QRMatrix::M_QUIETZONE => 'quietzone', + QRMatrix::M_LOGO => 'logo', + QRMatrix::M_FINDER_DOT_LIGHT => 'finder-dot-light', + // dark + QRMatrix::M_DARKMODULE => 'darkmodule', + QRMatrix::M_DATA_DARK => 'data-dark', + QRMatrix::M_FINDER_DARK => 'finder-dark', + QRMatrix::M_SEPARATOR_DARK => 'separator-dark', + QRMatrix::M_ALIGNMENT_DARK => 'alignment-dark', + QRMatrix::M_TIMING_DARK => 'timing-dark', + QRMatrix::M_FORMAT_DARK => 'format-dark', + QRMatrix::M_VERSION_DARK => 'version-dark', + QRMatrix::M_QUIETZONE_DARK => 'quietzone-dark', + QRMatrix::M_LOGO_DARK => 'logo-dark', + QRMatrix::M_FINDER_DOT => 'finder-dot', + ]; + + /** + * @var string + * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI() + * @internal do not call this constant from the interface, but rather from one of the child classes + */ + public const MIME_TYPE = ''; + + /** + * Determines whether the given value is valid + * + * @param mixed $value + */ + public static function moduleValueIsValid($value):bool; + + /** + * Generates the output, optionally dumps it to a file, and returns it + * + * please note that the value of QROptions::$cachefile is already evaluated at this point. + * if the output module is invoked manually, it has no effect at all. + * you need to supply the $file parameter here in that case (or handle the option value in your custom output module). + * + * @see \chillerlan\QRCode\QRCode::renderMatrix() * * @return mixed */ - public function dump(string $file = null); + public function dump(?string $file = null); } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRString.php b/vendor/chillerlan/php-qrcode/src/Output/QRString.php index 3ed5153e1..2d6d052d6 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QRString.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QRString.php @@ -2,75 +2,110 @@ /** * Class QRString * - * @filesource QRString.php * @created 05.12.2015 - * @package chillerlan\QRCode\Output * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT * - * @noinspection PhpUnusedParameterInspection * @noinspection PhpComposerExtensionStubsInspection */ namespace chillerlan\QRCode\Output; -use chillerlan\QRCode\QRCode; - -use function implode, is_string, json_encode; +use function implode, is_string, json_encode, max, min, sprintf; +use const JSON_THROW_ON_ERROR; /** * Converts the matrix data into string types + * + * @deprecated 5.0.0 this class will be removed in future versions, use one of QRStringText or QRStringJSON instead */ class QRString extends QROutputAbstract{ - protected string $defaultMode = QRCode::OUTPUT_STRING_TEXT; - /** * @inheritDoc */ - protected function setModuleValues():void{ + public static function moduleValueIsValid($value):bool{ + return is_string($value); + } - foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){ - $v = $this->options->moduleValues[$M_TYPE] ?? null; + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + return $value; + } - if(!is_string($v)){ - $this->moduleValues[$M_TYPE] = $defaultValue - ? $this->options->textDark - : $this->options->textLight; - } - else{ - $this->moduleValues[$M_TYPE] = $v; - } + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '██' : '░░'; + } + /** + * @inheritDoc + */ + public function dump(?string $file = null):string{ + + switch($this->options->outputType){ + case QROutputInterface::STRING_TEXT: + $data = $this->text(); + break; + case QROutputInterface::STRING_JSON: + default: + $data = $this->json(); } + $this->saveToFile($data, $file); + + return $data; } /** * string output */ - protected function text(string $file = null):string{ - $str = []; + protected function text():string{ + $lines = []; + $linestart = $this->options->textLineStart; - foreach($this->matrix->matrix() as $row){ + for($y = 0; $y < $this->moduleCount; $y++){ $r = []; - foreach($row as $M_TYPE){ - $r[] = $this->moduleValues[$M_TYPE]; + for($x = 0; $x < $this->moduleCount; $x++){ + $r[] = $this->getModuleValueAt($x, $y); } - $str[] = implode('', $r); + $lines[] = $linestart.implode('', $r); } - return implode($this->options->eol, $str); + return implode($this->eol, $lines); } /** * JSON output + * + * @throws \JsonException */ - protected function json(string $file = null):string{ - return json_encode($this->matrix->matrix()); + protected function json():string{ + return json_encode($this->matrix->getMatrix($this->options->jsonAsBooleans), JSON_THROW_ON_ERROR); + } + + // + + /** + * a little helper to create a proper ANSI 8-bit color escape sequence + * + * @see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @see https://en.wikipedia.org/wiki/Block_Elements + * + * @codeCoverageIgnore + */ + public static function ansi8(string $str, int $color, ?bool $background = null):string{ + $color = max(0, min($color, 255)); + $background = ($background === true) ? 48 : 38; + + return sprintf("\x1b[%s;5;%sm%s\x1b[0m", $background, $color, $str); } } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php b/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php new file mode 100644 index 000000000..87ed2d7ff --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php @@ -0,0 +1,67 @@ +<?php +/** + * Class QRStringJSON + * + * @created 25.10.2023 + * @author smiley <smiley@chillerlan.net> + * @copyright 2023 smiley + * @license MIT + * + * @noinspection PhpComposerExtensionStubsInspection + */ + +namespace chillerlan\QRCode\Output; + +use function json_encode; + +/** + * + */ +class QRStringJSON extends QROutputAbstract{ + + public const MIME_TYPE = 'application/json'; + + /** + * @inheritDoc + * @throws \JsonException + */ + public function dump(?string $file = null):string{ + $matrix = $this->matrix->getMatrix($this->options->jsonAsBooleans); + $data = json_encode($matrix, $this->options->jsonFlags); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + protected function prepareModuleValue($value):string{ + return ''; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ''; + } + + /** + * unused - required by interface + * + * @inheritDoc + * @codeCoverageIgnore + */ + public static function moduleValueIsValid($value):bool{ + return true; + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php b/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php new file mode 100644 index 000000000..a91591da7 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php @@ -0,0 +1,76 @@ +<?php +/** + * Class QRStringText + * + * @created 25.10.2023 + * @author smiley <smiley@chillerlan.net> + * @copyright 2023 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Output; + +use function array_map, implode, is_string, max, min, sprintf; + +/** + * + */ +class QRStringText extends QROutputAbstract{ + + public const MIME_TYPE = 'text/plain'; + + /** + * @inheritDoc + */ + public static function moduleValueIsValid($value):bool{ + return is_string($value); + } + + /** + * @inheritDoc + */ + protected function prepareModuleValue($value):string{ + return $value; + } + + /** + * @inheritDoc + */ + protected function getDefaultModuleValue(bool $isDark):string{ + return ($isDark) ? '██' : '░░'; + } + + /** + * @inheritDoc + */ + public function dump(?string $file = null):string{ + $lines = []; + $linestart = $this->options->textLineStart; + + foreach($this->matrix->getMatrix() as $row){ + $lines[] = $linestart.implode('', array_map([$this, 'getModuleValue'], $row)); + } + + $data = implode($this->eol, $lines); + + $this->saveToFile($data, $file); + + return $data; + } + + /** + * a little helper to create a proper ANSI 8-bit color escape sequence + * + * @see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @see https://en.wikipedia.org/wiki/Block_Elements + * + * @codeCoverageIgnore + */ + public static function ansi8(string $str, int $color, ?bool $background = null):string{ + $color = max(0, min($color, 255)); + $background = ($background === true) ? 48 : 38; + + return sprintf("\x1b[%s;5;%sm%s\x1b[0m", $background, $color, $str); + } + +} |