Skip to content

Commit 1f73485

Browse files
committed
✨ XML output!?
1 parent 125656e commit 1f73485

File tree

10 files changed

+436
-5
lines changed

10 files changed

+436
-5
lines changed

benchmark/OutputBenchmark.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use chillerlan\QRCode\Common\Mode;
1515
use chillerlan\QRCode\Data\Byte;
1616
use chillerlan\QRCode\Output\{
17-
QREps, QRFpdf, QRGdImageAVIF, QRGdImageJPEG, QRGdImagePNG, QRGdImageWEBP, QRImagick, QRMarkupSVG, QRStringJSON
17+
QREps, QRFpdf, QRGdImageJPEG, QRGdImagePNG, QRGdImageWEBP, QRImagick, QRMarkupSVG, QRMarkupXML, QRStringJSON
1818
};
1919
use PhpBench\Attributes\{BeforeMethods, Subject};
2020

@@ -54,9 +54,9 @@ public function QRFpdf():void{
5454
* for some reason imageavif() is extremely slow, ~50x slower than imagepng()
5555
*/
5656
#[Subject]
57-
public function QRGdImageAVIF():void{
58-
(new QRGdImageAVIF($this->options, $this->matrix))->dump();
59-
}
57+
# public function QRGdImageAVIF():void{
58+
# (new \chillerlan\QRCode\Output\QRGdImageAVIF($this->options, $this->matrix))->dump();
59+
# }
6060

6161
#[Subject]
6262
public function QRGdImageJPEG():void{
@@ -83,6 +83,11 @@ public function QRMarkupSVG():void{
8383
(new QRMarkupSVG($this->options, $this->matrix))->dump();
8484
}
8585

86+
#[Subject]
87+
public function QRMarkupXML():void{
88+
(new QRMarkupXML($this->options, $this->matrix))->dump();
89+
}
90+
8691
#[Subject]
8792
public function QRStringJSON():void{
8893
(new QRStringJSON($this->options, $this->matrix))->dump();

examples/Readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [FPDF](./fpdf.php): PDF output via [FPDF](http://www.fpdf.org/)
1010
- [EPS](./eps.php): Encapsulated PostScript
1111
- [String](./text.php): String output
12+
- [XML](./xml.php): XML output (rendered as SVG via an [XSLT style](./qrcode.style.xsl))
1213
- [Multi mode](./multimode.php): a demostration of multi mode usage
1314
- [Reflectance](./reflectance.php): demonstrates reflectance reversal
1415
- [QRCode reader](./reader.php): a simple reader example

examples/qrcode.style.xsl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!-- XSLT style for the XML output example -->
3+
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
4+
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
5+
<xsl:template match="/">
6+
<!-- SVG header -->
7+
<svg xmlns="http://www.w3.org/2000/svg"
8+
version="1.0"
9+
viewBox="0 0 {qrcode/matrix/@width} {qrcode/matrix/@height}"
10+
preserveAspectRatio="xMidYMid"
11+
>
12+
<!--
13+
path for a single module
14+
we could define a path for each layer and use the @layer attribute for selection,
15+
but that would exaggerate this example
16+
-->
17+
<symbol id="module" width="1" height="1">
18+
<circle cx="0.5" cy="0.5" r="0.4" />
19+
</symbol>
20+
<!-- loop over the rows -->
21+
<xsl:for-each select="qrcode/matrix/row">
22+
<!-- set a variable for $y (vertical) -->
23+
<xsl:variable name="y" select="@y"/>
24+
<xsl:for-each select="module">
25+
<!-- set a variable for $x (horizontal) -->
26+
<xsl:variable name="x" select="@x"/>
27+
<!-- draw only dark modules -->
28+
<xsl:if test="@dark='true'">
29+
<!-- position the module and set its fill color -->
30+
<use href="#module" class="{@layer}" x="{$x}" y="{$y}" fill="{@value}"/>
31+
</xsl:if>
32+
</xsl:for-each>
33+
</xsl:for-each>
34+
</svg>
35+
</xsl:template>
36+
</xsl:stylesheet>

examples/xml.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
/**
3+
* XML output example (not a meme)
4+
*
5+
* @created 02.05.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
11+
use chillerlan\QRCode\{Data\QRMatrix, QRCode, QROptions};
12+
use chillerlan\QRCode\Output\QRMarkupXML;
13+
14+
require_once __DIR__.'/../vendor/autoload.php';
15+
16+
$options = new QROptions;
17+
18+
$options->version = 7;
19+
$options->outputInterface = QRMarkupXML::class;
20+
$options->outputBase64 = false;
21+
$options->drawLightModules = false;
22+
23+
// assign an XSLT stylesheet
24+
$options->xmlStylesheet = './qrcode.style.xsl';
25+
26+
$options->moduleValues = [
27+
// finder
28+
QRMatrix::M_FINDER_DARK => '#A71111', // dark (true)
29+
QRMatrix::M_FINDER_DOT => '#A71111', // finder dot, dark (true)
30+
QRMatrix::M_FINDER => '#FFBFBF', // light (false)
31+
// alignment
32+
QRMatrix::M_ALIGNMENT_DARK => '#A70364',
33+
QRMatrix::M_ALIGNMENT => '#FFC9C9',
34+
// timing
35+
QRMatrix::M_TIMING_DARK => '#98005D',
36+
QRMatrix::M_TIMING => '#FFB8E9',
37+
// format
38+
QRMatrix::M_FORMAT_DARK => '#003804',
39+
QRMatrix::M_FORMAT => '#CCFB12',
40+
// version
41+
QRMatrix::M_VERSION_DARK => '#650098',
42+
QRMatrix::M_VERSION => '#E0B8FF',
43+
// data
44+
QRMatrix::M_DATA_DARK => '#4A6000',
45+
QRMatrix::M_DATA => '#ECF9BE',
46+
// darkmodule
47+
QRMatrix::M_DARKMODULE => '#080063',
48+
// separator
49+
QRMatrix::M_SEPARATOR => '#DDDDDD',
50+
// quietzone
51+
QRMatrix::M_QUIETZONE => '#DDDDDD',
52+
];
53+
54+
55+
try{
56+
$out = (new QRCode($options))->render('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
57+
}
58+
catch(Throwable $e){
59+
// handle the exception in whatever way you need
60+
exit($e->getMessage());
61+
}
62+
63+
64+
if(php_sapi_name() !== 'cli'){
65+
header('Content-type: '.QRMarkupXML::MIME_TYPE);
66+
}
67+
68+
echo $out;
69+
70+
exit;

src/Output/QRMarkupSVG.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ protected function createMarkup(bool $saveToFile):string{
8585

8686
// transform to data URI only when not saving to file
8787
if(!$saveToFile && $this->options->outputBase64){
88-
$svg = $this->toBase64DataURI($svg);
88+
return $this->toBase64DataURI($svg);
8989
}
9090

9191
return $svg;

src/Output/QRMarkupXML.php

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
/**
3+
* Class QRMarkupXML
4+
*
5+
* @created 01.05.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*
10+
* @noinspection PhpComposerExtensionStubsInspection
11+
* @phan-file-suppress PhanTypeMismatchArgumentInternal
12+
*/
13+
14+
namespace chillerlan\QRCode\Output;
15+
16+
use DOMDocument;
17+
use DOMElement;
18+
use function sprintf;
19+
20+
/**
21+
* XML/XSLT output
22+
*/
23+
class QRMarkupXML extends QRMarkup{
24+
25+
final public const MIME_TYPE = 'application/xml';
26+
protected const XML_SCHEMA = 'https://raw.githubusercontent.com/chillerlan/php-qrcode/main/src/Output/qrcode.schema.xsd';
27+
28+
protected DOMDocument $dom;
29+
30+
/**
31+
* @inheritDoc
32+
*/
33+
protected function getOutputDimensions():array{
34+
return [$this->moduleCount, $this->moduleCount];
35+
}
36+
37+
/**
38+
* @inheritDoc
39+
*/
40+
protected function createMarkup(bool $saveToFile):string{
41+
/** @noinspection PhpComposerExtensionStubsInspection */
42+
$this->dom = new DOMDocument(encoding: 'UTF-8');
43+
$this->dom->formatOutput = true;
44+
45+
if($this->options->xmlStylesheet !== null){
46+
$stylesheet = sprintf('type="text/xsl" href="%s"', $this->options->xmlStylesheet);
47+
$xslt = $this->dom->createProcessingInstruction('xml-stylesheet', $stylesheet);
48+
49+
$this->dom->appendChild($xslt);
50+
}
51+
52+
$root = $this->dom->createElement('qrcode');
53+
54+
$root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
55+
$root->setAttribute('xsi:noNamespaceSchemaLocation', $this::XML_SCHEMA);
56+
$root->setAttribute('version', $this->matrix->getVersion());
57+
$root->setAttribute('eccLevel', $this->matrix->getEccLevel());
58+
$root->appendChild($this->createMatrix());
59+
60+
$this->dom->appendChild($root);
61+
62+
$xml = $this->dom->saveXML();
63+
64+
// transform to data URI only when not saving to file
65+
if(!$saveToFile && $this->options->outputBase64){
66+
return $this->toBase64DataURI($xml);
67+
}
68+
69+
return $xml;
70+
}
71+
72+
/**
73+
* Creates the matrix element
74+
*/
75+
protected function createMatrix():DOMElement{
76+
[$width, $height] = $this->getOutputDimensions();
77+
$matrix = $this->dom->createElement('matrix');
78+
$dimension = $this->matrix->getVersion()->getDimension();
79+
80+
$matrix->setAttribute('size', $dimension);
81+
$matrix->setAttribute('quietzoneSize', (int)(($this->moduleCount - $dimension) / 2));
82+
$matrix->setAttribute('maskPattern', $this->matrix->getMaskPattern()->getPattern());
83+
$matrix->setAttribute('width', $width);
84+
$matrix->setAttribute('height', $height);
85+
86+
foreach($this->matrix->getMatrix() as $y => $row){
87+
$matrixRow = $this->row($y, $row);
88+
89+
if($matrixRow !== null){
90+
$matrix->appendChild($matrixRow);
91+
}
92+
}
93+
94+
return $matrix;
95+
}
96+
97+
/**
98+
* Creates a DOM element for a matrix row
99+
*/
100+
protected function row(int $y, array $row):DOMElement|null{
101+
$matrixRow = $this->dom->createElement('row');
102+
103+
$matrixRow->setAttribute('y', $y);
104+
105+
foreach($row as $x => $M_TYPE){
106+
$module = $this->module($x, $y, $M_TYPE);
107+
108+
if($module !== null){
109+
$matrixRow->appendChild($module);
110+
}
111+
112+
}
113+
114+
if($matrixRow->childElementCount > 0){
115+
return $matrixRow;
116+
}
117+
118+
// skip empty rows
119+
return null;
120+
}
121+
122+
/**
123+
* Creates a DOM element for single module
124+
*/
125+
protected function module(int $x, int $y, int $M_TYPE):DOMElement|null{
126+
$isDark = $this->matrix->isDark($M_TYPE);
127+
128+
if(!$this->drawLightModules && !$isDark){
129+
return null;
130+
}
131+
132+
$module = $this->dom->createElement('module');
133+
134+
$module->setAttribute('x', $x);
135+
$module->setAttribute('dark', ($isDark ? 'true' : 'false'));
136+
$module->setAttribute('layer', ($this::LAYERNAMES[$M_TYPE] ?? ''));
137+
$module->setAttribute('value', $this->getModuleValue($M_TYPE));
138+
139+
return $module;
140+
}
141+
142+
}

src/Output/QROutputInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ interface QROutputInterface{
3535
QRImagick::class,
3636
QRMarkupHTML::class,
3737
QRMarkupSVG::class,
38+
QRMarkupXML::class,
3839
QRStringJSON::class,
3940
QRStringText::class,
4041
];

0 commit comments

Comments
 (0)