Просмотр исходного кода

:octocat: fixes/implements #223

smiley 2 лет назад
Родитель
Сommit
ab358f2f38

+ 2 - 0
.phan/config.php

@@ -56,5 +56,7 @@ return [
 	'suppress_issue_types' => [
 		'PhanAccessMethodInternal',
 		'PhanAccessOverridesFinalConstant',
+		'PhanDeprecatedClass',
+		'PhanDeprecatedClassConstant',
 	],
 ];

+ 3 - 3
examples/imageWithLogo.php

@@ -13,7 +13,7 @@
 use chillerlan\QRCode\{QRCode, QROptions};
 use chillerlan\QRCode\Common\EccLevel;
 use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCode\Output\{QRGdImage, QRCodeOutputException};
+use chillerlan\QRCode\Output\{QRGdImagePNG, QRCodeOutputException};
 
 require_once __DIR__.'/../vendor/autoload.php';
 
@@ -21,7 +21,7 @@ require_once __DIR__.'/../vendor/autoload.php';
  * Class definition
  */
 
-class QRImageWithLogo extends QRGdImage{
+class QRImageWithLogo extends QRGdImagePNG{
 
 	/**
 	 * @param string|null $file
@@ -64,7 +64,7 @@ class QRImageWithLogo extends QRGdImage{
 		$this->saveToFile($imageData, $file);
 
 		if($this->options->outputBase64){
-			$imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType);
+			$imageData = $this->toBase64DataURI($imageData);
 		}
 
 		return $imageData;

+ 2 - 2
examples/imageWithRoundedShapes.php

@@ -15,7 +15,7 @@
 
 use chillerlan\QRCode\Common\EccLevel;
 use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCode\Output\QRGdImage;
+use chillerlan\QRCode\Output\QRGdImagePNG;
 use chillerlan\QRCode\Output\QROutputInterface;
 use chillerlan\QRCode\QRCode;
 use chillerlan\QRCode\QROptions;
@@ -27,7 +27,7 @@ require_once __DIR__ . '/../vendor/autoload.php';
 // Class definition
 // --------------------
 
-class QRGdRounded extends QRGdImage{
+class QRGdRounded extends QRGdImagePNG{
 
 	/** @inheritDoc */
 	public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){

+ 3 - 4
examples/imageWithText.php

@@ -13,7 +13,7 @@
  */
 
 use chillerlan\QRCode\{QRCode, QROptions};
-use chillerlan\QRCode\Output\{QROutputInterface, QRGdImage};
+use chillerlan\QRCode\Output\{QROutputInterface, QRGdImagePNG};
 
 require_once __DIR__.'/../vendor/autoload.php';
 
@@ -21,7 +21,7 @@ require_once __DIR__.'/../vendor/autoload.php';
  * Class definition
  */
 
-class QRImageWithText extends QRGdImage{
+class QRImageWithText extends QRGdImagePNG{
 
 	/**
 	 * @inheritDoc
@@ -43,7 +43,7 @@ class QRImageWithText extends QRGdImage{
 		$this->saveToFile($imageData, $file);
 
 		if($this->options->outputBase64){
-			$imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType);
+			$imageData = $this->toBase64DataURI($imageData);
 		}
 
 		return $imageData;
@@ -97,7 +97,6 @@ class QRImageWithText extends QRGdImage{
 $options = new QROptions;
 
 $options->version      = 7;
-$options->outputType   = QROutputInterface::GDIMAGE_PNG;
 $options->scale        = 3;
 $options->outputBase64 = false;
 

+ 17 - 17
examples/text.php

@@ -10,7 +10,7 @@
 
 use chillerlan\QRCode\{QRCode, QROptions};
 use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCode\Output\{QROutputInterface, QRString};
+use chillerlan\QRCode\Output\{QROutputInterface, QRStringText};
 
 require_once __DIR__.'/../vendor/autoload.php';
 
@@ -22,22 +22,22 @@ $options->outputType    = QROutputInterface::STRING_TEXT;
 $options->eol           = "\n";
 $options->textLineStart = str_repeat(' ', 6);
 $options->moduleValues  = [
-	QRMatrix::M_FINDER_DARK    => QRString::ansi8('██', 124),
-	QRMatrix::M_FINDER         => QRString::ansi8('░░', 124),
-	QRMatrix::M_FINDER_DOT     => QRString::ansi8('██', 124),
-	QRMatrix::M_ALIGNMENT_DARK => QRString::ansi8('██', 2),
-	QRMatrix::M_ALIGNMENT      => QRString::ansi8('░░', 2),
-	QRMatrix::M_TIMING_DARK    => QRString::ansi8('██', 184),
-	QRMatrix::M_TIMING         => QRString::ansi8('░░', 184),
-	QRMatrix::M_FORMAT_DARK    => QRString::ansi8('██', 200),
-	QRMatrix::M_FORMAT         => QRString::ansi8('░░', 200),
-	QRMatrix::M_VERSION_DARK   => QRString::ansi8('██', 21),
-	QRMatrix::M_VERSION        => QRString::ansi8('░░', 21),
-	QRMatrix::M_DARKMODULE     => QRString::ansi8('██', 53),
-	QRMatrix::M_DATA_DARK      => QRString::ansi8('██', 166),
-	QRMatrix::M_DATA           => QRString::ansi8('░░', 166),
-	QRMatrix::M_QUIETZONE      => QRString::ansi8('░░', 253),
-	QRMatrix::M_SEPARATOR      => QRString::ansi8('░░', 253),
+	QRMatrix::M_FINDER_DARK    => QRStringText::ansi8('██', 124),
+	QRMatrix::M_FINDER         => QRStringText::ansi8('░░', 124),
+	QRMatrix::M_FINDER_DOT     => QRStringText::ansi8('██', 124),
+	QRMatrix::M_ALIGNMENT_DARK => QRStringText::ansi8('██', 2),
+	QRMatrix::M_ALIGNMENT      => QRStringText::ansi8('░░', 2),
+	QRMatrix::M_TIMING_DARK    => QRStringText::ansi8('██', 184),
+	QRMatrix::M_TIMING         => QRStringText::ansi8('░░', 184),
+	QRMatrix::M_FORMAT_DARK    => QRStringText::ansi8('██', 200),
+	QRMatrix::M_FORMAT         => QRStringText::ansi8('░░', 200),
+	QRMatrix::M_VERSION_DARK   => QRStringText::ansi8('██', 21),
+	QRMatrix::M_VERSION        => QRStringText::ansi8('░░', 21),
+	QRMatrix::M_DARKMODULE     => QRStringText::ansi8('██', 53),
+	QRMatrix::M_DATA_DARK      => QRStringText::ansi8('██', 166),
+	QRMatrix::M_DATA           => QRStringText::ansi8('░░', 166),
+	QRMatrix::M_QUIETZONE      => QRStringText::ansi8('░░', 253),
+	QRMatrix::M_SEPARATOR      => QRStringText::ansi8('░░', 253),
 ];
 
 

+ 2 - 0
src/Output/QREps.php

@@ -22,6 +22,8 @@ use function array_values, count, date, implode, is_array, is_numeric, max, min,
  */
 class QREps extends QROutputAbstract{
 
+	public const MIME_TYPE = 'application/postscript';
+
 	/**
 	 * @inheritDoc
 	 */

+ 3 - 1
src/Output/QRFpdf.php

@@ -25,6 +25,8 @@ use function array_values, class_exists, count, intval, is_array, is_numeric, ma
  */
 class QRFpdf extends QROutputAbstract{
 
+	public const MIME_TYPE = 'application/pdf';
+
 	protected FPDF   $fpdf;
 	protected ?array $prevColor = null;
 
@@ -146,7 +148,7 @@ class QRFpdf extends QROutputAbstract{
 		$this->saveToFile($pdfData, $file);
 
 		if($this->options->outputBase64){
-			$pdfData = $this->toBase64DataURI($pdfData, 'application/pdf');
+			$pdfData = $this->toBase64DataURI($pdfData);
 		}
 
 		return $pdfData;

+ 46 - 19
src/Output/QRGdImage.php

@@ -25,6 +25,10 @@ use function array_values, count, extension_loaded, gd_info, imagebmp, imagecolo
  * 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{
 
@@ -33,6 +37,8 @@ class QRGdImage extends QROutputAbstract{
 	 *
 	 * @see imagecreatetruecolor()
 	 * @var resource|\GdImage
+	 *
+	 * @todo: add \GdImage type in v6
 	 */
 	protected $image;
 
@@ -209,6 +215,7 @@ class QRGdImage extends QROutputAbstract{
 		$this->saveToFile($imageData, $file);
 
 		if($this->options->outputBase64){
+			// @todo: remove mime parameter in v6
 			$imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType);
 		}
 
@@ -261,6 +268,7 @@ class QRGdImage extends QROutputAbstract{
 	 */
 	protected function setTransparencyColor():void{
 
+		// @todo: the jpg skip can be removed in v6
 		if($this->options->outputType === QROutputInterface::GDIMAGE_JPG || !$this->options->imageTransparent){
 			return;
 		}
@@ -319,6 +327,42 @@ class QRGdImage extends QROutputAbstract{
 		);
 	}
 
+	/**
+	 * 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
 	 *
@@ -326,29 +370,12 @@ class QRGdImage extends QROutputAbstract{
 	 */
 	protected function dumpImage():string{
 		$exception = null;
+		$imageData = null;
 
 		ob_start();
 
 		try{
-
-			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)));
-			}
+			$this->renderImage();
 
 			$imageData = ob_get_contents();
 			imagedestroy($this->image);

+ 33 - 0
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));
+	}
+
+}

+ 33 - 0
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);
+	}
+
+}

+ 42 - 0
src/Output/QRGdImageJPEG.php

@@ -0,0 +1,42 @@
+<?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;
+use function max;
+use function 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)));
+	}
+
+}

