more tests & cleanup

This commit is contained in:
smiley
2017-12-24 07:54:47 +01:00
parent 66d551520f
commit 019d932d35
11 changed files with 417 additions and 107 deletions
+106 -35
View File
@@ -12,14 +12,18 @@
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\{QRCode, Data\QRMatrix};
/**
* Converts the matrix into images, raw or base64 output
*/
class QRImage extends QROutputAbstract{
const transparencyTypes = [
QRCode::OUTPUT_IMAGE_PNG,
QRCode::OUTPUT_IMAGE_GIF,
];
protected $moduleValues = [
// light
QRMatrix::M_DATA => [255, 255, 255],
@@ -42,40 +46,51 @@ class QRImage extends QROutputAbstract{
QRMatrix::M_TEST << 8 => [0, 0, 0],
];
/**
* @see imagecreatetruecolor()
* @var resource
*/
protected $image;
/**
* @var int
*/
protected $scale;
/**
* @var int
*/
protected $length;
/**
* @see imagecolorallocate()
* @var int
*/
protected $background;
/**
* @return string
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
public function dump():string{
$scale = $this->options->scale;
$length = $this->moduleCount * $scale;
$image = imagecreatetruecolor($length, $length);
$background = imagecolorallocate($image, ...$this->options->imageTransparencyBG);
$moduleValues = is_array($this->options->moduleValues[QRMatrix::M_DATA])
if($this->options->cachefile !== null && !is_writable(dirname($this->options->cachefile))){
throw new QRCodeOutputException('Could not write data to cache file: '.$this->options->cachefile);
}
$this->setImage();
$moduleValues = is_array($this->options->moduleValues[$this->matrix::M_DATA])
? $this->options->moduleValues // @codeCoverageIgnore
: $this->moduleValues;
if((bool)$this->options->imageTransparent && in_array($this->options->outputType, [QRCode::OUTPUT_IMAGE_PNG, QRCode::OUTPUT_IMAGE_GIF,], true)){
imagecolortransparent($image, $background);
}
imagefilledrectangle($image, 0, 0, $length, $length, $background);
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $pixel){
$color = imagecolorallocate($image, ...$moduleValues[$pixel]);
imagefilledrectangle($image, $x * $scale, $y * $scale, ($x + 1) * $scale - 1, ($y + 1) * $scale - 1, $color);
$this->setPixel($x, $y, imagecolorallocate($this->image, ...$moduleValues[$pixel]));
}
}
ob_start();
call_user_func_array([$this, $this->options->outputType ?? QRCode::OUTPUT_IMAGE_PNG], [&$image]);
$imageData = ob_get_contents();
imagedestroy($image);
ob_end_clean();
$imageData = $this->dumpImage();
if((bool)$this->options->imageBase64){
$imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData);
@@ -85,34 +100,90 @@ class QRImage extends QROutputAbstract{
}
/**
* @param $image
* @return void
*/
protected function png(&$image){
protected function setImage(){
$this->scale = $this->options->scale;
$this->length = $this->moduleCount * $this->scale;
$this->image = imagecreatetruecolor($this->length, $this->length);
$this->background = imagecolorallocate($this->image, ...$this->options->imageTransparencyBG);
if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::transparencyTypes, true)){
imagecolortransparent($this->image, $this->background);
}
imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $this->background);
}
/**
* @param $x
* @param $y
* @param $color
* @return void
*/
protected function setPixel($x, $y, $color){
imagefilledrectangle(
$this->image,
$x * $this->scale,
$y * $this->scale,
($x + 1) * $this->scale - 1,
($y + 1) * $this->scale - 1,
$color
);
}
/**
* @return string
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function dumpImage():string {
ob_start();
try{
call_user_func([$this, $this->options->outputType ?? QRCode::OUTPUT_IMAGE_PNG]);
}
// not going to cover edge cases
// @codeCoverageIgnoreStart
catch(\Exception $e){
throw new QRCodeOutputException($e->getMessage());
}
// @codeCoverageIgnoreEnd
$imageData = ob_get_contents();
imagedestroy($this->image);
ob_end_clean();
return $imageData;
}
/**
* @return void
*/
protected function png(){
imagepng(
$image,
$this->image,
$this->options->cachefile,
in_array($this->options->pngCompression, range(-1, 9), true)
? $this->options->pngCompression
: -1
);
}
/**
* Jiff - like... JitHub!
*
* @param $image
* @return void
*/
protected function gif(&$image){
imagegif($image, $this->options->cachefile);
protected function gif(){
imagegif($this->image, $this->options->cachefile);
}
/**
* @param $image
* @return void
*/
protected function jpg(&$image){
protected function jpg(){
imagejpeg(
$image,
$this->image,
$this->options->cachefile,
in_array($this->options->jpegQuality, range(0, 100), true)
? $this->options->jpegQuality
+16 -14
View File
@@ -23,13 +23,20 @@ class QRMarkup extends QROutputAbstract{
* @return string
*/
public function dump(){
switch($this->options->outputType){
case QRCode::OUTPUT_MARKUP_HTML:
return $this->toHTML();
case QRCode::OUTPUT_MARKUP_SVG :
default:
return $this->toSVG();
if($this->options->cachefile !== null && !is_writable(dirname($this->options->cachefile))){
throw new QRCodeOutputException('Could not write data to cache file: '.$this->options->cachefile);
}
$data = $this->options->outputType === QRCode::OUTPUT_MARKUP_HTML
? $this->toHTML()
: $this->toSVG();
if($this->options->cachefile !== null){
$this->saveToFile($data);
}
return $data;
}
/**
@@ -45,14 +52,11 @@ class QRMarkup extends QROutputAbstract{
$html .= '<span style="background: '.($this->options->moduleValues[$pixel] ?: 'lightgrey').';"></span>';
}
$html .= '</div>';
$html .= $this->options->eol;
$html .= '</div>'.$this->options->eol;
}
if($this->options->cachefile){
$html = '<!DOCTYPE html><head><meta charset="UTF-8"></head><body>'.$this->options->eol.$html.'</body>';
return $this->saveToFile($html);
return '<!DOCTYPE html><head><meta charset="UTF-8"></head><body>'.$this->options->eol.$html.'</body>';
}
return $html;
@@ -117,9 +121,7 @@ class QRMarkup extends QROutputAbstract{
// if saving to file, append the correct headers
if($this->options->cachefile){
$svg = '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'.$this->options->eol.$svg;
return $this->saveToFile($svg);
return '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'.$this->options->eol.$svg;
}
return $svg;
+10 -26
View File
@@ -12,9 +12,7 @@
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\{
Data\QRMatrix, QROptions
};
use chillerlan\QRCode\{Data\QRMatrix, QROptions};
/**
*
@@ -41,36 +39,22 @@ abstract class QROutputAbstract implements QROutputInterface{
*
* @param \chillerlan\QRCode\QROptions $options
* @param \chillerlan\QRCode\Data\QRMatrix $matrix
*
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
public function __construct(QROptions $options, QRMatrix $matrix){
$this->options = $options;
$this->moduleCount = $matrix->size();
if($this->moduleCount < 21){ // minimum QR modules @todo: quet zone
throw new QRCodeOutputException('Invalid matrix!');
}
$this->matrix = $matrix;
$this->options = $options;
$this->matrix = $matrix;
$this->moduleCount = $this->matrix->size();
}
/**
* @param string $data
* @see file_put_contents()
*
* @return bool
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
* @param string $data
* @return bool|int
*/
protected function saveToFile(string $data):bool {
try{
return (bool)file_put_contents($this->options->cachefile, $data);
}
catch(\Exception $e){
throw new QRCodeOutputException('Could not write data to cache file: '.$e->getMessage());
}
protected function saveToFile(string $data) {
return file_put_contents($this->options->cachefile, $data);
}
}
+22 -13
View File
@@ -24,39 +24,48 @@ class QRString extends QROutputAbstract{
*/
public function dump():string{
switch($this->options->outputType){
case QRCode::OUTPUT_STRING_TEXT:
return $this->toString();
case QRCode::OUTPUT_STRING_JSON:
default:
return json_encode($this->matrix->matrix());
$data = $this->options->outputType === QRCode::OUTPUT_STRING_JSON
? json_encode($this->matrix->matrix())
: $this->toString();
if($this->options->cachefile !== null){
if(!is_writable(dirname($this->options->cachefile))){
throw new QRCodeOutputException('Could not write data to cache file: '.$this->options->cachefile);
}
$this->saveToFile($data);
}
return $data;
}
/**
* @return string
*/
protected function toString():string{
$str = '';
$str = [];
foreach($this->matrix->matrix() as $row){
$r = [];
foreach($row as $col){
$col = $this->options->moduleValues[$col];
// fallback
if(is_bool($col) || !is_string($col)){
$col = $col ? $this->options->textDark : $this->options->textLight;
$col = $col
? $this->options->textDark
: $this->options->textLight;
}
$str .= $col;
$r[] = $col;
}
$str .= $this->options->eol;
$str[] = implode('', $r);
}
return $str;
return implode($this->options->eol, $str);
}
}
-2
View File
@@ -321,8 +321,6 @@ class QRCode{
/**
* a dummy
*
* @codeCoverageIgnore
*
* @param $data
*
* @return bool
+52
View File
@@ -0,0 +1,52 @@
<?php
/**
* Class QRImageTest
*
* @filesource QRImageTest.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\Output\QRImage;
use chillerlan\QRCode\QRCode;
class QRImageTest extends QROutputTestAbstract{
protected $FQCN = QRImage::class;
public function types(){
return [
[QRCode::OUTPUT_IMAGE_PNG],
[QRCode::OUTPUT_IMAGE_GIF],
[QRCode::OUTPUT_IMAGE_JPG],
];
}
/**
* @dataProvider types
* @param $type
*/
public function testImageOutput($type){
$this->options->outputType = $type;
$this->options->cachefile = $this::cachefile.$type;
$this->setOutputInterface();
$this->outputInterface->dump();
$this->options->cachefile = null;
$this->options->imageBase64 = false;
$this->setOutputInterface();
$img = $this->outputInterface->dump();
if($type === QRCode::OUTPUT_IMAGE_JPG){ // jpeg encoding may cause different results
$this->markAsRisky();
}
$this->assertSame($img, file_get_contents($this::cachefile.$type));
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
/**
* Class QRMarkupTest
*
* @filesource QRMarkupTest.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\Output\QRMarkup;
use chillerlan\QRCode\QRCode;
class QRMarkupTest extends QROutputTestAbstract{
protected $FQCN = QRMarkup::class;
public function types(){
return [
[QRCode::OUTPUT_MARKUP_HTML],
[QRCode::OUTPUT_MARKUP_SVG],
];
}
/**
* @dataProvider types
* @param $type
*/
public function testMarkupOutputFile($type){
$this->options->outputType = $type;
$this->options->cachefile = $this::cachefile.$type;
$this->setOutputInterface();
$data = $this->outputInterface->dump();
$this->assertSame($data, file_get_contents($this->options->cachefile));
}
/**
* @dataProvider types
* @param $type
*/
public function testMarkupOutput($type){
$this->options->outputType = $type;
$this->setOutputInterface();
$expected = explode($this->options->eol, file_get_contents($this::cachefile.$type));
// cut off the doctype & head
array_shift($expected);
if($type === QRCode::OUTPUT_MARKUP_HTML){
// cut off the </body> tag
array_pop($expected);
}
$expected = implode($this->options->eol, $expected);
$this->assertSame(trim($expected), trim($this->outputInterface->dump()));
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
/**
* Class QROutputTestAbstract
*
* @filesource QROutputTestAbstract.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\Data\Byte;
use chillerlan\QRCode\Output\QROutputInterface;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCodeTest\QRTestAbstract;
/**
*/
abstract class QROutputTestAbstract extends QRTestAbstract{
const cachefile = __DIR__.'/output_test.';
/**
* @var \chillerlan\QRCode\Output\QROutputInterface
*/
protected $outputInterface;
/**
* @var \chillerlan\QRCode\QROptions
*/
protected $options;
/**
* @var \chillerlan\QRCode\Data\QRMatrix
*/
protected $matrix;
protected function setUp(){
parent::setUp();
$this->options = new QROptions;
$this->setOutputInterface();
}
protected function setOutputInterface(){
$this->outputInterface = $this->reflection->newInstanceArgs([$this->options, (new Byte($this->options, 'testdata'))->initMatrix(0)]);
return $this->outputInterface;
}
public function testInstance(){
$this->assertInstanceOf(QROutputInterface::class, $this->outputInterface);
}
/**
* @expectedException \chillerlan\QRCode\Output\QRCodeOutputException
* @expectedExceptionMessage Could not write data to cache file: /foo
*/
public function testSaveException(){
$this->options->cachefile = '/foo';
$this->setOutputInterface();
$this->outputInterface->dump();
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
/**
* Class QRStringTest
*
* @filesource QRStringTest.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\Output\QRString;
use chillerlan\QRCode\QRCode;
class QRStringTest extends QROutputTestAbstract{
protected $FQCN = QRString::class;
public function types(){
return [
[QRCode::OUTPUT_STRING_JSON],
[QRCode::OUTPUT_STRING_TEXT],
];
}
/**
* @dataProvider types
* @param $type
*/
public function testStringOutput($type){
$this->options->outputType = $type;
$this->options->cachefile = $this::cachefile.$type;
$this->setOutputInterface();
$data = $this->outputInterface->dump();
$this->assertSame($data, file_get_contents($this->options->cachefile));
}
}
+7 -17
View File
@@ -30,21 +30,6 @@ class QRCodeTest extends QRTestAbstract{
$this->qrcode = $this->reflection->newInstance();
}
/*
public function optionsDataProvider(){
return [
[QROptions::class],
# [],
];
}
public function testInstance($options){
$q = $this->reflection->newInstanceArgs([new $options]);
$this->assertInstanceOf($this->FQCN, $q);
# print_r($q->render('test'));
}
*/
public function testIsNumber(){
$this->assertTrue($this->qrcode->isNumber('0123456789'));
$this->assertFalse($this->qrcode->isNumber('ABC'));
@@ -100,7 +85,12 @@ class QRCodeTest extends QRTestAbstract{
$this->qrcode->setOptions(new QROptions(['outputType' => 'foo']))->render('test');
}
/**
* @expectedException \chillerlan\QRCode\Data\QRCodeDataException
* @expectedExceptionMessage QRCode::getMatrix() No data given.
*/
public function testGetMatrixException(){
$this->qrcode->getMatrix('');
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
/**
* Class QRAuthenticatorTest
*
* @filesource QRAuthenticatorTest.php
* @created 24.12.2017
* @package chillerlan\QRCodeTest\Traits
* @author Smiley <smiley@chillerlan.net>
* @copyright 2017 Smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Traits;
use chillerlan\QRCode\Traits\QRAuthenticator;
use chillerlan\QRCodeTest\QRTestAbstract;
class QRAuthenticatorTest extends QRTestAbstract{
use QRAuthenticator;
protected function setUp(){}
public function testGetURI(){
$this->authenticatorSecret = 'SECRETTEST234567';
$expected = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAIAAACx0UUtAAAABnRSTlMA/wD/AP83WBt9AAAFh0lEQVR4nO3dy47rNhBF0XSQ///lm0lGGjAg+NC2e61pW5KTe0C4QFXx58+fP39B2N9vfwH4HzJKnYxSJ6PUySh1MkqdjFIno9TJKHUySp2MUiej1MkodTJKnYxSJ6PUySh1MkqdjFL3z8rFPz8/u77H2KPpauW5U7daee64Uexxq6musvG1U3d+619winWUOhmlTkapk1Hqlmqmh43jJKZKmalbrRQrK8/daKpImvrvvfYvOMU6Sp2MUiej1MkodTtrpoeNuzJTD5oqC6YKjnO3Gpu6dqUoXHnuuQGM1lHqZJQ6GaVORqk7WDNds7E6Gd/5YWN1Mr7zQ+R1u2uso9TJKHUySp2MUvcNNdOUc2+ybdxJ2tje9AWso9TJKHUySp2MUnewZrr24/2thp5z8yPOtXOd6986xzpKnYxSJ6PUySh1O2uma2+FrRRJ1/46dq0XakrzvT7rKHUySp2MUiej1P1E9hKmnGv3WdmjGj/orW/1BW/uWUepk1HqZJQ6GaVuqWba+HLatbLg2qC8jTaeRzW+87kxFs5n4pvJKHUySp2MUrezZrp28uxKofPw1vG4Y28Vo9dGDk6xjlIno9TJKHUySt29uXlTP883jnWYuvP4w2PnTrzdOE6iufk3Zh2lTkapk1HqZJS6yqzxlXlu5ybjXXvNb8rGUmblW117NdE6Sp2MUiej1MkodUs108Yf/g9vzb6bcu70pnPj+869P3mOdZQ6GaVORqmTUeqWaqZzo6mvlUHn3pF7fPjcrI0VzY20B+sodTJKnYxSJ6PU7Zw1fm0E+DnX2ozGt3r4xB6slQc9WEepk1HqZJQ6GaXu4Ny8qWsfrk3ze2tAeKRIeuvaKdZR6mSUOhmlTkap2zkDYmUWw+PajZXNW81PK3MCp3xii9IU6yh1MkqdjFIno9TtnAGxUhZc26Pa2Bu08ayjc3O7z9WI1zbwrKPUySh1MkqdjFK3cwbExo2lsY1HPUWcOydp5bkrQy6cz8QvIqPUySh1MkrdvfOZru0znZv2du1Fvre81fs1Zh2lTkapk1HqZJS6e+czXZv2tnHLKtJ0NXXt1F+nvFVCWUepk1HqZJQ6GaXu4AyIh3On+2ystz7x5Kfxc69NE9fPxO8lo9TJKHUySt3Bd/NW6o+VomHjHLmHje/mrbRzTXnr1Cj9TPwiMkqdjFIno9R9//lMY2/N6zu3VzS2sdnL+UzwHxmlTkapk1HqDs4an9oNethYNJzbdjp357Frdc+5ncIp1lHqZJQ6GaVORqk7uM907XDVt1qjVh608Wuc64VaoZ+JX0RGqZNR6mSUup0108O5bphzfUUfMZj8XEU1vnbs3MuW1lHqZJQ6GaVORqm7188U2f/Y+Nxzw7bP9RWNvbVTOGYdpU5GqZNR6mSUuoNz86Z+cV+rqM6Nd3vrhbqNhc659qYV1lHqZJQ6GaVORqlb2md6y7UX6sbXbmznOjdzb+pBU7cas8/ELyKj1MkodTJK3dI+07VJcY8f4M3Z2xu/xtSHx/9zxne+Np9QPxPfTEapk1HqZJS6ne/mvVVDjK3srEQ2lqZcOzbp2qh16yh1MkqdjFIno9RV+pk2VjZThc74w1ObNBtNVVQb+8aarKPUySh1MkqdjFJ3sGa65tpZq5HBgOemp48f9NYUQesodTJKnYxSJ6PUfWTNtPGNsoe3zuFdaUIaf42VD187JWvMOkqdjFIno9TJKHUHa6a3ji9a0RxqvvKq3rkHTf11hXWUOhmlTkapk1HqdtZMb/XKbNzgWfnwNVMbPG/VeRvDYB2lTkapk1HqZJS6jzyfiV/FOkqdjFIno9TJKHUySp2MUiej1MkodTJKnYxSJ6PUySh1MkqdjFIno9TJKHUySp2MUiej1P0LqTXB8AO7a6AAAAAASUVORK5CYII=';
$this->assertSame($expected, $this->getURIQRCode('testlabel', 'example.com'));
}
}