Skip to content

Commit 81eb21a

Browse files
committed
:octocat: QRStringJSON overhaul
1 parent f28605c commit 81eb21a

File tree

4 files changed

+229
-53
lines changed

4 files changed

+229
-53
lines changed

src/Output/QRStringJSON.php

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,50 +18,96 @@
1818
*
1919
*/
2020
class QRStringJSON extends QROutputAbstract{
21+
use CssColorModuleValueTrait;
2122

2223
final public const MIME_TYPE = 'application/json';
24+
final public const SCHEMA = 'https://raw.githubusercontent.com/chillerlan/php-qrcode/main/src/Output/qrcode.schema.json';
25+
26+
/**
27+
* @inheritDoc
28+
*/
29+
protected function getOutputDimensions():array{
30+
return [$this->moduleCount, $this->moduleCount];
31+
}
2332

2433
/**
2534
* @inheritDoc
2635
* @throws \JsonException
2736
*/
2837
public function dump(string $file = null):string{
29-
$matrix = $this->matrix->getMatrix($this->options->jsonAsBooleans);
30-
$data = json_encode($matrix, $this->options->jsonFlags);;
38+
[$width, $height] = $this->getOutputDimensions();
39+
$version = $this->matrix->getVersion();
40+
$dimension = $version->getDimension();
41+
42+
$json = [
43+
'$schema' => $this::SCHEMA,
44+
'qrcode' => [
45+
'version' => $version->getVersionNumber(),
46+
'eccLevel' => (string)$this->matrix->getEccLevel(),
47+
'matrix' => [
48+
'size' => $dimension,
49+
'quietzoneSize' => (int)(($this->moduleCount - $dimension) / 2),
50+
'maskPattern' => $this->matrix->getMaskPattern()->getPattern(),
51+
'width' => $width,
52+
'height' => $height,
53+
'rows' => [],
54+
],
55+
],
56+
];
57+
58+
foreach($this->matrix->getMatrix() as $y => $row){
59+
$matrixRow = $this->row($y, $row);
60+
61+
if($matrixRow !== null){
62+
$json['qrcode']['matrix']['rows'][] = $matrixRow;
63+
}
64+
}
65+
66+
$data = json_encode($json, $this->options->jsonFlags);;
3167

3268
$this->saveToFile($data, $file);
3369

3470
return $data;
3571
}
3672

3773
/**
38-
* unused - required by interface
39-
*
40-
* @inheritDoc
41-
* @codeCoverageIgnore
74+
* Creates an array element for a matrix row
4275
*/
43-
protected function prepareModuleValue(mixed $value):string{
44-
return '';
45-
}
76+
protected function row(int $y, array $row):array|null{
77+
$matrixRow = ['y' => $y, 'modules' => []];
4678

47-
/**
48-
* unused - required by interface
49-
*
50-
* @inheritDoc
51-
* @codeCoverageIgnore
52-
*/
53-
protected function getDefaultModuleValue(bool $isDark):string{
54-
return '';
79+
foreach($row as $x => $M_TYPE){
80+
$module = $this->module($x, $y, $M_TYPE);
81+
82+
if($module !== null){
83+
$matrixRow['modules'][] = $module;
84+
}
85+
}
86+
87+
if(!empty($matrixRow['modules'])){
88+
return $matrixRow;
89+
}
90+
91+
// skip empty rows
92+
return null;
5593
}
5694

5795
/**
58-
* unused - required by interface
59-
*
60-
* @inheritDoc
61-
* @codeCoverageIgnore
96+
* Creates an array element for a single module
6297
*/
63-
public static function moduleValueIsValid(mixed $value):bool{
64-
return true;
98+
protected function module(int $x, int $y, int $M_TYPE):array|null{
99+
$isDark = $this->matrix->isDark($M_TYPE);
100+
101+
if(!$this->drawLightModules && !$isDark){
102+
return null;
103+
}
104+
105+
return [
106+
'x' => $x,
107+
'dark' => $isDark,
108+
'layer' => ($this::LAYERNAMES[$M_TYPE] ?? ''),
109+
'value' => $this->getModuleValue($M_TYPE),
110+
];
65111
}
66112

67113
}

src/Output/qrcode.schema.json

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"title": "chillerlan php-qrcode schema",
4+
"type": "object",
5+
"required": [
6+
"qrcode"
7+
],
8+
"properties": {
9+
"qrcode": {
10+
"$ref": "#/$defs/qrcode"
11+
}
12+
},
13+
"$defs": {
14+
"qrcode": {
15+
"description": "QR Code root element",
16+
"type": "object",
17+
"required": [
18+
"eccLevel",
19+
"matrix",
20+
"version"
21+
],
22+
"properties": {
23+
"version": {
24+
"description": "The QR Code version: [1...40]",
25+
"type": "number",
26+
"minimum": 1,
27+
"maximum": 40
28+
},
29+
"eccLevel": {
30+
"description": "The ECC level: [L, M, Q, H]",
31+
"enum": [
32+
"L",
33+
"M",
34+
"Q",
35+
"H"
36+
]
37+
},
38+
"matrix": {
39+
"$ref": "#/$defs/matrix"
40+
}
41+
}
42+
},
43+
"matrix": {
44+
"description": "The matrix holds the encoded data in a 2-dimensional array of modules.",
45+
"type": "object",
46+
"required": [
47+
"size",
48+
"quietzoneSize",
49+
"maskPattern",
50+
"width",
51+
"height"
52+
],
53+
"properties": {
54+
"size": {
55+
"description": "The side length of the QR symbol, excluding the quiet zone (version * 4 + 17). [21...177]",
56+
"type": "number",
57+
"minimum": 21,
58+
"maximum": 177
59+
},
60+
"quietzoneSize": {
61+
"description": "The size of the quiet zone (margin around the QR symbol).",
62+
"type": "number",
63+
"minimum": 0
64+
},
65+
"maskPattern": {
66+
"description": "The detected mask pattern that was used to mask this matrix. [0...7].",
67+
"type": "number",
68+
"minimum": 0,
69+
"maximum": 7
70+
},
71+
"width": {
72+
"description": "The total width of the matrix, including the quiet zone.",
73+
"type": "number",
74+
"minimum": 21
75+
},
76+
"height": {
77+
"description": "The total height of the matrix, including the quiet zone.",
78+
"type": "number",
79+
"minimum": 21
80+
},
81+
"rows": {
82+
"type": "array",
83+
"items": {
84+
"$ref": "#/$defs/row"
85+
},
86+
"minItems": 0
87+
}
88+
}
89+
},
90+
"row": {
91+
"description": "A row holds an array of modules",
92+
"type": "object",
93+
"required": [
94+
"y",
95+
"modules"
96+
],
97+
"properties": {
98+
"y": {
99+
"description": "The 'y' (vertical) coordinate of this row.",
100+
"type": "number",
101+
"minimum": 0
102+
},
103+
"modules": {
104+
"type": "array",
105+
"items": {
106+
"$ref": "#/$defs/module"
107+
},
108+
"minItems": 0
109+
}
110+
}
111+
},
112+
"module": {
113+
"description": "Represents a single module (pixel) of a QR symbol.",
114+
"type": "object",
115+
"required": [
116+
"dark",
117+
"layer",
118+
"value",
119+
"x"
120+
],
121+
"properties": {
122+
"dark": {
123+
"description": "Indicates whether this module is dark.",
124+
"type": "boolean"
125+
},
126+
"layer": {
127+
"description": "The layer (functional pattern) this module belongs to.",
128+
"type": "string"
129+
},
130+
"value": {
131+
"description": "The value for this module.",
132+
"type": "string"
133+
},
134+
"x": {
135+
"description": "The 'x' (horizontal) coordinate of this module.",
136+
"type": "integer",
137+
"minimum": 0
138+
}
139+
}
140+
}
141+
}
142+
}

src/QROptionsTrait.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
1818
use chillerlan\QRCode\Output\QRMarkupSVG;
1919
use function constant, in_array, is_string, max, min, sprintf, strtolower, strtoupper, trim;
20-
use const JSON_THROW_ON_ERROR, PHP_EOL;
20+
use const JSON_THROW_ON_ERROR, JSON_UNESCAPED_SLASHES, PHP_EOL;
21+
2122

2223
/**
2324
* The QRCode plug-in settings & setter functionality
@@ -408,12 +409,7 @@ trait QROptionsTrait{
408409
*
409410
* @see https://www.php.net/manual/json.constants.php
410411
*/
411-
protected int $jsonFlags = JSON_THROW_ON_ERROR;
412-
413-
/**
414-
* Whether to return matrix values in JSON as booleans or `$M_TYPE` integers
415-
*/
416-
protected bool $jsonAsBooleans = false;
412+
protected int $jsonFlags = JSON_THROW_ON_ERROR|JSON_UNESCAPED_SLASHES;
417413

418414
/*
419415
* QRFpdf settings

tests/Output/QRStringJSONTest.php

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,12 @@
1414
use chillerlan\QRCode\Data\QRMatrix;
1515
use chillerlan\QRCode\Output\{QROutputInterface, QRStringJSON};
1616
use chillerlan\Settings\SettingsContainerInterface;
17-
use PHPUnit\Framework\Attributes\DataProvider;
18-
use function extension_loaded;
1917

2018
/**
2119
*
2220
*/
2321
final class QRStringJSONTest extends QROutputTestAbstract{
24-
25-
/**
26-
* @inheritDoc
27-
*/
28-
protected function setUp():void{
29-
// just in case someone's running this on some weird distro that's been compiled without ext-json
30-
if(!extension_loaded('json')){
31-
$this::markTestSkipped('ext-json not loaded');
32-
}
33-
34-
parent::setUp();
35-
}
22+
use CssColorModuleValueProviderTrait;
3623

3724
protected function getOutputInterface(
3825
SettingsContainerInterface|QROptions $options,
@@ -41,17 +28,22 @@ protected function getOutputInterface(
4128
return new QRStringJSON($options, $matrix);
4229
}
4330

44-
public static function moduleValueProvider():array{
45-
return [[null, false]];
46-
}
31+
/**
32+
* @inheritDoc
33+
*/
34+
public function testSetModuleValues():void{
4735

48-
#[DataProvider('moduleValueProvider')]
49-
public function testValidateModuleValues(mixed $value, bool $expected):void{
50-
$this::markTestSkipped('N/A (JSON test)');
51-
}
36+
$this->options->moduleValues = [
37+
// data
38+
QRMatrix::M_DATA_DARK => '#AAA',
39+
QRMatrix::M_DATA => '#BBB',
40+
];
5241

53-
public function testSetModuleValues():void{
54-
$this::markTestSkipped('N/A (JSON test)');
42+
$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
43+
$data = $this->outputInterface->dump();
44+
45+
$this::assertStringContainsString('"layer":"data-dark","value":"#AAA"', $data);
46+
$this::assertStringContainsString('"layer":"data","value":"#BBB"', $data);
5547
}
5648

5749
}

0 commit comments

Comments
 (0)