mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-08-30 20:17:17 +00:00
✨ XML output!?
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,6 +35,7 @@ interface QROutputInterface{
|
||||
QRImagick::class,
|
||||
QRMarkupHTML::class,
|
||||
QRMarkupSVG::class,
|
||||
QRMarkupXML::class,
|
||||
QRStringJSON::class,
|
||||
QRStringText::class,
|
||||
];
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user