+ 35 - 0
src/Output/QRGdImagePNG.php

@@ -0,0 +1,35 @@
+<?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;
+use function max;
+use function 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)));
+	}
+
+}

+ 35 - 0
src/Output/QRGdImageWEBP.php

@@ -0,0 +1,35 @@
+<?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;
+use function max;
+use function 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)));
+	}
+
+}

+ 2 - 0
src/Output/QRMarkupHTML.php

@@ -17,6 +17,8 @@ use function implode, sprintf;
  */
 class QRMarkupHTML extends QRMarkup{
 
+	public const MIME_TYPE = 'text/html';
+
 	/**
 	 * @inheritDoc
 	 */

+ 3 - 1
src/Output/QRMarkupSVG.php

@@ -22,6 +22,8 @@ use function array_chunk, implode, is_string, preg_match, sprintf, trim;
  */
 class QRMarkupSVG extends QRMarkup{
 
+	public const MIME_TYPE = 'image/svg+xml';
+
 	/**
 	 * @todo: XSS proof
 	 *
@@ -69,7 +71,7 @@ class QRMarkupSVG extends QRMarkup{
 
 		// transform to data URI only when not saving to file
 		if(!$saveToFile && $this->options->outputBase64){
-			$svg = $this->toBase64DataURI($svg, 'image/svg+xml');
+			$svg = $this->toBase64DataURI($svg);
 		}
 
 		return $svg;

+ 2 - 2
src/Output/QROutputAbstract.php

@@ -188,8 +188,8 @@ abstract class QROutputAbstract implements QROutputInterface{
 	/**
 	 * Returns a base64 data URI for the given string and mime type
 	 */
-	protected function toBase64DataURI(string $data, string $mime):string{
-		return sprintf('data:%s;base64,%s', $mime, base64_encode($data));
+	protected function toBase64DataURI(string $data, string $mime = null):string{
+		return sprintf('data:%s;base64,%s', ($mime ?? $this::MIME_TYPE), base64_encode($data));
 	}
 
 	/**

+ 93 - 20
src/Output/QROutputInterface.php

@@ -17,48 +17,114 @@ use chillerlan\QRCode\Data\QRMatrix;
  */
 interface QROutputInterface{
 
-	/** @var string */
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const MARKUP_HTML  = 'html';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const MARKUP_SVG   = 'svg';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const GDIMAGE_BMP  = 'bmp';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const GDIMAGE_GIF  = 'gif';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const GDIMAGE_JPG  = 'jpg';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const GDIMAGE_PNG  = 'png';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const GDIMAGE_WEBP = 'webp';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const STRING_JSON  = 'json';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const STRING_TEXT  = 'text';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const IMAGICK      = 'imagick';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const FPDF         = 'fpdf';
-	/** @var string */
+
+	/**
+	 * @var string
+	 * @deprecated 5.0.0 <no replacement>
+	 * @see https://github.com/chillerlan/php-qrcode/issues/223
+	 */
 	public const EPS          = 'eps';
-	/** @var string */
+
+	/**
+	 * @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  => QRGdImage::class,
-		self::GDIMAGE_GIF  => QRGdImage::class,
-		self::GDIMAGE_JPG  => QRGdImage::class,
-		self::GDIMAGE_PNG  => QRGdImage::class,
-		self::GDIMAGE_WEBP => QRGdImage::class,
-		self::STRING_JSON  => QRString::class,
-		self::STRING_TEXT  => QRString::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,
@@ -130,6 +196,13 @@ interface QROutputInterface{
 		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
 	 *

+ 2 - 0
src/Output/QRString.php

@@ -17,6 +17,8 @@ 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{
 

+ 64 - 0
src/Output/QRStringJSON.php

@@ -0,0 +1,64 @@
+<?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
+	 */
+	protected function prepareModuleValue($value):string{
+		return '';
+	}
+
+	/**
+	 * unused - required by interface
+	 *
+	 * @inheritDoc
+	 */
+	protected function getDefaultModuleValue(bool $isDark):string{
+		return '';
+	}
+
+	/**
+	 * unused - required by interface
+	 *
+	 * @inheritDoc
+	 */
+	public static function moduleValueIsValid($value):bool{
+		return true;
+	}
+
+}

+ 86 - 0
src/Output/QRStringText.php

@@ -0,0 +1,86 @@
+<?php
+/**
+ * Class QRStringText
+ *
+ * @created      25.10.2023
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2023 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function implode;
+use function is_string;
+use function max;
+use function min;
+use function 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;
+
+		for($y = 0; $y < $this->moduleCount; $y++){
+			$r = [];
+
+			for($x = 0; $x < $this->moduleCount; $x++){
+				$r[] = $this->getModuleValueAt($x, $y);
+			}
+
+			$lines[] = $linestart.implode('', $r);
+		}
+
+		$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);
+	}
+
+}

+ 25 - 8
src/QROptionsTrait.php

@@ -9,7 +9,7 @@
  * @copyright    2018 smiley
  * @license      MIT
  *
- * @noinspection PhpUnused
+ * @noinspection PhpUnused, PhpComposerExtensionStubsInspection
  */
 
 namespace chillerlan\QRCode;
@@ -17,7 +17,7 @@ namespace chillerlan\QRCode;
 use chillerlan\QRCode\Output\QROutputInterface;
 use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
 use function extension_loaded, in_array, max, min, strtolower;
-use const PHP_EOL;
+use const JSON_THROW_ON_ERROR, PHP_EOL;
 
 /**
  * The QRCode plug-in settings & setter functionality
@@ -119,6 +119,9 @@ trait QROptionsTrait{
 	 * @see \chillerlan\QRCode\Output\QRMarkupHTML
 	 * @see \chillerlan\QRCode\Output\QRMarkupSVG
 	 * @see \chillerlan\QRCode\Output\QRString
+	 *
+	 * @deprecated 5.0.0 use `QROptions::$outputInterface` instead
+	 * @see        https://github.com/chillerlan/php-qrcode/issues/223
 	 */
 	protected string $outputType = QROutputInterface::MARKUP_SVG;
 
@@ -126,6 +129,9 @@ trait QROptionsTrait{
 	 * The FQCN of the custom `QROutputInterface`
 	 *
 	 * if `QROptions::$outputType` is set to `QROutputInterface::CUSTOM` (default: `null`)
+	 *
+	 * @deprecated 5.0.0 the nullable type will be removed in future versions
+	 *                   and the default value will be set to `QRMarkupSVG::class`
 	 */
 	protected ?string $outputInterface = null;
 
@@ -325,11 +331,11 @@ trait QROptionsTrait{
 	 *
 	 * The given value depends on the used output type:
 	 *
-	 * - `QROutputInterface::GDIMAGE_BMP`:  `[0...1]`
-	 * - `QROutputInterface::GDIMAGE_JPG`:  `[0...100]`
-	 * - `QROutputInterface::GDIMAGE_WEBP`: `[0...9]`
-	 * - `QROutputInterface::GDIMAGE_PNG`:  `[0...100]`
-	 * - `QROutputInterface::IMAGICK`:      `[0...100]`
+	 * - `QRGdImageBMP`:  `[0...1]`
+	 * - `QRGdImageJPEG`: `[0...100]`
+	 * - `QRGdImageWEBP`: `[0...9]`
+	 * - `QRGdImagePNG`:  `[0...100]`
+	 * - `QRImagick`:     `[0...100]`
 	 *
 	 * @see \imagebmp()
 	 * @see \imagejpeg()
@@ -410,7 +416,7 @@ trait QROptionsTrait{
 	protected bool $svgUseFillAttributes = true;
 
 	/*
-	 * QRString settings
+	 * QRStringText settings
 	 */
 
 	/**
@@ -418,6 +424,17 @@ trait QROptionsTrait{
 	 */
 	protected string $textLineStart = '';
 
+	/*
+	 * QRStringJSON settings
+	 */
+
+	/**
+	 * Sets the flags to use for the `json_encode()` call
+	 *
+	 * @see https://www.php.net/manual/json.constants.php
+	 */
+	protected int $jsonFlags = JSON_THROW_ON_ERROR;
+
 	/**
 	 * Whether to return matrix values in JSON as booleans or `$M_TYPE` integers
 	 */

+ 2 - 0
tests/Output/QRGdImageBMPTest.php

@@ -10,6 +10,7 @@
 
 namespace chillerlan\QRCodeTest\Output;
 
+use chillerlan\QRCode\Output\QRGdImageBMP;
 use chillerlan\QRCode\Output\QROutputInterface;
 
 /**
@@ -18,5 +19,6 @@ use chillerlan\QRCode\Output\QROutputInterface;
 final class QRGdImageBMPTest extends QRGdImageTestAbstract{
 
 	protected string $type = QROutputInterface::GDIMAGE_BMP;
+	protected string $FQN  = QRGdImageBMP::class;
 
 }

+ 2 - 0
tests/Output/QRGdImageGIFTest.php

@@ -10,6 +10,7 @@
 
 namespace chillerlan\QRCodeTest\Output;
 
+use chillerlan\QRCode\Output\QRGdImageGIF;
 use chillerlan\QRCode\Output\QROutputInterface;
 
 /**
@@ -18,5 +19,6 @@ use chillerlan\QRCode\Output\QROutputInterface;
 final class QRGdImageGIFTest extends QRGdImageTestAbstract{
 
 	protected string $type = QROutputInterface::GDIMAGE_GIF;
+	protected string $FQN  = QRGdImageGIF::class;
 
 }

+ 2 - 0
tests/Output/QRGdImageJPGTest.php

@@ -10,6 +10,7 @@
 
 namespace chillerlan\QRCodeTest\Output;
 
+use chillerlan\QRCode\Output\QRGdImageJPEG;
 use chillerlan\QRCode\Output\QROutputInterface;
 
 /**
@@ -18,5 +19,6 @@ use chillerlan\QRCode\Output\QROutputInterface;
 final class QRGdImageJPGTest extends QRGdImageTestAbstract{
 
 	protected string $type = QROutputInterface::GDIMAGE_JPG;
+	protected string $FQN  = QRGdImageJPEG::class;
 
 }

+ 2 - 0
tests/Output/QRGdImagePNGTest.php

@@ -10,6 +10,7 @@
 
 namespace chillerlan\QRCodeTest\Output;
 
+use chillerlan\QRCode\Output\QRGdImagePNG;
 use chillerlan\QRCode\Output\QROutputInterface;
 
 /**
@@ -18,5 +19,6 @@ use chillerlan\QRCode\Output\QROutputInterface;
 final class QRGdImagePNGTest extends QRGdImageTestAbstract{
 
 	protected string $type = QROutputInterface::GDIMAGE_PNG;
+	protected string $FQN  = QRGdImagePNG::class;
 
 }

+ 8 - 4
tests/Output/QRGdImageTestAbstract.php

@@ -11,7 +11,6 @@
 namespace chillerlan\QRCodeTest\Output;
 
 use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCode\Output\QRGdImage;
 use const PHP_MAJOR_VERSION;
 
 /**
@@ -19,8 +18,6 @@ use const PHP_MAJOR_VERSION;
  */
 abstract class QRGdImageTestAbstract extends QROutputTestAbstract{
 
-	protected string $FQN  = QRGdImage::class;
-
 	/**
 	 * @inheritDoc
 	 */
@@ -62,7 +59,7 @@ abstract class QRGdImageTestAbstract extends QROutputTestAbstract{
 	}
 
 	/**
-	 *
+	 * @todo: remove php version check in v6
 	 */
 	public function testOutputGetResource():void{
 		$this->options->returnResource = true;
@@ -76,4 +73,11 @@ abstract class QRGdImageTestAbstract extends QROutputTestAbstract{
 			: $this::assertIsResource($actual);
 	}
 
+	public function testBase64MimeType():void{
+		$this->options->outputBase64 = true;
+		$this->outputInterface       = new $this->FQN($this->options, $this->matrix);
+
+		$this::assertStringContainsString($this->outputInterface::MIME_TYPE, $this->outputInterface->dump());
+	}
+
 }

+ 2 - 0
tests/Output/QRGdImageWEBPTest.php

@@ -10,6 +10,7 @@
 
 namespace chillerlan\QRCodeTest\Output;
 
+use chillerlan\QRCode\Output\QRGdImageWEBP;
 use chillerlan\QRCode\Output\QROutputInterface;
 
 /**
@@ -18,5 +19,6 @@ use chillerlan\QRCode\Output\QROutputInterface;
 final class QRGdImageWEBPTest extends QRGdImageTestAbstract{
 
 	protected string $type = QROutputInterface::GDIMAGE_WEBP;
+	protected string $FQN  = QRGdImageWEBP::class;
 
 }

+ 6 - 1
tests/Output/QRStringJSONTest.php

@@ -11,14 +11,16 @@
 namespace chillerlan\QRCodeTest\Output;
 
 use chillerlan\QRCode\Output\QROutputInterface;
+use chillerlan\QRCode\Output\QRStringJSON;
 use function extension_loaded;
 
 /**
  *
  */
-final class QRStringJSONTest extends QRStringTestAbstract{
+final class QRStringJSONTest extends QROutputTestAbstract{
 
 	protected string $type = QROutputInterface::STRING_JSON;
+	protected string $FQN  = QRStringJSON::class;
 
 	/**
 	 * @inheritDoc
@@ -32,6 +34,9 @@ final class QRStringJSONTest extends QRStringTestAbstract{
 		parent::setUp();
 	}
 
+	public static function moduleValueProvider():array{
+		return [];
+	}
 
 	public function testSetModuleValues():void{
 		/** @noinspection PhpUnitTestFailedLineInspection */

+ 12 - 1
tests/Output/QRStringTEXTTest.php

@@ -12,13 +12,24 @@ namespace chillerlan\QRCodeTest\Output;
 
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\QROutputInterface;
+use chillerlan\QRCode\Output\QRStringText;
 
 /**
  *
  */
-final class QRStringTEXTTest extends QRStringTestAbstract{
+final class QRStringTEXTTest extends QROutputTestAbstract{
 
 	protected string $type = QROutputInterface::STRING_TEXT;
+	protected string $FQN  = QRStringText::class;
+
+	public static function moduleValueProvider():array{
+		return [
+			'invalid: wrong type'       => [[], false],
+			'valid: string'             => ['abc', true],
+			'valid: zero length string' => ['', true],
+			'valid: empty string'       => [' ', true],
+		];
+	}
 
 	/**
 	 * @inheritDoc

+ 0 - 31
tests/Output/QRStringTestAbstract.php

@@ -1,31 +0,0 @@
-<?php
-/**
- * Class QRStringTestAbstract
- *
- * @created      24.12.2017
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2017 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCodeTest\Output;
-
-use chillerlan\QRCode\Output\QRString;
-
-/**
- * Tests the QRString output module
- */
-abstract class QRStringTestAbstract extends QROutputTestAbstract{
-
-	protected string $FQN  = QRString::class;
-
-	public static function moduleValueProvider():array{
-		return [
-			'invalid: wrong type'       => [[], false],
-			'valid: string'             => ['abc', true],
-			'valid: zero length string' => ['', true],
-			'valid: empty string'       => [' ', true],
-		];
-	}
-
-}