XML output!?

This commit is contained in:
smiley
2024-05-03 19:34:23 +02:00
parent 125656e48c
commit 1f73485224
10 changed files with 436 additions and 5 deletions
+9 -4
View File
@@ -14,7 +14,7 @@ namespace chillerlan\QRCodeBenchmark;
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};
@@ -54,9 +54,9 @@ final class OutputBenchmark extends BenchmarkAbstract{
* 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{
@@ -83,6 +83,11 @@ final class OutputBenchmark extends BenchmarkAbstract{
(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();
+1
View File
@@ -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
+36
View File
@@ -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
View File
@@ -0,0 +1,70 @@
<?php
/**
* XML output example (not a meme)
*
* @created 02.05.2024
* @author smiley <smiley@chillerlan.net>
* @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;
+1 -1
View File
@@ -85,7 +85,7 @@ class QRMarkupSVG extends QRMarkup{
// 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;
+142
View File
@@ -0,0 +1,142 @@
<?php
/**
* Class QRMarkupXML
*
* @created 01.05.2024
* @author smiley <smiley@chillerlan.net>
* @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
View File
@@ -35,6 +35,7 @@ interface QROutputInterface{
QRImagick::class,
QRMarkupHTML::class,
QRMarkupSVG::class,
QRMarkupXML::class,
QRStringJSON::class,
QRStringText::class,
];
+135
View File
@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="qrcode">
<xs:annotation>
<xs:documentation>QR Code root element</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element ref="matrix"/>
</xs:sequence>
<xs:attribute name="eccLevel" use="required">
<xs:annotation>
<xs:documentation>The ECC level: [L, M, Q, H]</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="H"/>
<xs:enumeration value="L"/>
<xs:enumeration value="M"/>
<xs:enumeration value="Q"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="version" use="required">
<xs:annotation>
<xs:documentation>The QR Code version: [1...40]</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="40"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="matrix">
<xs:annotation>
<xs:documentation>The matrix holds the encoded data in a 2-dimensional array of modules</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="row"/>
</xs:sequence>
<xs:attribute name="height" use="required">
<xs:annotation>
<xs:documentation>The total height of the matrix, including the quiet zone.</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="21"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="maskPattern" use="required">
<xs:annotation>
<xs:documentation>The detected mask pattern that was used to mask this matrix. [0...7]</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:nonNegativeInteger">
<xs:maxInclusive value="7"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="quietzoneSize" use="required" type="xs:nonNegativeInteger">
<xs:annotation>
<xs:documentation>The size of the quiet zone (margin around the QR symbol)</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="size" use="required">
<xs:annotation>
<xs:documentation>The side length of the QR symbol, excluding the quiet zone (version * 4 + 17). [21...177]</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="21"/>
<xs:maxInclusive value="177"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="width" use="required">
<xs:annotation>
<xs:documentation>The total width of the matrix, including the quiet zone.</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="21"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="row">
<xs:annotation>
<xs:documentation>A row holds an array of modules</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="module"/>
</xs:sequence>
<xs:attribute name="y" use="required" type="xs:nonNegativeInteger">
<xs:annotation>
<xs:documentation>The "y" (vertical) coordinate of this row.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="module">
<xs:annotation>
<xs:documentation>Represents a single module (pixel) of a QR symbol.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="dark" use="required" type="xs:boolean">
<xs:annotation>
<xs:documentation>Indicates whether this module is dark.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="layer" use="required" type="xs:normalizedString">
<xs:annotation>
<xs:documentation>The layer (functional pattern) this module belongs to.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="value" use="required" type="xs:normalizedString">
<xs:annotation>
<xs:documentation>The value for this module (CSS color).</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="x" use="required" type="xs:nonNegativeInteger">
<xs:annotation>
<xs:documentation>The "x" (horizontal) coordinate of this module.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
+11
View File
@@ -426,6 +426,17 @@ trait QROptionsTrait{
*/
protected string $fpdfMeasureUnit = 'pt';
/*
* QRMarkupXML settings
*/
/**
* Sets an optional XSLT stylesheet in the XML output
*
* @see https://developer.mozilla.org/en-US/docs/Web/XSLT
*/
protected ?string $xmlStylesheet = null;
/**
* clamp min/max version number
+30
View File
@@ -0,0 +1,30 @@
<?php
/**
* Class QRMarkupXMLTest
*
* @created 01.05.2024
* @author smiley <smiley@chillerlan.net>
* @copyright 2024 smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\Output\{QRMarkupXML, QROutputInterface};
use chillerlan\Settings\SettingsContainerInterface;
/**
*
*/
class QRMarkupXMLTest extends QRMarkupTestAbstract{
protected function getOutputInterface(
SettingsContainerInterface|QROptions $options,
QRMatrix $matrix,
):QROutputInterface{
return new QRMarkupXML($options, $matrix);
}
}