Преглед на файлове

:octocat: QRStringJSON overhaul

smiley преди 1 година
родител
ревизия
81eb21a7f8
променени са 4 файла, в които са добавени 229 реда и са изтрити 53 реда
  1. 69 23
      src/Output/QRStringJSON.php
  2. 142 0
      src/Output/qrcode.schema.json
  3. 3 7
      src/QROptionsTrait.php
  4. 15 23
      tests/Output/QRStringJSONTest.php

+ 69 - 23
src/Output/QRStringJSON.php

@@ -18,16 +18,52 @@ use function json_encode;
  *
  */
 class QRStringJSON extends QROutputAbstract{
+	use CssColorModuleValueTrait;
 
 	final public const MIME_TYPE = 'application/json';
+	final public const SCHEMA    = 'https://raw.githubusercontent.com/chillerlan/php-qrcode/main/src/Output/qrcode.schema.json';
+
+	/**
+	 * @inheritDoc
+	 */
+	protected function getOutputDimensions():array{
+		return [$this->moduleCount, $this->moduleCount];
+	}
 
 	/**
 	 * @inheritDoc
 	 * @throws \JsonException
 	 */
 	public function dump(string $file = null):string{
-		$matrix = $this->matrix->getMatrix($this->options->jsonAsBooleans);
-		$data   = json_encode($matrix, $this->options->jsonFlags);;
+		[$width, $height] = $this->getOutputDimensions();
+		$version   = $this->matrix->getVersion();
+		$dimension = $version->getDimension();
+
+		$json = [
+			'$schema' => $this::SCHEMA,
+			'qrcode' => [
+				'version'  => $version->getVersionNumber(),
+				'eccLevel' => (string)$this->matrix->getEccLevel(),
+				'matrix'   => [
+					'size'          => $dimension,
+					'quietzoneSize' => (int)(($this->moduleCount - $dimension) / 2),
+					'maskPattern'   => $this->matrix->getMaskPattern()->getPattern(),
+					'width'         => $width,
+					'height'        => $height,
+					'rows'          => [],
+				],
+			],
+		];
+
+		foreach($this->matrix->getMatrix() as $y => $row){
+			$matrixRow = $this->row($y, $row);
+
+			if($matrixRow !== null){
+				$json['qrcode']['matrix']['rows'][] = $matrixRow;
+			}
+		}
+
+		$data = json_encode($json, $this->options->jsonFlags);;
 
 		$this->saveToFile($data, $file);
 
@@ -35,33 +71,43 @@ class QRStringJSON extends QROutputAbstract{
 	}
 
 	/**
-	 * unused - required by interface
-	 *
-	 * @inheritDoc
-	 * @codeCoverageIgnore
+	 * Creates an array element for a matrix row
 	 */
-	protected function prepareModuleValue(mixed $value):string{
-		return '';
-	}
+	protected function row(int $y, array $row):array|null{
+		$matrixRow = ['y' => $y, 'modules' => []];
 
-	/**
-	 * unused - required by interface
-	 *
-	 * @inheritDoc
-	 * @codeCoverageIgnore
-	 */
-	protected function getDefaultModuleValue(bool $isDark):string{
-		return '';
+		foreach($row as $x => $M_TYPE){
+			$module = $this->module($x, $y, $M_TYPE);
+
+			if($module !== null){
+				$matrixRow['modules'][] = $module;
+			}
+		}
+
+		if(!empty($matrixRow['modules'])){
+			return $matrixRow;
+		}
+
+		// skip empty rows
+		return null;
 	}
 
 	/**
-	 * unused - required by interface
-	 *
-	 * @inheritDoc
-	 * @codeCoverageIgnore
+	 * Creates an array element for a single module
 	 */
-	public static function moduleValueIsValid(mixed $value):bool{
-		return true;
+	protected function module(int $x, int $y, int $M_TYPE):array|null{
+		$isDark = $this->matrix->isDark($M_TYPE);
+
+		if(!$this->drawLightModules && !$isDark){
+			return null;
+		}
+
+		return [
+			'x'     => $x,
+			'dark'  => $isDark,
+			'layer' => ($this::LAYERNAMES[$M_TYPE] ?? ''),
+			'value' => $this->getModuleValue($M_TYPE),
+		];
 	}
 
 }

+ 142 - 0
src/Output/qrcode.schema.json

@@ -0,0 +1,142 @@
+{
+	"$schema": "https://json-schema.org/draft/2020-12/schema",
+	"title": "chillerlan php-qrcode schema",
+	"type": "object",
+	"required": [
+		"qrcode"
+	],
+	"properties": {
+		"qrcode": {
+			"$ref": "#/$defs/qrcode"
+		}
+	},
+	"$defs": {
+		"qrcode": {
+			"description": "QR Code root element",
+			"type": "object",
+			"required": [
+				"eccLevel",
+				"matrix",
+				"version"
+			],
+			"properties": {
+				"version": {
+					"description": "The QR Code version: [1...40]",
+					"type": "number",
+					"minimum": 1,
+					"maximum": 40
+				},
+				"eccLevel": {
+					"description": "The ECC level: [L, M, Q, H]",
+					"enum": [
+						"L",
+						"M",
+						"Q",
+						"H"
+					]
+				},
+				"matrix": {
+					"$ref": "#/$defs/matrix"
+				}
+			}
+		},
+		"matrix": {
+			"description": "The matrix holds the encoded data in a 2-dimensional array of modules.",
+			"type": "object",
+			"required": [
+				"size",
+				"quietzoneSize",
+				"maskPattern",
+				"width",
+				"height"
+			],
+			"properties": {
+				"size": {
+					"description": "The side length of the QR symbol, excluding the quiet zone (version * 4 + 17). [21...177]",
+					"type": "number",
+					"minimum": 21,
+					"maximum": 177
+				},
+				"quietzoneSize": {
+					"description": "The size of the quiet zone (margin around the QR symbol).",
+					"type": "number",
+					"minimum": 0
+				},
+				"maskPattern": {
+					"description": "The detected mask pattern that was used to mask this matrix. [0...7].",
+					"type": "number",
+					"minimum": 0,
+					"maximum": 7
+				},
+				"width": {
+					"description": "The total width of the matrix, including the quiet zone.",
+					"type": "number",
+					"minimum": 21
+				},
+				"height": {
+					"description": "The total height of the matrix, including the quiet zone.",
+					"type": "number",
+					"minimum": 21
+				},
+				"rows": {
+					"type": "array",
+					"items": {
+						"$ref": "#/$defs/row"
+					},
+					"minItems": 0
+				}
+			}
+		},
+		"row": {
+			"description": "A row holds an array of modules",
+			"type": "object",
+			"required": [
+				"y",
+				"modules"
+			],
+			"properties": {
+				"y": {
+					"description": "The 'y' (vertical) coordinate of this row.",
+					"type": "number",
+					"minimum": 0
+				},
+				"modules": {
+					"type": "array",
+					"items": {
+						"$ref": "#/$defs/module"
+					},
+					"minItems": 0
+				}
+			}
+		},
+		"module": {
+			"description": "Represents a single module (pixel) of a QR symbol.",
+			"type": "object",
+			"required": [
+				"dark",
+				"layer",
+				"value",
+				"x"
+			],
+			"properties": {
+				"dark": {
+					"description": "Indicates whether this module is dark.",
+					"type": "boolean"
+				},
+				"layer": {
+					"description": "The layer (functional pattern) this module belongs to.",
+					"type": "string"
+				},
+				"value": {
+					"description": "The value for this module.",
+					"type": "string"
+				},
+				"x": {
+					"description": "The 'x' (horizontal) coordinate of this module.",
+					"type": "integer",
+					"minimum": 0
+				}
+			}
+		}
+	}
+}

+ 3 - 7
src/QROptionsTrait.php

@@ -17,7 +17,8 @@ namespace chillerlan\QRCode;
 use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
 use chillerlan\QRCode\Output\QRMarkupSVG;
 use function constant, in_array, is_string, max, min, sprintf, strtolower, strtoupper, trim;
-use const JSON_THROW_ON_ERROR, PHP_EOL;
+use const JSON_THROW_ON_ERROR, JSON_UNESCAPED_SLASHES, PHP_EOL;
+
 
 /**
  * The QRCode plug-in settings & setter functionality
@@ -408,12 +409,7 @@ trait QROptionsTrait{
 	 *
 	 * @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
-	 */
-	protected bool $jsonAsBooleans = false;
+	protected int $jsonFlags = JSON_THROW_ON_ERROR|JSON_UNESCAPED_SLASHES;
 
 	/*
 	 * QRFpdf settings

+ 15 - 23
tests/Output/QRStringJSONTest.php

@@ -14,25 +14,12 @@ use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Output\{QROutputInterface, QRStringJSON};
 use chillerlan\Settings\SettingsContainerInterface;
-use PHPUnit\Framework\Attributes\DataProvider;
-use function extension_loaded;
 
 /**
  *
  */
 final class QRStringJSONTest extends QROutputTestAbstract{
-
-	/**
-	 * @inheritDoc
-	 */
-	protected function setUp():void{
-		// just in case someone's running this on some weird distro that's been compiled without ext-json
-		if(!extension_loaded('json')){
-			$this::markTestSkipped('ext-json not loaded');
-		}
-
-		parent::setUp();
-	}
+	use CssColorModuleValueProviderTrait;
 
 	protected function getOutputInterface(
 		SettingsContainerInterface|QROptions $options,
@@ -41,17 +28,22 @@ final class QRStringJSONTest extends QROutputTestAbstract{
 		return new QRStringJSON($options, $matrix);
 	}
 
-	public static function moduleValueProvider():array{
-		return [[null, false]];
-	}
+	/**
+	 * @inheritDoc
+	 */
+	public function testSetModuleValues():void{
 
-	#[DataProvider('moduleValueProvider')]
-	public function testValidateModuleValues(mixed $value, bool $expected):void{
-		$this::markTestSkipped('N/A (JSON test)');
-	}
+		$this->options->moduleValues = [
+			// data
+			QRMatrix::M_DATA_DARK => '#AAA',
+			QRMatrix::M_DATA      => '#BBB',
+		];
 
-	public function testSetModuleValues():void{
-		$this::markTestSkipped('N/A (JSON test)');
+		$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
+		$data                  = $this->outputInterface->dump();
+
+		$this::assertStringContainsString('"layer":"data-dark","value":"#AAA"', $data);
+		$this::assertStringContainsString('"layer":"data","value":"#BBB"', $data);
 	}
 
 }