Skip to content

Commit

Permalink
✨ XML output!?
Browse files Browse the repository at this point in the history
  • Loading branch information
codemasher committed May 3, 2024
1 parent 125656e commit 1f73485
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 5 deletions.
13 changes: 9 additions & 4 deletions benchmark/OutputBenchmark.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use chillerlan\QRCode\Common\Mode;
use chillerlan\QRCode\Data\Byte;
use chillerlan\QRCode\Output\{
QREps, QRFpdf, QRGdImageAVIF, QRGdImageJPEG, QRGdImagePNG, QRGdImageWEBP, QRImagick, QRMarkupSVG, QRStringJSON
QREps, QRFpdf, QRGdImageJPEG, QRGdImagePNG, QRGdImageWEBP, QRImagick, QRMarkupSVG, QRMarkupXML, QRStringJSON
};
use PhpBench\Attributes\{BeforeMethods, Subject};

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

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

#[Subject]
public function QRMarkupXML():void{
(new QRMarkupXML($this->options, $this->matrix))->dump();
}

#[Subject]
public function QRStringJSON():void{
(new QRStringJSON($this->options, $this->matrix))->dump();
Expand Down
1 change: 1 addition & 0 deletions examples/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [FPDF](./fpdf.php): PDF output via [FPDF](http://www.fpdf.org/)
- [EPS](./eps.php): Encapsulated PostScript
- [String](./text.php): String output
- [XML](./xml.php): XML output (rendered as SVG via an [XSLT style](./qrcode.style.xsl))
- [Multi mode](./multimode.php): a demostration of multi mode usage
- [Reflectance](./reflectance.php): demonstrates reflectance reversal
- [QRCode reader](./reader.php): a simple reader example
Expand Down
36 changes: 36 additions & 0 deletions examples/qrcode.style.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- XSLT style for the XML output example -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<!-- SVG header -->
<svg xmlns="http://www.w3.org/2000/svg"
version="1.0"
viewBox="0 0 {qrcode/matrix/@width} {qrcode/matrix/@height}"
preserveAspectRatio="xMidYMid"
>
<!--
path for a single module
we could define a path for each layer and use the @layer attribute for selection,
but that would exaggerate this example
-->
<symbol id="module" width="1" height="1">
<circle cx="0.5" cy="0.5" r="0.4" />
</symbol>
<!-- loop over the rows -->
<xsl:for-each select="qrcode/matrix/row">
<!-- set a variable for $y (vertical) -->
<xsl:variable name="y" select="@y"/>
<xsl:for-each select="module">
<!-- set a variable for $x (horizontal) -->
<xsl:variable name="x" select="@x"/>
<!-- draw only dark modules -->
<xsl:if test="@dark='true'">
<!-- position the module and set its fill color -->
<use href="#module" class="{@layer}" x="{$x}" y="{$y}" fill="{@value}"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</svg>
</xsl:template>
</xsl:stylesheet>
70 changes: 70 additions & 0 deletions examples/xml.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php
/**
* XML output example (not a meme)
*
* @created 02.05.2024
* @author smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*/

use chillerlan\QRCode\{Data\QRMatrix, QRCode, QROptions};
use chillerlan\QRCode\Output\QRMarkupXML;

require_once __DIR__.'/../vendor/autoload.php';

$options = new QROptions;

$options->version = 7;
$options->outputInterface = QRMarkupXML::class;
$options->outputBase64 = false;
$options->drawLightModules = false;

// assign an XSLT stylesheet
$options->xmlStylesheet = './qrcode.style.xsl';

$options->moduleValues = [
// finder
QRMatrix::M_FINDER_DARK => '#A71111', // dark (true)
QRMatrix::M_FINDER_DOT => '#A71111', // finder dot, dark (true)
QRMatrix::M_FINDER => '#FFBFBF', // light (false)
// alignment
QRMatrix::M_ALIGNMENT_DARK => '#A70364',
QRMatrix::M_ALIGNMENT => '#FFC9C9',
// timing
QRMatrix::M_TIMING_DARK => '#98005D',
QRMatrix::M_TIMING => '#FFB8E9',
// format
QRMatrix::M_FORMAT_DARK => '#003804',
QRMatrix::M_FORMAT => '#CCFB12',
// version
QRMatrix::M_VERSION_DARK => '#650098',
QRMatrix::M_VERSION => '#E0B8FF',
// data
QRMatrix::M_DATA_DARK => '#4A6000',
QRMatrix::M_DATA => '#ECF9BE',
// darkmodule
QRMatrix::M_DARKMODULE => '#080063',
// separator
QRMatrix::M_SEPARATOR => '#DDDDDD',
// quietzone
QRMatrix::M_QUIETZONE => '#DDDDDD',
];


try{
$out = (new QRCode($options))->render('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
}
catch(Throwable $e){
// handle the exception in whatever way you need
exit($e->getMessage());
}


if(php_sapi_name() !== 'cli'){
header('Content-type: '.QRMarkupXML::MIME_TYPE);
}

echo $out;

exit;
2 changes: 1 addition & 1 deletion src/Output/QRMarkupSVG.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ protected function createMarkup(bool $saveToFile):string{

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

return $svg;
Expand Down
142 changes: 142 additions & 0 deletions src/Output/QRMarkupXML.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php
/**
* Class QRMarkupXML
*
* @created 01.05.2024
* @author smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
* @phan-file-suppress PhanTypeMismatchArgumentInternal
*/

namespace chillerlan\QRCode\Output;

use DOMDocument;
use DOMElement;
use function sprintf;

/**
* XML/XSLT output
*/
class QRMarkupXML extends QRMarkup{

final public const MIME_TYPE = 'application/xml';
protected const XML_SCHEMA = 'https://raw.githubusercontent.com/chillerlan/php-qrcode/main/src/Output/qrcode.schema.xsd';

protected DOMDocument $dom;

/**
* @inheritDoc
*/
protected function getOutputDimensions():array{
return [$this->moduleCount, $this->moduleCount];
}

/**
* @inheritDoc
*/
protected function createMarkup(bool $saveToFile):string{
/** @noinspection PhpComposerExtensionStubsInspection */
$this->dom = new DOMDocument(encoding: 'UTF-8');
$this->dom->formatOutput = true;

if($this->options->xmlStylesheet !== null){
$stylesheet = sprintf('type="text/xsl" href="%s"', $this->options->xmlStylesheet);
$xslt = $this->dom->createProcessingInstruction('xml-stylesheet', $stylesheet);

$this->dom->appendChild($xslt);
}

$root = $this->dom->createElement('qrcode');

$root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$root->setAttribute('xsi:noNamespaceSchemaLocation', $this::XML_SCHEMA);
$root->setAttribute('version', $this->matrix->getVersion());
$root->setAttribute('eccLevel', $this->matrix->getEccLevel());
$root->appendChild($this->createMatrix());

$this->dom->appendChild($root);

$xml = $this->dom->saveXML();

// transform to data URI only when not saving to file
if(!$saveToFile && $this->options->outputBase64){
return $this->toBase64DataURI($xml);
}

return $xml;
}

/**
* Creates the matrix element
*/
protected function createMatrix():DOMElement{
[$width, $height] = $this->getOutputDimensions();
$matrix = $this->dom->createElement('matrix');
$dimension = $this->matrix->getVersion()->getDimension();

$matrix->setAttribute('size', $dimension);
$matrix->setAttribute('quietzoneSize', (int)(($this->moduleCount - $dimension) / 2));
$matrix->setAttribute('maskPattern', $this->matrix->getMaskPattern()->getPattern());
$matrix->setAttribute('width', $width);
$matrix->setAttribute('height', $height);

foreach($this->matrix->getMatrix() as $y => $row){
$matrixRow = $this->row($y, $row);

if($matrixRow !== null){
$matrix->appendChild($matrixRow);
}
}

return $matrix;
}

/**
* Creates a DOM element for a matrix row
*/
protected function row(int $y, array $row):DOMElement|null{
$matrixRow = $this->dom->createElement('row');

$matrixRow->setAttribute('y', $y);

foreach($row as $x => $M_TYPE){
$module = $this->module($x, $y, $M_TYPE);

if($module !== null){
$matrixRow->appendChild($module);
}

}

if($matrixRow->childElementCount > 0){
return $matrixRow;
}

// skip empty rows
return null;
}

/**
* Creates a DOM element for single module
*/
protected function module(int $x, int $y, int $M_TYPE):DOMElement|null{
$isDark = $this->matrix->isDark($M_TYPE);

if(!$this->drawLightModules && !$isDark){
return null;
}

$module = $this->dom->createElement('module');

$module->setAttribute('x', $x);
$module->setAttribute('dark', ($isDark ? 'true' : 'false'));
$module->setAttribute('layer', ($this::LAYERNAMES[$M_TYPE] ?? ''));
$module->setAttribute('value', $this->getModuleValue($M_TYPE));

return $module;
}

}
1 change: 1 addition & 0 deletions src/Output/QROutputInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface QROutputInterface{
QRImagick::class,
QRMarkupHTML::class,
QRMarkupSVG::class,
QRMarkupXML::class,
QRStringJSON::class,
QRStringText::class,
];
Expand Down
Loading

0 comments on commit 1f73485

Please sign in to comment.