smiley 8 лет назад
Родитель
Сommit
7ea7b3c85a
55 измененных файлов с 2447 добавлено и 3233 удалено
  1. 3 1
      composer.json
  2. 164 0
      public/index.html
  3. 90 0
      public/qrcode.php
  4. 0 66
      src/Container.php
  5. 20 34
      src/Data/AlphaNum.php
  6. 9 9
      src/Data/Byte.php
  7. 19 12
      src/Data/Kanji.php
  8. 215 0
      src/Data/MaskPatternTester.php
  9. 13 15
      src/Data/Number.php
  10. 290 20
      src/Data/QRDataAbstract.php
  11. 143 21
      src/Data/QRDataInterface.php
  12. 563 0
      src/Data/QRMatrix.php
  13. 9 8
      src/Helpers/BitBuffer.php
  14. 182 0
      src/Helpers/Polynomial.php
  15. 48 66
      src/Output/QRImage.php
  16. 0 101
      src/Output/QRImageOptions.php
  17. 34 52
      src/Output/QRMarkup.php
  18. 0 85
      src/Output/QRMarkupOptions.php
  19. 14 41
      src/Output/QROutputAbstract.php
  20. 0 8
      src/Output/QROutputInterface.php
  21. 0 48
      src/Output/QROutputOptionsAbstract.php
  22. 2 9
      src/Output/QRString.php
  23. 0 45
      src/Output/QRStringOptions.php
  24. 0 158
      src/Polynomial.php
  25. 247 65
      src/QRCode.php
  26. 0 989
      src/QRDataGenerator.php
  27. 221 9
      src/QROptions.php
  28. 13 10
      tests/Data/BitBufferTest.php
  29. 0 120
      tests/Data/DataTest.php
  30. 9 8
      tests/Data/PolynomialTest.php
  31. 49 0
      tests/Data/QRMatrixTest.php
  32. 0 59
      tests/Output/ImageTest.php
  33. 0 87
      tests/Output/MarkupTest.php
  34. 0 56
      tests/Output/OutputTestAbstract.php
  35. 0 46
      tests/Output/StringTest.php
  36. 0 1
      tests/Output/image/img1.gif.uri
  37. 0 0
      tests/Output/image/img1.jpg.uri
  38. 0 1
      tests/Output/image/img1.png.uri
  39. 0 0
      tests/Output/image/img2.gif.uri
  40. 0 0
      tests/Output/image/img2.jpg.uri
  41. 0 1
      tests/Output/image/img2.png.uri
  42. 0 21
      tests/Output/markup/str1.html
  43. 0 124
      tests/Output/markup/str1.svg
  44. 0 21
      tests/Output/markup/str2.html
  45. 0 366
      tests/Output/markup/str2.svg
  46. 0 37
      tests/Output/markup/str3.html
  47. 0 37
      tests/Output/markup/str4.html
  48. 0 0
      tests/Output/string/str1.json
  49. 0 21
      tests/Output/string/str1.txt
  50. 0 0
      tests/Output/string/str2.json
  51. 0 37
      tests/Output/string/str2.txt
  52. 0 49
      tests/Output/string/str3.txt
  53. 16 97
      tests/QRCodeTest.php
  54. 0 172
      tests/QRDataGeneratorTest.php
  55. 74 0
      tests/QRTestAbstract.php

+ 3 - 1
composer.json

@@ -18,7 +18,8 @@
 		}
 	],
 	"require": {
-		"php": ">=7.0.3"
+		"php": ">=7.0.3",
+		"chillerlan/php-traits": "1.0.*"
 	},
 	"require-dev": {
 		"chillerlan/php-googleauth": "1.1.*",
@@ -34,6 +35,7 @@
 	},
 	"autoload-dev": {
 		"psr-4": {
+			"chillerlan\\QRCodePublic\\": "public/",
 			"chillerlan\\QRCodeTest\\": "tests/"
 		}
 	}

+ 164 - 0
public/index.html

@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html lang="en" >
+<head >
+	<meta charset="UTF-8" >
+	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+	<title >QR Code Generator</title >
+	<style >
+		body{ font-size: 20px; line-height: 1.4em; font-family: "Trebuchet MS", sans-serif; color: #000;}
+		input, textarea, select{font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 75%; line-height: 1.25em; border: 1px solid #aaa; }
+		input:focus, textarea:focus, select:focus{ border: 1px solid #ccc; }
+		label{ cursor: pointer; }
+		#qrcode-settings, div#qrcode-output{ text-align: center; }
+		div#qrcode-output > p {margin: 0;padding: 0;height: 3px;}
+		div#qrcode-output > p > b, div#qrcode-output > p > i {display: inline-block;width: 3px;height: 3px;}
+		div#qrcode-output > p > b {background-color: lightseagreen;}
+		div#qrcode-output > p > i {background-color: #fff;}
+	</style >
+</head >
+<body >
+
+<form id="qrcode-settings" >
+
+	<label for="inputstring" >Input String</label ><br /><textarea name="inputstring" id="inputstring" cols="80" rows="3" autocomplete="off" spellcheck="false"></textarea ><br />
+
+	<label for="version" >Version</label >
+	<input id="version" name="version" class="options" type="number" min="1" max="40" value="5" placeholder="version" />
+
+	<label for="maskpattern" >Mask Pattern</label >
+	<input id="maskpattern" name="maskpattern" class="options" type="number" min="-1" max="7" value="-1" placeholder="mask pattern" />
+
+	<label for="ecc" >ECC</label >
+	<select class="options" id="ecc" name="ecc" >
+		<option value="L" selected="selected" >L - 7%</option >
+		<option value="M" >M - 15%</option >
+		<option value="Q" >Q - 25%</option >
+		<option value="H" >H - 30%</option >
+	</select >
+
+	<br />
+
+	<label for="quietzone" >Quiet Zone
+		<input id="quietzone" name="quietzone" class="options" type="checkbox" value="true" />
+	</label >
+
+	<label for="quietzonesize" >size</label >
+	<input id="quietzonesize" name="quietzonesize" class="options" type="number" min="0" max="100" value="4" placeholder="quiet zone" />
+
+	<br />
+
+	<label for="output_type" >Output</label >
+	<select class="options" id="output_type" name="output_type" >
+		<option value="html" selected="selected"  >Markup - HTML</option >
+		<option value="svg" >Markup - SVG</option >
+		<option value="png">Image - png</option >
+		<option value="jpg" >Image - jpg</option >
+		<option value="gif" >Image - gif</option >
+		<option value="text" >String - text</option >
+		<option value="json" >String - json</option >
+	</select >
+
+	<label for="scale" >scale</label >
+	<input id="scale" name="scale" class="options" type="number" min="1" max="10" value="5" placeholder="scale" />
+
+	<div>Finder</div>
+	<label for="m_finder_light" >
+		<input type="text" readonly="readonly" id="m_finder_light" name="m_finder_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_finder_dark" >
+		<input type="text" readonly="readonly" id="m_finder_dark" name="m_finder_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<div>Alignment</div>
+	<label for="m_alignment_light" >
+		<input type="text" readonly="readonly" id="m_alignment_light" name="m_alignment_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_alignment_dark" >
+		<input type="text" readonly="readonly" id="m_alignment_dark" name="m_alignment_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<div>Timing</div>
+	<label for="m_timing_light" >
+		<input type="text" readonly="readonly" id="m_timing_light" name="m_timing_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_timing_dark" >
+		<input type="text" readonly="readonly" id="m_timing_dark" name="m_timing_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<div>Format</div>
+	<label for="m_format_light" >
+		<input type="text" readonly="readonly" id="m_format_light" name="m_format_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_format_dark" >
+		<input type="text" readonly="readonly" id="m_format_dark" name="m_format_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<div>Version</div>
+	<label for="m_version_light" >
+		<input type="text" readonly="readonly" id="m_version_light" name="m_version_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_version_dark" >
+		<input type="text" readonly="readonly" id="m_version_dark" name="m_version_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<div>Data</div>
+	<label for="m_data_light" >
+		<input type="text" readonly="readonly" id="m_data_light" name="m_data_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_data_dark" >
+		<input type="text" readonly="readonly" id="m_data_dark" name="m_data_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<div>Dark Module</div>
+	<label for="m_darkmodule_light" >
+		<input disabled="disabled" type="text" id="m_darkmodule_light" class="options" value="" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_darkmodule_dark" >
+		<input type="text" readonly="readonly" id="m_darkmodule_dark" name="m_darkmodule_dark" class="jscolor options" value="000000" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<div>Separator</div>
+	<label for="m_separator_light" >
+		<input type="text" readonly="readonly" id="m_separator_light" name="m_separator_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_separator_dark" >
+		<input disabled="disabled" type="text" id="m_separator_dark" class="options" value="" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<div>Quiet Zone</div>
+	<label for="m_quietzone_light" >
+		<input type="text" readonly="readonly" id="m_quietzone_light" name="m_quietzone_light" class="jscolor options" value="ffffff" autocomplete="off" spellcheck="false" />
+	</label >
+	<label for="m_quietzone_dark" >
+		<input disabled="disabled" type="text" id="m_quietzone_dark" class="options" value="" autocomplete="off" spellcheck="false" />
+	</label >
+
+	<br />
+	<button type="submit" >generate</button >
+</form >
+<div id="qrcode-output" ></div >
+
+<div><a href="https://play.google.com/store/apps/details?id=com.google.zxing.client.android" >ZXing Barcode Scanner</a ></div>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.3/prototype.js" ></script >
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.0.4/jscolor.js" ></script >
+<script >
+	((form, output, url) => {
+
+		$(form).observe('submit', ev => {
+			Event.stop(ev);
+
+			new Ajax.Request(url, {
+				method: 'post',
+				parameters: ev.target.serialize(true),
+				onUninitialized: $(output).update(),
+				onLoading: $(output).update('[portlandia_screaming.gif]'),
+				onFailure: response => $(output).update(response.responseJSON.error),
+				onSuccess: response => $(output).update(response.responseJSON.qrcode),
+			});
+
+		});
+	})('qrcode-settings', 'qrcode-output', './qrcode.php');
+</script >
+
+</body >
+</html >

+ 90 - 0
public/qrcode.php

@@ -0,0 +1,90 @@
+<?php
+/**
+ * @filesource   qrcode.php
+ * @created      18.11.2017
+ * @author       Smiley <smiley@chillerlan.net>
+ * @copyright    2017 Smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodePublic;
+
+use chillerlan\QRCode\QRCode;
+use chillerlan\QRCode\QROptions;
+
+require_once '../vendor/autoload.php';
+
+try{
+
+	$moduleValues = [
+		// finder
+		1536 => $_POST['m_finder_dark'],
+		6    => $_POST['m_finder_light'],
+		// alignment
+		2560 => $_POST['m_alignment_dark'],
+		10   => $_POST['m_alignment_light'],
+		// timing
+		3072 => $_POST['m_timing_dark'],
+		12   => $_POST['m_timing_light'],
+		// format
+		3584 => $_POST['m_format_dark'],
+		14   => $_POST['m_format_light'],
+		// version
+		4096 => $_POST['m_version_dark'],
+		16   => $_POST['m_version_light'],
+		// data
+		1024 => $_POST['m_data_dark'],
+		4    => $_POST['m_data_light'],
+		// darkmodule
+		512  => $_POST['m_darkmodule_dark'],
+		// separator
+		8    => $_POST['m_separator_light'],
+		// quietzone
+		18   => $_POST['m_quietzone_light'],
+	];
+
+	$moduleValues = array_map(function($v){
+		#var_dump(array_map('hexdec', unpack('a2r/a2g/a2b','ff00ff')));
+
+		if(preg_match('/[a-f\d]{6}/i', $v) === 1){
+			return '#'.$v;
+		}
+
+		return strpos($v, '_dark') !== false
+			? '#111111'
+			: '#eeeeee';
+
+	}, $moduleValues);
+
+
+	$ecc = in_array($_POST['ecc'], ['L', 'M', 'Q', 'H'], true) ? $_POST['ecc'] : 'L';
+
+	$qro = new QROptions;
+
+	$qro->version       = (int)$_POST['version'];
+	$qro->eccLevel      = constant('chillerlan\\QRCode\\QRCode::ECC_'.$ecc);
+	$qro->moduleValues  = $moduleValues;
+	$qro->outputType    = $_POST['output_type'];
+	$qro->maskPattern   = (int)$_POST['maskpattern'];
+	$qro->addQuietzone  = isset($_POST['quietzone']);
+	$qro->quietzoneSize = (int)$_POST['quietzonesize'];
+	$qro->scale         = (int)$_POST['scale'];
+
+	$qrcode = (new QRCode($qro))->render($_POST['inputstring']);
+
+	send_response(['qrcode' => $qrcode]);
+}
+// Pokémon exception handler
+catch(\Exception $e){
+	header('HTTP/1.1 500 Internal Server Error');
+	send_response(['error' => $e->getMessage()]);
+}
+
+/**
+ * @param array $response
+ */
+function send_response(array $response){
+	header('Content-type: application/json;charset=utf-8;');
+	echo json_encode($response);
+	exit;
+}

+ 0 - 66
src/Container.php

@@ -1,66 +0,0 @@
-<?php
-/**
- * Trait Container
- *
- * @filesource   Container.php
- * @created      09.07.2017
- * @package      chillerlan\QRCode
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2017 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCode;
-
-/**
- * a generic container with getter and setter
- * @codeCoverageIgnore
- */
-trait Container{
-
-	/**
-	 * Boa constructor.
-	 *
-	 * @param array $properties
-	 */
-	public function __construct(array $properties = []){
-
-		foreach($properties as $key => $value){
-			$this->__set($key, $value);
-		}
-
-	}
-
-	/**
-	 * David Getter
-	 *
-	 * @param string $property
-	 *
-	 * @return mixed
-	 */
-	public function __get(string $property){
-
-		if(property_exists($this, $property)){
-			return $this->{$property};
-		}
-
-		return false;
-	}
-
-	/**
-	 * Jet-setter
-	 *
-	 * @param string $property
-	 * @param mixed  $value
-	 *
-	 * @return void
-	 */
-	public function __set(string $property, $value){
-
-		if(property_exists($this, $property)){
-			$this->{$property} = $value;
-		}
-
-	}
-
-}

+ 20 - 34
src/Data/AlphaNum.php

@@ -12,29 +12,26 @@
 
 namespace chillerlan\QRCode\Data;
 
-use chillerlan\QRCode\BitBuffer;
+use chillerlan\QRCode\QRCode;
 
 /**
- *
+ * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / :
  */
 class AlphaNum extends QRDataAbstract{
 
 	const CHAR_MAP = [
-		36 => ' ',
-		37 => '$',
-		38 => '%',
-		39 => '*',
-		40 => '+',
-		41 => '-',
-		42 => '.',
-		43 => '/',
-		44 => ':',
+		'0', '1', '2', '3', '4', '5', '6', '7',
+		'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+		'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+		'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+		'W', 'X', 'Y', 'Z', ' ', '$', '%', '*',
+		'+', '-', '.', '/', ':',
 	];
 
 	/**
 	 * @var int
 	 */
-	public $mode = self::MODE_ALPHANUM;
+	protected $datamode = QRCode::DATA_ALPHANUM;
 
 	/**
 	 * @var array
@@ -42,20 +39,16 @@ class AlphaNum extends QRDataAbstract{
 	protected $lengthBits = [9, 11, 13];
 
 	/**
-	 * @param \chillerlan\QRCode\BitBuffer $buffer
-	 *
-	 * @return void
+	 * @inheritdoc
 	 */
-	public function write(BitBuffer &$buffer){
-		$i = 0;
+	protected function write(string $data){
 
-		while($i + 1 < $this->dataLength){
-			$buffer->put($this->getCharCode($this->data[$i]) * 45 + $this->getCharCode($this->data[$i + 1]), 11);
-			$i += 2;
+		for($i = 0; $i + 1 < $this->strlen; $i += 2){
+			$this->bitBuffer->put($this->getCharCode($data[$i]) * 45 + $this->getCharCode($data[$i + 1]), 11);
 		}
 
-		if($i < $this->dataLength){
-			$buffer->put($this->getCharCode($this->data[$i]), 6);
+		if($i < $this->strlen){
+			$this->bitBuffer->put($this->getCharCode($data[$i]), 6);
 		}
 
 	}
@@ -66,21 +59,14 @@ class AlphaNum extends QRDataAbstract{
 	 * @return int
 	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
 	 */
-	private static function getCharCode(string $chr):int {
-		$chr = ord($chr);
+	protected function getCharCode(string $chr):int {
+		$i = array_search($chr, self::CHAR_MAP);
 
-		switch(true){
-			case ord('0') <= $chr && $chr <= ord('9'): return $chr - ord('0');
-			case ord('A') <= $chr && $chr <= ord('Z'): return $chr - ord('A') + 10;
-			default:
-				foreach(self::CHAR_MAP as $i => $c){
-					if(ord($c) === $chr){
-						return $i;
-					}
-				}
+		if($i !== false){
+			return $i;
 		}
 
-		throw new QRCodeDataException('illegal char: '.$chr);
+		throw new QRCodeDataException('illegal char: "'.$chr.'" ['.ord($chr).']');
 	}
 
 }

+ 9 - 9
src/Data/Byte.php

@@ -12,17 +12,17 @@
 
 namespace chillerlan\QRCode\Data;
 
-use chillerlan\QRCode\BitBuffer;
+use chillerlan\QRCode\QRCode;
 
 /**
- *
+ * Byte mode, ISO-8859-1 or UTF-8
  */
 class Byte extends QRDataAbstract{
 
 	/**
 	 * @var int
 	 */
-	public $mode = self::MODE_BYTE;
+	protected $datamode = QRCode::DATA_BYTE;
 
 	/**
 	 * @var array
@@ -30,16 +30,16 @@ class Byte extends QRDataAbstract{
 	protected $lengthBits = [8, 16, 16];
 
 	/**
-	 * @param \chillerlan\QRCode\BitBuffer $buffer
-	 *
-	 * @return void
+	 * @inheritdoc
 	 */
-	public function write(BitBuffer &$buffer){
+	protected function write(string $data){
 		$i = 0;
-		while($i < $this->dataLength){
-			$buffer->put(ord($this->data[$i]), 8);
+
+		while($i < $this->strlen){
+			$this->bitBuffer->put(ord($data[$i]), 8);
 			$i++;
 		}
+
 	}
 
 }

+ 19 - 12
src/Data/Kanji.php

@@ -12,17 +12,17 @@
 
 namespace chillerlan\QRCode\Data;
 
-use chillerlan\QRCode\BitBuffer;
+use chillerlan\QRCode\QRCode;
 
 /**
- *
+ * Kanji mode: double-byte characters from the Shift JIS character set
  */
 class Kanji extends QRDataAbstract{
 
 	/**
 	 * @var int
 	 */
-	public $mode = self::MODE_KANJI;
+	protected $datamode = QRCode::DATA_KANJI;
 
 	/**
 	 * @var array
@@ -30,32 +30,39 @@ class Kanji extends QRDataAbstract{
 	protected $lengthBits = [8, 10, 12];
 
 	/**
-	 * @param \chillerlan\QRCode\BitBuffer $buffer
+	 * @inheritdoc
+	 */
+	protected function getLength(string $data):int{
+		return mb_strlen($data, 'SJIS');
+	}
+
+	/**
+	 * @param string $data
 	 *
 	 * @return void
 	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
 	 */
-	public function write(BitBuffer &$buffer){
+	protected function write(string $data){
+		$len = strlen($data);
 
-		$i = 0;
-		while($i + 1 < $this->dataLength){
-			$c = ((0xff&ord($this->data[$i])) << 8)|(0xff&ord($this->data[$i + 1]));
+		for($i = 0; $i + 1 < $len; $i += 2){
+			$c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1]));
 
 			if(0x8140 <= $c && $c <= 0x9FFC){
 				$c -= 0x8140;
 			}
-			else if(0xE040 <= $c && $c <= 0xEBBF){
+			elseif(0xE040 <= $c && $c <= 0xEBBF){
 				$c -= 0xC140;
 			}
 			else{
 				throw new QRCodeDataException('illegal char at '.($i + 1).' ('.$c.')');
 			}
 
-			$buffer->put((($c >> 8)&0xff) * 0xC0 + ($c&0xff), 13);
-			$i += 2;
+			$this->bitBuffer->put((($c >> 8) & 0xff) * 0xC0 + ($c & 0xff), 13);
+
 		}
 
-		if($i < $this->dataLength){
+		if($i < $len){
 			throw new QRCodeDataException('illegal char at '.($i + 1));
 		}
 

+ 215 - 0
src/Data/MaskPatternTester.php

@@ -0,0 +1,215 @@
+<?php
+/**
+ * Class MaskPatternTester
+ *
+ * @filesource   MaskPatternTester.php
+ * @created      22.11.2017
+ * @package      chillerlan\QRCode\Data
+ * @author       Smiley <smiley@chillerlan.net>
+ * @copyright    2017 Smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCode\Data;
+
+/**
+ * @link http://www.thonky.com/qr-code-tutorial/data-masking
+ */
+class MaskPatternTester{
+
+	/**
+	 * @var \chillerlan\QRCode\Data\QRMatrix
+	 */
+	protected $matrix;
+
+	/**
+	 * @var int
+	 */
+	protected $moduleCount;
+
+	/**
+	 * @param \chillerlan\QRCode\Data\QRMatrix $matrix
+	 */
+	public function setMatrix(QRMatrix $matrix){
+		$this->matrix = $matrix;
+		$this->moduleCount = $this->matrix->size();
+	}
+
+	/**
+	 * Returns the penalty for the given mask pattern
+	 *
+	 * @see \chillerlan\QRCode\QRCode::getBestMaskPattern()
+	 *
+	 * @return float
+	 */
+	public function testPattern():float{
+		$penalty  = 0;
+
+		for($level = 1; $level <= 4; $level++){
+			$penalty += call_user_func([$this, 'testLevel'.$level]);
+		}
+
+		return $penalty;
+	}
+
+	/**
+	 * Checks for each group of five or more same-colored modules in a row (or column)
+	 *
+	 * @return float
+	 */
+	protected function testLevel1():float{
+		$penalty = 0;
+
+		foreach($this->matrix->matrix() as $y => $row){
+			foreach($row as $x => $val){
+				$count = 0;
+
+				for($ry = -1; $ry <= 1; $ry++){
+
+					if($y + $ry < 0 || $this->moduleCount <= $y + $ry){
+						continue;
+					}
+
+					for($rx = -1; $rx <= 1; $rx++){
+
+						if(($ry === 0 && $rx === 0) || ($x + $rx < 0 || $this->moduleCount <= $x + $rx)){
+							continue;
+						}
+
+						if($this->matrix->check($x + $rx, $y + $ry) === ($val >> 8 > 0)){
+							$count++;
+						}
+
+					}
+				}
+
+				if($count > 5){
+					$penalty += (3 + $count - 5);
+				}
+
+			}
+		}
+
+		return $penalty;
+	}
+
+	/**
+	 * Checks for each 2x2 area of same-colored modules in the matrix
+	 *
+	 * @return float
+	 */
+	protected function testLevel2():float{
+		$penalty = 0;
+
+		foreach($this->matrix->matrix() as $y => $row){
+
+			if($y > $this->moduleCount - 2){
+				break;
+			}
+
+			foreach($row as $x => $val){
+
+				if($x > $this->moduleCount - 2){
+					break;
+				}
+
+				$count = 0;
+
+				if($val >> 8 > 0){
+					$count++;
+				}
+
+				if($this->matrix->check($y, $x + 1)){
+					$count++;
+				}
+
+				if($this->matrix->check($y + 1, $x)){
+					$count++;
+				}
+
+				if($this->matrix->check($y + 1, $x + 1)){
+					$count++;
+				}
+
+				if($count === 0 || $count === 4){
+					$penalty += 3;
+				}
+
+			}
+		}
+
+		return $penalty;
+	}
+
+	/**
+	 * Checks if there are patterns that look similar to the finder patterns
+	 *
+	 * @return float
+	 */
+	protected function testLevel3():float{
+		$penalty = 0;
+
+		foreach($this->matrix->matrix() as $y => $row){
+			foreach($row as $x => $val){
+
+				if($x > $this->moduleCount - 7){
+					break;
+				}
+
+				if(
+					$val >> 8 > 0
+					&& !$this->matrix->check($x + 1, $y)
+					&&  $this->matrix->check($x + 2, $y)
+					&&  $this->matrix->check($x + 3, $y)
+					&&  $this->matrix->check($x + 4, $y)
+					&& !$this->matrix->check($x + 5, $y)
+					&&  $this->matrix->check($x + 6, $y)
+				){
+					$penalty += 40;
+				}
+
+			}
+		}
+
+		for ($x = 0; $x < $this->moduleCount; $x++) {
+			for ($y = 0; $y < $this->moduleCount - 6; $y++) {
+
+				if(
+					$this->matrix->check($x, $y    )
+					&& !$this->matrix->check($x, $y + 1)
+					&&  $this->matrix->check($x, $y + 2)
+					&&  $this->matrix->check($x, $y + 3)
+					&&  $this->matrix->check($x, $y + 4)
+					&& !$this->matrix->check($x, $y + 5)
+					&&  $this->matrix->check($x, $y + 6)
+				){
+					$penalty += 40;
+				}
+
+			}
+		}
+
+		return $penalty;
+	}
+
+	/**
+	 * Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference
+	 *
+	 * @return float
+	 */
+	protected function testLevel4():float {
+		$count = 0;
+
+		foreach($this->matrix->matrix() as $y => $row){
+			foreach($row as $x => $val){
+				if($val >> 8 > 0){
+					$count++;
+				}
+			}
+		}
+
+		return (abs(100 * $count / $this->moduleCount / $this->moduleCount - 50) / 5) * 10;
+	}
+
+
+}

+ 13 - 15
src/Data/Number.php

@@ -12,17 +12,17 @@
 
 namespace chillerlan\QRCode\Data;
 
-use chillerlan\QRCode\BitBuffer;
+use chillerlan\QRCode\QRCode;
 
 /**
- *
+ * Numeric mode: decimal digits 0 through 9
  */
 class Number extends QRDataAbstract{
 
 	/**
 	 * @var int
 	 */
-	public $mode = self::MODE_NUMBER;
+	protected $datamode = QRCode::DATA_NUMBER;
 
 	/**
 	 * @var array
@@ -30,25 +30,23 @@ class Number extends QRDataAbstract{
 	protected $lengthBits = [10, 12, 14];
 
 	/**
-	 * @param \chillerlan\QRCode\BitBuffer $buffer
-	 *
-	 * @return void
+	 * @inheritdoc
 	 */
-	public function write(BitBuffer &$buffer){
+	protected function write(string $data){
 		$i = 0;
 
-		while($i + 2 < $this->dataLength){
-			$buffer->put($this->parseInt(substr($this->data, $i, 3)), 10);
+		while($i + 2 < $this->strlen){
+			$this->bitBuffer->put($this->parseInt(substr($data, $i, 3)), 10);
 			$i += 3;
 		}
 
-		if($i < $this->dataLength){
+		if($i < $this->strlen){
 
-			if($this->dataLength - $i === 1){
-				$buffer->put($this->parseInt(substr($this->data, $i, $i + 1)), 4);
+			if($this->strlen - $i === 1){
+				$this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 1)), 4);
 			}
-			elseif($this->dataLength - $i === 2){
-				$buffer->put($this->parseInt(substr($this->data, $i, $i + 2)), 7);
+			elseif($this->strlen - $i === 2){
+				$this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 2)), 7);
 			}
 
 		}
@@ -61,7 +59,7 @@ class Number extends QRDataAbstract{
 	 * @return int
 	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
 	 */
-	private static function parseInt(string $string):int {
+	protected function parseInt(string $string):int {
 		$num = 0;
 
 		$len = strlen($string);

+ 290 - 20
src/Data/QRDataAbstract.php

@@ -12,20 +12,29 @@
 
 namespace chillerlan\QRCode\Data;
 
+use chillerlan\QRCode\{
+	QRCode, QRCodeException, QROptions
+};
+use chillerlan\QRCode\Helpers\{
+	BitBuffer, Polynomial
+};
+use chillerlan\Traits\ClassLoader;
+
 /**
  *
  */
 abstract class QRDataAbstract implements QRDataInterface{
+	use ClassLoader;
 
 	/**
-	 * @var string
+	 * @var int
 	 */
-	public $data;
+	protected $strlen;
 
 	/**
 	 * @var int
 	 */
-	public $dataLength;
+	protected $datamode;
 
 	/**
 	 * @var array
@@ -33,37 +42,298 @@ abstract class QRDataAbstract implements QRDataInterface{
 	protected $lengthBits = [0, 0, 0];
 
 	/**
-	 * QRDataAbstract constructor.
+	 * @var int
+	 */
+	protected $version;
+
+	/**
+	 * @var array
+	 */
+	protected $ecdata;
+
+	/**
+	 * @var array
+	 */
+	protected $dcdata;
+
+	/**
+	 * @var array
+	 */
+	protected $matrixdata;
+
+	/**
+	 * @var \chillerlan\QRCode\QROptions
+	 */
+	protected $options;
+
+	/**
+	 * @var \chillerlan\QRCode\Helpers\BitBuffer
+	 */
+	protected $bitBuffer;
+
+	/**
+	 * QRDataInterface constructor.
 	 *
+	 * @param \chillerlan\QRCode\QROptions $options
+	 * @param string|null                  $data
+	 */
+	public function __construct(QROptions $options, string $data = null){
+		$this->options = $options;
+
+		if($data !== null){
+			$this->setData($data);
+		}
+	}
+
+	/**
 	 * @param string $data
+	 *
+	 * @return \chillerlan\QRCode\Data\QRDataInterface
 	 */
-	public function __construct(string $data){
-		$this->data = $data;
-		$this->dataLength = strlen($data);
+	public function setData(string $data):QRDataInterface{
+
+		if($this->datamode === QRCode::DATA_KANJI){
+			$data = mb_convert_encoding($data, 'SJIS', mb_detect_encoding($data));
+		}
+
+		$this->strlen  = $this->getLength($data);
+		$this->version = $this->options->version === QRCode::VERSION_AUTO
+			? $this->getMinimumVersion()
+			: $this->options->version;
+
+		$this->matrixdata = $this
+			->writeBitBuffer($data)
+			->maskECC()
+		;
+
+		return $this;
 	}
 
 	/**
-	 * @see QRCode::createData()
+	 * @param int  $maskPattern
+	 * @param bool $test
 	 *
-	 * @param int $type
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{
+		/** @var \chillerlan\QRCode\Data\QRMatrix $matrix */
+		$matrix = $this->loadClass(QRMatrix::class, null, $this->version, $this->options->eccLevel);
+
+		return $matrix
+			->setFinderPattern()
+			->setSeparators()
+			->setAlignmentPattern()
+			->setTimingPattern()
+			->setVersionNumber($test)
+			->setFormatInfo($maskPattern, $test)
+			->setDarkModule()
+			->mapData($this->matrixdata, $maskPattern)
+		;
+	}
+
+	/**
+	 * @param int $version
 	 *
 	 * @return int
 	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
 	 * @codeCoverageIgnore
 	 */
-	public function getLengthInBits(int $type):int {
-
-		switch(true){
-			case $type >= 1 && $type <= 9:
-				return $this->lengthBits[0]; //  1 -  9
-			case $type <= 26:
-				return $this->lengthBits[1]; // 10 - 26
-			case $type <= 40:
-				return $this->lengthBits[2]; // 27 - 40
-			default:
-				throw new QRCodeDataException('$type: '.$type);
+	protected function getLengthBits(int $version):int {
+
+		 foreach([9, 26, 40] as $key => $breakpoint){
+			 if($version <= $breakpoint){
+				 return $this->lengthBits[$key];
+			 }
+		 }
+
+		throw new QRCodeDataException('invalid version number: '.$version);
+	}
+
+	/**
+	 * returns the byte count of the string
+	 *
+	 * @param string $data
+	 *
+	 * @return int
+	 */
+	protected function getLength(string $data):int{
+		return strlen($data);
+	}
+
+	/**
+	 * @return int
+	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
+	 */
+	protected function getMinimumVersion():int{
+
+		// guess the version number within the given range
+		foreach(range(max(1, $this->options->versionMin), min($this->options->versionMax, 40)) as $version){
+			$maxlength = self::MAX_LENGTH[$version][QRCode::DATA_MODES[$this->datamode]][QRCode::ECC_MODES[$this->options->eccLevel]];
+
+			if($this->strlen <= $maxlength){
+				return $version;
+			}
+		}
+
+		throw new QRCodeDataException('data exceeds '.$maxlength.' characters');
+	}
+
+	/**
+	 * writes the $data bits to $this->bitBuffer
+	 *
+	 * @param string $data
+	 *
+	 * @return void
+	 */
+	abstract protected function write(string $data);
+
+	/**
+	 * @param string $data
+	 *
+	 * @return \chillerlan\QRCode\Data\QRDataAbstract
+	 * @throws \chillerlan\QRCode\QRCodeException
+	 */
+	protected function writeBitBuffer(string $data):QRDataInterface {
+		$this->bitBuffer = new BitBuffer;
+
+		// @todo: fixme, get real length
+		$MAX_BITS = self::MAX_BITS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]];
+
+		$this->bitBuffer
+			->clear()
+			->put($this->datamode, 4)
+			->put($this->strlen, $this->getLengthBits($this->version))
+		;
+
+		$this->write($data);
+
+		if($this->bitBuffer->length > $MAX_BITS){
+			throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.' > '.$MAX_BITS.'bit)');
+		}
+
+		// end code.
+		if($this->bitBuffer->length + 4 <= $MAX_BITS){
+			$this->bitBuffer->put(0, 4);
+		}
+
+		// padding
+		while($this->bitBuffer->length % 8 !== 0){
+			$this->bitBuffer->putBit(false);
 		}
 
+		// padding
+		while(true){
+
+			if($this->bitBuffer->length >= $MAX_BITS){
+				break;
+			}
+
+			$this->bitBuffer->put(0xEC, 8);
+
+			if($this->bitBuffer->length >= $MAX_BITS){
+				break;
+			}
+
+			$this->bitBuffer->put(0x11, 8);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
+	 *
+	 * @return array
+	 */
+	protected function maskECC():array {
+		list($l1, $l2, $b1, $b2) = self::RSBLOCKS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]];
+
+		$rsBlocks       = array_fill(0, $l1, [$b1, $b2]);
+		$rsCount        = $l1 + $l2;
+		$this->ecdata   = array_fill(0, $rsCount, null);
+		$this->dcdata   = $this->ecdata;
+
+		if($l2 > 0){
+			$rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1]));
+		}
+
+		$totalCodeCount = 0;
+		$maxDcCount     = 0;
+		$maxEcCount     = 0;
+		$offset         = 0;
+
+		foreach($rsBlocks as $key => $block){
+			list($rsBlockTotal, $dcCount) = $block;
+
+			$ecCount            = $rsBlockTotal - $dcCount;
+			$maxDcCount         = max($maxDcCount, $dcCount);
+			$maxEcCount         = max($maxEcCount, $ecCount);
+			$this->dcdata[$key] = array_fill(0, $dcCount, null);
+
+			foreach($this->dcdata[$key] as $a => $_z){
+				$this->dcdata[$key][$a] = 0xff & $this->bitBuffer->buffer[$a + $offset];
+			}
+
+			list($num, $add) = $this->poly($key, $ecCount);
+
+			foreach($this->ecdata[$key] as $c => $_z){
+				$modIndex               = $c + $add;
+				$this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0;
+			}
+
+			$offset         += $dcCount;
+			$totalCodeCount += $rsBlockTotal;
+		}
+
+		$data  = array_fill(0, $totalCodeCount, null);
+		$index = 0;
+
+		$mask = function($arr, $count) use (&$data, &$index, $rsCount){
+			for($x = 0; $x < $count; $x++){
+				for($y = 0; $y < $rsCount; $y++){
+					if($x < count($arr[$y])){
+						$data[$index] = $arr[$y][$x];
+						$index++;
+					}
+				}
+			}
+		};
+
+		$mask($this->dcdata, $maxDcCount);
+		$mask($this->ecdata, $maxEcCount);
+
+		return $data;
+	}
+
+	/**
+	 * @param int $key
+	 * @param int $count
+	 *
+	 * @return int[]
+	 */
+	protected function poly(int $key, int $count):array{
+		$rsPoly  = new Polynomial;
+		$modPoly = new Polynomial;
+
+		for($i = 0; $i < $count; $i++){
+			$modPoly->setNum([1, $modPoly->gexp($i)]);
+			$rsPoly->multiply($modPoly->getNum());
+		}
+
+		$rsPolyCount = count($rsPoly->getNum());
+
+		$modPoly
+			->setNum($this->dcdata[$key], $rsPolyCount - 1)
+			->mod($rsPoly->getNum())
+		;
+
+		$this->ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
+		$num                = $modPoly->getNum();
+
+		return [
+			$num,
+			count($num) - count($this->ecdata[$key]),
+		];
 	}
 
 }

+ 143 - 21
src/Data/QRDataInterface.php

@@ -12,39 +12,161 @@
 
 namespace chillerlan\QRCode\Data;
 
-use chillerlan\QRCode\BitBuffer;
-
 /**
- * @property string data
- * @property int    dataLength
- * @property int    mode
+ *
  */
 interface QRDataInterface{
 
-	const MODE_NUMBER   = 1 << 0;
-	const MODE_ALPHANUM = 1 << 1;
-	const MODE_BYTE     = 1 << 2;
-	const MODE_KANJI    = 1 << 3;
+	/**
+	 * @link http://www.qrcode.com/en/about/version.html
+	 */
+	const MAX_LENGTH =[ 1 => // start at 1
+	//  [NUMERIC => [L, M, Q, H ], ALPHANUM => [L, M, Q, H], BINARY => [L, M, Q, H  ], KANJI => [L, M, Q, H   ]]  // modules
+		[[  41,   34,   27,   17], [  25,   20,   16,   10], [  17,   14,   11,    7], [  10,    8,    7,    4]], //  21
+		[[  77,   63,   48,   34], [  47,   38,   29,   20], [  32,   26,   20,   14], [  20,   16,   12,    8]], //  25
+		[[ 127,  101,   77,   58], [  77,   61,   47,   35], [  53,   42,   32,   24], [  32,   26,   20,   15]], //  29
+		[[ 187,  149,  111,   82], [ 114,   90,   67,   50], [  78,   62,   46,   34], [  48,   38,   28,   21]], //  33
+		[[ 255,  202,  144,  106], [ 154,  122,   87,   64], [ 106,   84,   60,   44], [  65,   52,   37,   27]], //  37
+		[[ 322,  255,  178,  139], [ 195,  154,  108,   84], [ 134,  106,   74,   58], [  82,   65,   45,   36]], //  41
+		[[ 370,  293,  207,  154], [ 224,  178,  125,   93], [ 154,  122,   86,   64], [  95,   75,   53,   39]], //  45
+		[[ 461,  365,  259,  202], [ 279,  221,  157,  122], [ 192,  152,  108,   84], [ 118,   93,   66,   52]], //  49
+		[[ 552,  432,  312,  235], [ 335,  262,  189,  143], [ 230,  180,  130,   98], [ 141,  111,   80,   60]], //  53
+		[[ 652,  513,  364,  288], [ 395,  311,  221,  174], [ 271,  213,  151,  119], [ 167,  131,   93,   74]], //  57
+		[[ 772,  604,  427,  331], [ 468,  366,  259,  200], [ 321,  251,  177,  137], [ 198,  155,  109,   85]], //  61
+		[[ 883,  691,  489,  374], [ 535,  419,  296,  227], [ 367,  287,  203,  155], [ 226,  177,  125,   96]], //  65
+		[[1022,  796,  580,  427], [ 619,  483,  352,  259], [ 425,  331,  241,  177], [ 262,  204,  149,  109]], //  69 NICE!
+		[[1101,  871,  621,  468], [ 667,  528,  376,  283], [ 458,  362,  258,  194], [ 282,  223,  159,  120]], //  73
+		[[1250,  991,  703,  530], [ 758,  600,  426,  321], [ 520,  412,  292,  220], [ 320,  254,  180,  136]], //  77
+		[[1408, 1082,  775,  602], [ 854,  656,  470,  365], [ 586,  450,  322,  250], [ 361,  277,  198,  154]], //  81
+		[[1548, 1212,  876,  674], [ 938,  734,  531,  408], [ 644,  504,  364,  280], [ 397,  310,  224,  173]], //  85
+		[[1725, 1346,  948,  746], [1046,  816,  574,  452], [ 718,  560,  394,  310], [ 442,  345,  243,  191]], //  89
+		[[1903, 1500, 1063,  813], [1153,  909,  644,  493], [ 792,  624,  442,  338], [ 488,  384,  272,  208]], //  93
+		[[2061, 1600, 1159,  919], [1249,  970,  702,  557], [ 858,  666,  482,  382], [ 528,  410,  297,  235]], //  97
+		[[2232, 1708, 1224,  969], [1352, 1035,  742,  587], [ 929,  711,  509,  403], [ 572,  438,  314,  248]], // 101
+		[[2409, 1872, 1358, 1056], [1460, 1134,  823,  640], [1003,  779,  565,  439], [ 618,  480,  348,  270]], // 105
+		[[2620, 2059, 1468, 1108], [1588, 1248,  890,  672], [1091,  857,  611,  461], [ 672,  528,  376,  284]], // 109
+		[[2812, 2188, 1588, 1228], [1704, 1326,  963,  744], [1171,  911,  661,  511], [ 721,  561,  407,  315]], // 113
+		[[3057, 2395, 1718, 1286], [1853, 1451, 1041,  779], [1273,  997,  715,  535], [ 784,  614,  440,  330]], // 117
+		[[3283, 2544, 1804, 1425], [1990, 1542, 1094,  864], [1367, 1059,  751,  593], [ 842,  652,  462,  365]], // 121
+		[[3517, 2701, 1933, 1501], [2132, 1637, 1172,  910], [1465, 1125,  805,  625], [ 902,  692,  496,  385]], // 125
+		[[3669, 2857, 2085, 1581], [2223, 1732, 1263,  958], [1528, 1190,  868,  658], [ 940,  732,  534,  405]], // 129
+		[[3909, 3035, 2181, 1677], [2369, 1839, 1322, 1016], [1628, 1264,  908,  698], [1002,  778,  559,  430]], // 133
+		[[4158, 3289, 2358, 1782], [2520, 1994, 1429, 1080], [1732, 1370,  982,  742], [1066,  843,  604,  457]], // 137
+		[[4417, 3486, 2473, 1897], [2677, 2113, 1499, 1150], [1840, 1452, 1030,  790], [1132,  894,  634,  486]], // 141
+		[[4686, 3693, 2670, 2022], [2840, 2238, 1618, 1226], [1952, 1538, 1112,  842], [1201,  947,  684,  518]], // 145
+		[[4965, 3909, 2805, 2157], [3009, 2369, 1700, 1307], [2068, 1628, 1168,  898], [1273, 1002,  719,  553]], // 149
+		[[5253, 4134, 2949, 2301], [3183, 2506, 1787, 1394], [2188, 1722, 1228,  958], [1347, 1060,  756,  590]], // 153
+		[[5529, 4343, 3081, 2361], [3351, 2632, 1867, 1431], [2303, 1809, 1283,  983], [1417, 1113,  790,  605]], // 157
+		[[5836, 4588, 3244, 2524], [3537, 2780, 1966, 1530], [2431, 1911, 1351, 1051], [1496, 1176,  832,  647]], // 161
+		[[6153, 4775, 3417, 2625], [3729, 2894, 2071, 1591], [2563, 1989, 1423, 1093], [1577, 1224,  876,  673]], // 165
+		[[6479, 5039, 3599, 2735], [3927, 3054, 2181, 1658], [2699, 2099, 1499, 1139], [1661, 1292,  923,  701]], // 169
+		[[6743, 5313, 3791, 2927], [4087, 3220, 2298, 1774], [2809, 2213, 1579, 1219], [1729, 1362,  972,  750]], // 173
+		[[7089, 5596, 3993, 3057], [4296, 3391, 2420, 1852], [2953, 2331, 1663, 1273], [1817, 1435, 1024,  784]], // 177
+	];
+
+	const MAX_BITS = [ 1 => // start at 1
+		// MAX_BITS => [L, M, Q, H ]
+		[  152,   128,   104,    72],
+		[  272,   224,   176,   128],
+		[  440,   352,   272,   208],
+		[  640,   512,   384,   288],
+		[  864,   688,   496,   368],
+		[ 1088,   864,   608,   480],
+		[ 1248,   992,   704,   528],
+		[ 1552,  1232,   880,   688],
+		[ 1856,  1456,  1056,   800],
+		[ 2192,  1728,  1232,   976],
+		[ 2592,  2032,  1440,  1120],
+		[ 2960,  2320,  1648,  1264],
+		[ 3424,  2672,  1952,  1440],
+		[ 3688,  2920,  2088,  1576],
+		[ 4184,  3320,  2360,  1784],
+		[ 4712,  3624,  2600,  2024],
+		[ 5176,  4056,  2936,  2264],
+		[ 5768,  4504,  3176,  2504],
+		[ 6360,  5016,  3560,  2728],
+		[ 6888,  5352,  3880,  3080],
+		[ 7456,  5712,  4096,  3248],
+		[ 8048,  6256,  4544,  3536],
+		[ 8752,  6880,  4912,  3712],
+		[ 9392,  7312,  5312,  4112],
+		[10208,  8000,  5744,  4304],
+		[10960,  8496,  6032,  4768],
+		[11744,  9024,  6464,  5024],
+		[12248,  9544,  6968,  5288],
+		[13048, 10136,  7288,  5608],
+		[13880, 10984,  7880,  5960],
+		[14744, 11640,  8264,  6344],
+		[15640, 12328,  8920,  6760],
+		[16568, 13048,  7208,  9368],
+		[17528, 13800,  9848,  7688],
+		[18448, 14496, 10288,  7888],
+		[19472, 15312, 10832,  8432],
+		[20528, 15936, 11408,  8768],
+		[21616, 16816, 12016,  9136],
+		[22496, 17728, 12656,  9776],
+		[23648, 18672, 13328, 10208],
+	];
 
-	const MODE = [
-		self::MODE_NUMBER   => 0,
-		self::MODE_ALPHANUM => 1,
-		self::MODE_BYTE     => 2,
-		self::MODE_KANJI    => 3,
+	/**
+	 * @link http://www.thonky.com/qr-code-tutorial/error-correction-table
+	 */
+	const RSBLOCKS = [ 1 => // start at 1
+		[[ 1,  0,  26,  19], [ 1,  0, 26, 16], [ 1,  0, 26, 13], [ 1,  0, 26,  9]], //  1
+		[[ 1,  0,  44,  34], [ 1,  0, 44, 28], [ 1,  0, 44, 22], [ 1,  0, 44, 16]], //
+		[[ 1,  0,  70,  55], [ 1,  0, 70, 44], [ 2,  0, 35, 17], [ 2,  0, 35, 13]], //
+		[[ 1,  0, 100,  80], [ 2,  0, 50, 32], [ 2,  0, 50, 24], [ 4,  0, 25,  9]], //
+		[[ 1,  0, 134, 108], [ 2,  0, 67, 43], [ 2,  2, 33, 15], [ 2,  2, 33, 11]], //  5
+		[[ 2,  0,  86,  68], [ 4,  0, 43, 27], [ 4,  0, 43, 19], [ 4,  0, 43, 15]], //
+		[[ 2,  0,  98,  78], [ 4,  0, 49, 31], [ 2,  4, 32, 14], [ 4,  1, 39, 13]], //
+		[[ 2,  0, 121,  97], [ 2,  2, 60, 38], [ 4,  2, 40, 18], [ 4,  2, 40, 14]], //
+		[[ 2,  0, 146, 116], [ 3,  2, 58, 36], [ 4,  4, 36, 16], [ 4,  4, 36, 12]], //
+		[[ 2,  2,  86,  68], [ 4,  1, 69, 43], [ 6,  2, 43, 19], [ 6,  2, 43, 15]], // 10
+		[[ 4,  0, 101,  81], [ 1,  4, 80, 50], [ 4,  4, 50, 22], [ 3,  8, 36, 12]], //
+		[[ 2,  2, 116,  92], [ 6,  2, 58, 36], [ 4,  6, 46, 20], [ 7,  4, 42, 14]], //
+		[[ 4,  0, 133, 107], [ 8,  1, 59, 37], [ 8,  4, 44, 20], [12,  4, 33, 11]], //
+		[[ 3,  1, 145, 115], [ 4,  5, 64, 40], [11,  5, 36, 16], [11,  5, 36, 12]], //
+		[[ 5,  1, 109,  87], [ 5,  5, 65, 41], [ 5,  7, 54, 24], [11,  7, 36, 12]], // 15
+		[[ 5,  1, 122,  98], [ 7,  3, 73, 45], [15,  2, 43, 19], [ 3, 13, 45, 15]], //
+		[[ 1,  5, 135, 107], [10,  1, 74, 46], [ 1, 15, 50, 22], [ 2, 17, 42, 14]], //
+		[[ 5,  1, 150, 120], [ 9,  4, 69, 43], [17,  1, 50, 22], [ 2, 19, 42, 14]], //
+		[[ 3,  4, 141, 113], [ 3, 11, 70, 44], [17,  4, 47, 21], [ 9, 16, 39, 13]], //
+		[[ 3,  5, 135, 107], [ 3, 13, 67, 41], [15,  5, 54, 24], [15, 10, 43, 15]], // 20
+		[[ 4,  4, 144, 116], [17,  0, 68, 42], [17,  6, 50, 22], [19,  6, 46, 16]], //
+		[[ 2,  7, 139, 111], [17,  0, 74, 46], [ 7, 16, 54, 24], [34,  0, 37, 13]], //
+		[[ 4,  5, 151, 121], [ 4, 14, 75, 47], [11, 14, 54, 24], [16, 14, 45, 15]], //
+		[[ 6,  4, 147, 117], [ 6, 14, 73, 45], [11, 16, 54, 24], [30,  2, 46, 16]], //
+		[[ 8,  4, 132, 106], [ 8, 13, 75, 47], [ 7, 22, 54, 24], [22, 13, 45, 15]], // 25
+		[[10,  2, 142, 114], [19,  4, 74, 46], [28,  6, 50, 22], [33,  4, 46, 16]], //
+		[[ 8,  4, 152, 122], [22,  3, 73, 45], [ 8, 26, 53, 23], [12, 28, 45, 15]], //
+		[[ 3, 10, 147, 117], [ 3, 23, 73, 45], [ 4, 31, 54, 24], [11, 31, 45, 15]], //
+		[[ 7,  7, 146, 116], [21,  7, 73, 45], [ 1, 37, 53, 23], [19, 26, 45, 15]], //
+		[[ 5, 10, 145, 115], [19, 10, 75, 47], [15, 25, 54, 24], [23, 25, 45, 15]], // 30
+		[[13,  3, 145, 115], [ 2, 29, 74, 46], [42,  1, 54, 24], [23, 28, 45, 15]], //
+		[[17,  0, 145, 115], [10, 23, 74, 46], [10, 35, 54, 24], [19, 35, 45, 15]], //
+		[[17,  1, 145, 115], [14, 21, 74, 46], [29, 19, 54, 24], [11, 46, 45, 15]], //
+		[[13,  6, 145, 115], [14, 23, 74, 46], [44,  7, 54, 24], [59,  1, 46, 16]], //
+		[[12,  7, 151, 121], [12, 26, 75, 47], [39, 14, 54, 24], [22, 41, 45, 15]], // 35
+		[[ 6, 14, 151, 121], [ 6, 34, 75, 47], [46, 10, 54, 24], [ 2, 64, 45, 15]], //
+		[[17,  4, 152, 122], [29, 14, 74, 46], [49, 10, 54, 24], [24, 46, 45, 15]], //
+		[[ 4, 18, 152, 122], [13, 32, 74, 46], [48, 14, 54, 24], [42, 32, 45, 15]], //
+		[[20,  4, 147, 117], [40,  7, 75, 47], [43, 22, 54, 24], [10, 67, 45, 15]], //
+		[[19,  6, 148, 118], [18, 31, 75, 47], [34, 34, 54, 24], [20, 61, 45, 15]], // 40
 	];
 
 	/**
-	 * @param \chillerlan\QRCode\BitBuffer $buffer
-	 * @return void
+	 * @param string $data
+	 *
+	 * @return \chillerlan\QRCode\Data\QRDataInterface
 	 */
-	public function write(BitBuffer &$buffer);
+	public function setData(string $data);
 
 	/**
-	 * @param $type
+	 * @param int  $maskPattern
+	 * @param bool $test
 	 *
-	 * @return int
-	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
+	 * @return \chillerlan\QRCode\Data\QRMatrix
 	 */
-	public function getLengthInBits(int $type):int;
+	public function initMatrix(int $maskPattern, bool $test = null):QRMatrix;
 
 }

+ 563 - 0
src/Data/QRMatrix.php

@@ -0,0 +1,563 @@
+<?php
+/**
+ * Class QRMatrix
+ *
+ * @filesource   QRMatrix.php
+ * @created      15.11.2017
+ * @package      chillerlan\QRCode\Data
+ * @author       Smiley <smiley@chillerlan.net>
+ * @copyright    2017 Smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCode\Data;
+
+use chillerlan\QRCode\QRCode;
+
+/**
+ * @link http://www.thonky.com/qr-code-tutorial/format-version-information
+ */
+class QRMatrix{
+
+	const M_NULL       = 0x00;
+	const M_DARKMODULE = 0x02;
+	const M_DATA       = 0x04;
+	const M_FINDER     = 0x06;
+	const M_SEPARATOR  = 0x08;
+	const M_ALIGNMENT  = 0x0a;
+	const M_TIMING     = 0x0c;
+	const M_FORMAT     = 0x0e;
+	const M_VERSION    = 0x10;
+	const M_QUIETZONE  = 0x12;
+	const M_LOGO       = 0x14; // @todo
+
+	const M_TEST       = 0xff;
+
+	/**
+	 * @link http://www.thonky.com/qr-code-tutorial/alignment-pattern-locations
+	 */
+	const alignmentPattern = [ 1 => // start at 1
+		[],
+		[6, 18],
+		[6, 22],
+		[6, 26],
+		[6, 30],
+		[6, 34],
+		[6, 22, 38],
+		[6, 24, 42],
+		[6, 26, 46],
+		[6, 28, 50],
+		[6, 30, 54],
+		[6, 32, 58],
+		[6, 34, 62],
+		[6, 26, 46, 66],
+		[6, 26, 48, 70],
+		[6, 26, 50, 74],
+		[6, 30, 54, 78],
+		[6, 30, 56, 82],
+		[6, 30, 58, 86],
+		[6, 34, 62, 90],
+		[6, 28, 50, 72,  94],
+		[6, 26, 50, 74,  98],
+		[6, 30, 54, 78, 102],
+		[6, 28, 54, 80, 106],
+		[6, 32, 58, 84, 110],
+		[6, 30, 58, 86, 114],
+		[6, 34, 62, 90, 118],
+		[6, 26, 50, 74,  98, 122],
+		[6, 30, 54, 78, 102, 126],
+		[6, 26, 52, 78, 104, 130],
+		[6, 30, 56, 82, 108, 134],
+		[6, 34, 60, 86, 112, 138],
+		[6, 30, 58, 86, 114, 142],
+		[6, 34, 62, 90, 118, 146],
+		[6, 30, 54, 78, 102, 126, 150],
+		[6, 24, 50, 76, 102, 128, 154],
+		[6, 28, 54, 80, 106, 132, 158],
+		[6, 32, 58, 84, 110, 136, 162],
+		[6, 26, 54, 82, 110, 138, 166],
+		[6, 30, 58, 86, 114, 142, 170],
+	];
+
+	/**
+	 * @link http://www.thonky.com/qr-code-tutorial/format-version-tables
+	 */
+	const versionPattern = [ 7  => // no version pattern for QR Codes < 7
+		0x07c94, 0x085bc, 0x09a99, 0x0a4d3, // 7-10
+		0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6,
+		0x15683, 0x168c9, 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
+		0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, 0x27541, 0x28c69,
+	];
+
+	const formatPattern = [
+		[0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976], // L
+		[0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0], // M
+		[0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed], // Q
+		[0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b], // H
+	];
+
+	/**
+	 * @var int
+	 */
+	protected $version;
+
+	/**
+	 * @var int
+	 */
+	protected $eclevel;
+
+	/**
+	 * @var int
+	 */
+	protected $maskPattern = -1;
+
+	/**
+	 * @var int
+	 */
+	protected $moduleCount;
+
+	/**
+	 * @var mixed[]
+	 */
+	protected $matrix;
+
+	/**
+	 * QRMatrix constructor.
+	 *
+	 * @param int $version
+	 * @param int $eclevel
+	 *
+	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
+	 */
+	public function __construct(int $version, int $eclevel){
+
+		if(!in_array($version, range(1, 40), true)){
+			throw new QRCodeDataException('invalid QR Code version');
+		}
+
+		if(!array_key_exists($eclevel, QRCode::ECC_MODES)){
+			throw new QRCodeDataException('invalid ecc level');
+		}
+
+		$this->version     = $version;
+		$this->eclevel     = $eclevel;
+		$this->moduleCount = $this->version * 4 + 17;
+		$this->matrix      = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, self::M_NULL));
+	}
+
+	/**
+	 * Returns the data array
+	 *
+	 * @return array
+	 */
+	public function matrix():array {
+		return $this->matrix;
+	}
+
+	/**
+	 * @return int
+	 */
+	public function version():int {
+		return $this->version;
+	}
+
+	/**
+	 * @return int
+	 */
+	public function eccLevel():int {
+		return $this->eclevel;
+	}
+
+	/**
+	 * @return int
+	 */
+	public function maskPattern():int {
+		return $this->maskPattern;
+	}
+
+	/**
+	 * Returns the absoulute size of the matrix, including quiet zone (after setting it).
+	 *
+	 * size = version * 4 + 17 [ + 2 * quietzone size]
+	 *
+	 * @return int
+	 */
+	public function size():int{
+		return $this->moduleCount;
+	}
+
+	/**
+	 * Returns the value of the module at position [$x, $y]
+	 *
+	 * @param int $x
+	 * @param int $y
+	 *
+	 * @return int
+	 */
+	public function get(int $x, int $y):int{
+		return $this->matrix[$y][$x];
+	}
+
+	/**
+	 * Sets the $M_TYPE value for the module at position [$x, $y]
+	 *
+	 *   true  => $M_TYPE << 8
+	 *   false => $M_TYPE
+	 *
+	 * @param int  $x
+	 * @param int  $y
+	 * @param int  $M_TYPE
+	 * @param bool $value
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{
+		$this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0);
+
+		return $this;
+	}
+
+	/**
+	 * Checks whether a module is true (dark) or false (light)
+	 *
+	 *   true  => $value >> 8 === $M_TYPE
+	 *            $value >> 8 > 0
+	 *
+	 *   false => $value === $M_TYPE
+	 *            $value >> 8 === 0
+	 *
+	 * @param int $x
+	 * @param int $y
+	 *
+	 * @return bool
+	 */
+	public function check(int $x, int $y):bool{
+		return $this->matrix[$y][$x] >> 8 > 0;
+	}
+
+
+	/**
+	 * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function setDarkModule():QRMatrix{
+		$this->set(8, 4 * $this->version + 9, true, self::M_DARKMODULE);
+
+		return $this;
+	}
+
+	/**
+	 * Draws the 7x7 finder patterns in the corners top left/right and bottom left
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function setFinderPattern():QRMatrix{
+
+		$pos = [
+			[0, 0], // top left
+			[$this->moduleCount - 7, 0], // bottom left
+			[0, $this->moduleCount - 7], // top right
+		];
+
+		foreach($pos as $c){
+			for($y = 0; $y < 7; $y++){
+				for($x = 0; $x < 7; $x++){
+					$this->set(
+						$c[0] + $y,
+						$c[1] + $x,
+						!(($x > 0 && $x < 6 && ($y === 1 || $y === 5)) || ($y > 0 && $y < 6 && ($x === 1 || $x === 5))),
+						self::M_FINDER
+					);
+				}
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Draws the separator lines around the finder patterns
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function setSeparators():QRMatrix{
+
+		$h = [
+			[7, 0],
+			[$this->moduleCount - 8, 0],
+			[7, $this->moduleCount - 8],
+		];
+
+		$v = [
+			[7, 7],
+			[$this->moduleCount - 1, 7],
+			[7, $this->moduleCount - 8],
+		];
+
+		$t = self::M_SEPARATOR;
+
+		for($c = 0; $c < 3; $c++){
+			for($i = 0; $i < 8; $i++){
+				$this->set($h[$c][0]     , $h[$c][1] + $i, false, $t);
+				$this->set($v[$c][0] - $i, $v[$c][1]     , false, $t);
+			}
+		}
+
+		return $this;
+	}
+
+
+	/**
+	 * Draws the 5x5 alignment patterns
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function setAlignmentPattern():QRMatrix{
+		$pattern = self::alignmentPattern[$this->version];
+
+		foreach($pattern as $y){
+			foreach($pattern as $x){
+
+				// skip existing patterns
+				if($this->matrix[$y][$x] !== self::M_NULL){
+					continue;
+				}
+
+				for($ry = -2; $ry <= 2; $ry++){
+					for($rx = -2; $rx <= 2; $rx++){
+						$v = ($ry === 0 && $rx === 0) || $ry === 2 || $ry === -2 || $rx === 2 || $rx === -2;
+
+						$this->set($x + $rx, $y + $ry, $v, self::M_ALIGNMENT);
+					}
+				}
+
+			}
+		}
+
+		return $this;
+	}
+
+
+	/**
+	 * Draws the timing pattern (h/v checkered line between the finder patterns)
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function setTimingPattern():QRMatrix{
+
+		foreach(range(8, $this->moduleCount - 8 - 1) as $i){
+
+			if($this->matrix[6][$i] !== self::M_NULL || $this->matrix[$i][6] !== self::M_NULL){
+				continue;
+			}
+
+			$v = $i % 2 === 0;
+			$t = self::M_TIMING;
+
+			$this->set($i, 6, $v, $t); // h
+			$this->set(6, $i, $v, $t); // v
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Draws the version information, 2x 3x6 pixel
+	 *
+	 * @param bool|null  $test
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function setVersionNumber(bool $test = null):QRMatrix{
+		$test = $test !== null ? $test : false;
+
+		$bits = self::versionPattern[$this->version] ?? false;
+
+		if($bits !== false){
+
+			for($i = 0; $i < 18; $i++){
+				$a = (int)floor($i / 3);
+				$b = $i % 3 + $this->moduleCount - 8 - 3;
+				$v = !$test && (($bits >> $i) & 1) === 1;
+				$t = self::M_VERSION;
+
+				$this->set($b, $a, $v, $t); // ne
+				$this->set($a, $b, $v, $t); // sw
+			}
+
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Draws the format info along the finder patterns
+	 *
+	 * @param int        $maskPattern
+	 * @param bool|null  $test
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{
+		$test = $test !== null ? $test : false;
+		$bits = self::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0;
+		$t    = self::M_FORMAT;
+
+		for($i = 0; $i < 15; $i++){
+			$v = !$test && (($bits >> $i) & 1) === 1;
+
+			if($i < 6){
+				$this->set(8, $i, $v, $t);
+			}
+			elseif($i < 8){
+				$this->set(8, $i + 1, $v, $t);
+			}
+			else{
+				$this->set(8, $this->moduleCount - 15 + $i, $v, $t);
+			}
+
+			if($i < 8){
+				$this->set($this->moduleCount - $i - 1, 8, $v, $t);
+			}
+			elseif($i < 9){
+				$this->set(15 - $i, 8, $v, $t);
+			}
+			else{
+				$this->set(15 - $i - 1, 8, $v, $t);
+			}
+
+		}
+
+		$this->set(8, $this->moduleCount - 8, !$test, $t);
+
+		return $this;
+	}
+
+	/**
+	 * Draws the "quiet zone" of $size around the matrix
+	 *
+	 * @param int|null $size
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function setQuietZone(int $size = null):QRMatrix{
+
+		if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === self::M_NULL){
+			throw new QRCodeDataException('use only after writing data');
+		}
+
+		$size = $size !== null ? max(0, min($size, floor($this->moduleCount / 2))) : 4;
+		$t    = self::M_QUIETZONE;
+
+		for($y = 0; $y < $this->moduleCount; $y++){
+			for($i = 0; $i < $size; $i++){
+				array_unshift($this->matrix[$y], $t);
+				array_push($this->matrix[$y], $t);
+			}
+		}
+
+		$this->moduleCount += ($size * 2);
+		$r                 = array_fill(0, $this->moduleCount, $t);
+
+		for($i = 0; $i < $size; $i++){
+			array_unshift($this->matrix, $r);
+			array_push($this->matrix, $r);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Maps the binary $data array from QRDataInterface::maskECC() on the matrix, using $maskPattern
+	 *
+	 * @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC()
+	 *
+	 * @param int[] $data
+	 * @param int   $maskPattern
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 */
+	public function mapData(array $data, int $maskPattern):QRMatrix{
+		$this->maskPattern = $maskPattern;
+		$byteCount = count($data);
+		$size      = $this->moduleCount - 1;
+
+		for($i = $size, $y = $size, $inc = -1, $byteIndex = 0, $bitIndex  = 7; $i > 0; $i -= 2){
+
+			if($i === 6){
+				$i--;
+			}
+
+			while(true){
+				for($c = 0; $c < 2; $c++){
+					$x = $i - $c;
+
+					if($this->matrix[$y][$x] === self::M_NULL){
+						$v = false;
+
+						if($byteIndex < $byteCount){
+							$v = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
+						}
+
+						if($this->getMask($x, $y, $maskPattern) === 0){
+							$v = !$v;
+						}
+
+						$this->matrix[$y][$x] = self::M_DATA << ($v ? 8 : 0);
+						$bitIndex--;
+
+						if($bitIndex === -1){
+							$byteIndex++;
+							$bitIndex = 7;
+						}
+
+					}
+				}
+
+				$y += $inc;
+
+				if($y < 0 || $this->moduleCount <= $y){
+					$y   -=  $inc;
+					$inc  = -$inc;
+
+					break;
+				}
+
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * @see \chillerlan\QRCode\QRMatrix::mapData()
+	 *
+	 * @internal
+	 *
+	 * @param int $x
+	 * @param int $y
+	 * @param int $maskPattern
+	 *
+	 * @return int
+	 */
+	protected function getMask(int $x, int $y, int $maskPattern):int {
+		$a = $y + $x;
+		$m = $y * $x;
+
+		// i hate it so much
+		switch($maskPattern){
+			case 0: return $a % 2;
+			case 1: return $y % 2;
+			case 2: return $x % 3;
+			case 3: return $a % 3;
+			case 4: return (floor($y / 2) + floor($x / 3)) % 2;
+			case 5: return $m % 2 + $m % 3;
+			case 6: return ($m % 2 + $m % 3) % 2;
+			case 7: return ($m % 3 + $a % 2) % 2;
+		}
+
+		throw new QRCodeDataException('invalid mask pattern');
+	}
+
+}

+ 9 - 8
src/BitBuffer.php → src/Helpers/BitBuffer.php

@@ -4,21 +4,22 @@
  *
  * @filesource   BitBuffer.php
  * @created      25.11.2015
- * @package      chillerlan\QRCode
+ * @package      chillerlan\QRCode\Data
  * @author       Smiley <smiley@chillerlan.net>
  * @copyright    2015 Smiley
  * @license      MIT
  */
 
-namespace chillerlan\QRCode;
+namespace chillerlan\QRCode\Helpers;
 
 /**
- *
+ * @property int[] $buffer
+ * @property int   $length
  */
 class BitBuffer{
 
 	/**
-	 * @var array
+	 * @var  int[]
 	 */
 	public $buffer = [];
 
@@ -28,7 +29,7 @@ class BitBuffer{
 	public $length = 0;
 
 	/**
-	 * @return \chillerlan\QRCode\BitBuffer
+	 * @return \chillerlan\QRCode\Helpers\BitBuffer
 	 */
 	public function clear():BitBuffer{
 		$this->buffer = [];
@@ -41,7 +42,7 @@ class BitBuffer{
 	 * @param int $num
 	 * @param int $length
 	 *
-	 * @return \chillerlan\QRCode\BitBuffer
+	 * @return \chillerlan\QRCode\Helpers\BitBuffer
 	 */
 	public function put(int $num, int $length):BitBuffer{
 
@@ -55,7 +56,7 @@ class BitBuffer{
 	/**
 	 * @param bool $bit
 	 *
-	 * @return \chillerlan\QRCode\BitBuffer
+	 * @return \chillerlan\QRCode\Helpers\BitBuffer
 	 */
 	public function putBit(bool $bit):BitBuffer{
 		$bufIndex = floor($this->length / 8);
@@ -64,7 +65,7 @@ class BitBuffer{
 			$this->buffer[] = 0;
 		}
 
-		if($bit){
+		if($bit === true){
 			$this->buffer[(int)$bufIndex] |= (0x80 >> ($this->length % 8));
 		}
 

+ 182 - 0
src/Helpers/Polynomial.php

@@ -0,0 +1,182 @@
+<?php
+/**
+ * Class Polynomial
+ *
+ * @filesource   Polynomial.php
+ * @created      25.11.2015
+ * @package      chillerlan\QRCode\Helpers
+ * @author       Smiley <smiley@chillerlan.net>
+ * @copyright    2015 Smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCode\Helpers;
+
+use chillerlan\QRCode\QRCodeException;
+
+/**
+ * @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
+ */
+class Polynomial{
+
+	/**
+	 * @link http://www.thonky.com/qr-code-tutorial/log-antilog-table
+	 */
+	const table = [
+		[  1,   0], [  2,   0], [  4,   1], [  8,  25], [ 16,   2], [ 32,  50], [ 64,  26], [128, 198],
+		[ 29,   3], [ 58, 223], [116,  51], [232, 238], [205,  27], [135, 104], [ 19, 199], [ 38,  75],
+		[ 76,   4], [152, 100], [ 45, 224], [ 90,  14], [180,  52], [117, 141], [234, 239], [201, 129],
+		[143,  28], [  3, 193], [  6, 105], [ 12, 248], [ 24, 200], [ 48,   8], [ 96,  76], [192, 113],
+		[157,   5], [ 39, 138], [ 78, 101], [156,  47], [ 37, 225], [ 74,  36], [148,  15], [ 53,  33],
+		[106,  53], [212, 147], [181, 142], [119, 218], [238, 240], [193,  18], [159, 130], [ 35,  69],
+		[ 70,  29], [140, 181], [  5, 194], [ 10, 125], [ 20, 106], [ 40,  39], [ 80, 249], [160, 185],
+		[ 93, 201], [186, 154], [105,   9], [210, 120], [185,  77], [111, 228], [222, 114], [161, 166],
+		[ 95,   6], [190, 191], [ 97, 139], [194,  98], [153, 102], [ 47, 221], [ 94,  48], [188, 253],
+		[101, 226], [202, 152], [137,  37], [ 15, 179], [ 30,  16], [ 60, 145], [120,  34], [240, 136],
+		[253,  54], [231, 208], [211, 148], [187, 206], [107, 143], [214, 150], [177, 219], [127, 189],
+		[254, 241], [225, 210], [223,  19], [163,  92], [ 91, 131], [182,  56], [113,  70], [226,  64],
+		[217,  30], [175,  66], [ 67, 182], [134, 163], [ 17, 195], [ 34,  72], [ 68, 126], [136, 110],
+		[ 13, 107], [ 26,  58], [ 52,  40], [104,  84], [208, 250], [189, 133], [103, 186], [206,  61],
+		[129, 202], [ 31,  94], [ 62, 155], [124, 159], [248,  10], [237,  21], [199, 121], [147,  43],
+		[ 59,  78], [118, 212], [236, 229], [197, 172], [151, 115], [ 51, 243], [102, 167], [204,  87],
+		[133,   7], [ 23, 112], [ 46, 192], [ 92, 247], [184, 140], [109, 128], [218,  99], [169,  13],
+		[ 79, 103], [158,  74], [ 33, 222], [ 66, 237], [132,  49], [ 21, 197], [ 42, 254], [ 84,  24],
+		[168, 227], [ 77, 165], [154, 153], [ 41, 119], [ 82,  38], [164, 184], [ 85, 180], [170, 124],
+		[ 73,  17], [146,  68], [ 57, 146], [114, 217], [228,  35], [213,  32], [183, 137], [115,  46],
+		[230,  55], [209,  63], [191, 209], [ 99,  91], [198, 149], [145, 188], [ 63, 207], [126, 205],
+		[252, 144], [229, 135], [215, 151], [179, 178], [123, 220], [246, 252], [241, 190], [255,  97],
+		[227, 242], [219,  86], [171, 211], [ 75, 171], [150,  20], [ 49,  42], [ 98,  93], [196, 158],
+		[149, 132], [ 55,  60], [110,  57], [220,  83], [165,  71], [ 87, 109], [174,  65], [ 65, 162],
+		[130,  31], [ 25,  45], [ 50,  67], [100, 216], [200, 183], [141, 123], [  7, 164], [ 14, 118],
+		[ 28, 196], [ 56,  23], [112,  73], [224, 236], [221, 127], [167,  12], [ 83, 111], [166, 246],
+		[ 81, 108], [162, 161], [ 89,  59], [178,  82], [121,  41], [242, 157], [249,  85], [239, 170],
+		[195, 251], [155,  96], [ 43, 134], [ 86, 177], [172, 187], [ 69, 204], [138,  62], [  9,  90],
+		[ 18, 203], [ 36,  89], [ 72,  95], [144, 176], [ 61, 156], [122, 169], [244, 160], [245,  81],
+		[247,  11], [243, 245], [251,  22], [235, 235], [203, 122], [139, 117], [ 11,  44], [ 22, 215],
+		[ 44,  79], [ 88, 174], [176, 213], [125, 233], [250, 230], [233, 231], [207, 173], [131, 232],
+		[ 27, 116], [ 54, 214], [108, 244], [216, 234], [173, 168], [ 71,  80], [142,  88], [  1, 175],
+	];
+
+	/**
+	 * @var array
+	 */
+	protected $num = [];
+
+	/**
+	 * Polynomial constructor.
+	 *
+	 * @param array $num
+	 * @param int   $shift
+	 */
+	public function __construct(array $num = [1], int $shift = 0){
+		$this->setNum($num, $shift);
+	}
+
+	/**
+	 * @return array
+	 */
+	public function getNum():array {
+		return $this->num;
+	}
+
+	/**
+	 * @param array $num
+	 * @param int   $shift
+	 *
+	 * @return \chillerlan\QRCode\Helpers\Polynomial
+	 */
+	public function setNum(array $num, int $shift = 0):Polynomial {
+		$offset = 0;
+		$numCount = count($num);
+
+		while($offset < $numCount && $num[$offset] === 0){
+			$offset++;
+		}
+
+		$this->num = array_fill(0, $numCount - $offset + $shift, 0);
+
+		for($i = 0; $i < $numCount - $offset; $i++){
+			$this->num[$i] = $num[$i + $offset];
+		}
+
+		return $this;
+	}
+
+	/**
+	 * @param array $e
+	 *
+	 * @return \chillerlan\QRCode\Helpers\Polynomial
+	 */
+	public function multiply(array $e):Polynomial {
+		$n = array_fill(0, count($this->num) + count($e) - 1, 0);
+
+		foreach($this->num as $i => $vi){
+			$vi = $this->glog($vi);
+
+			foreach($e as $j => $vj){
+				$n[$i + $j] ^= $this->gexp($vi + $this->glog($vj));
+			}
+
+		}
+
+		$this->setNum($n);
+
+		return $this;
+	}
+
+	/**
+	 * @param array $e
+	 *
+	 * @return \chillerlan\QRCode\Helpers\Polynomial
+	 */
+	public function mod(array $e):Polynomial{
+		$n = $this->num;
+
+		if(count($n) - count($e) < 0){
+			return $this;
+		}
+
+		$ratio = $this->glog($n[0]) - $this->glog($e[0]);
+
+		foreach($e as $i => $v){
+			$n[$i] ^= $this->gexp($this->glog($v) + $ratio);
+		}
+
+		$this->setNum($n)->mod($e);
+
+		return $this;
+	}
+
+	/**
+	 * @param int $n
+	 *
+	 * @return int
+	 * @throws \chillerlan\QRCode\QRCodeException
+	 */
+	public function glog(int $n):int {
+
+		if($n < 1){
+			throw new QRCodeException('log('.$n.')');
+		}
+
+		return Polynomial::table[$n][1];
+	}
+
+	/**
+	 * @param int $n
+	 *
+	 * @return int
+	 */
+	public function gexp(int $n):int {
+
+		if($n < 0){
+			$n += 255;
+		}
+		elseif($n >= 256){
+			$n -= 255;
+		}
+
+		return Polynomial::table[$n][0];
+	}
+
+}

+ 48 - 66
src/Output/QRImage.php

@@ -20,95 +20,77 @@ use chillerlan\QRCode\QRCode;
 class QRImage extends QROutputAbstract{
 
 	/**
-	 * @var string
-	 */
-	protected $optionsInterface = QRImageOptions::class;
-
-	/**
-	 * @var array
-	 */
-	protected $types = [
-		QRCode::OUTPUT_IMAGE_GIF,
-		QRCode::OUTPUT_IMAGE_JPG,
-		QRCode::OUTPUT_IMAGE_PNG,
-	];
-
-	/**
+	 * @todo
 	 * @return string
 	 */
 	public function dump():string {
-		// clamp input (@todo: determine sane values!)
-		$this->options->pixelSize = max(1, min(25, (int)$this->options->pixelSize));
-		$this->options->marginSize = max(0, min(25, (int)$this->options->marginSize));
-
-		foreach(['fgRed', 'fgGreen', 'fgBlue', 'bgRed', 'bgGreen', 'bgBlue'] as $val){
-			$this->options->{$val} = max(0, min(255, (int)$this->options->{$val}));
-		}
-
-		$length     = $this->pixelCount * $this->options->pixelSize + $this->options->marginSize * 2;
+		$length     = ($this->moduleCount + ($this->options->addQuietzone ? 8 : 0)) * $this->options->scale;
 		$image      = imagecreatetruecolor($length, $length);
-		$foreground = imagecolorallocate($image, $this->options->fgRed, $this->options->fgGreen, $this->options->fgBlue);
-		$background = imagecolorallocate($image, $this->options->bgRed, $this->options->bgGreen, $this->options->bgBlue);
+		$background = imagecolorallocate($image, 255, 255, 255);
 
-		if((bool)$this->options->transparent && $this->options->type !== QRCode::OUTPUT_IMAGE_JPG){
+		if((bool)$this->options->imageTransparent && $this->options->outputType !== QRCode::OUTPUT_IMAGE_JPG){
 			imagecolortransparent($image, $background);
 		}
 
 		imagefilledrectangle($image, 0, 0, $length, $length, $background);
 
-		foreach($this->matrix as $r => $row){
+		foreach($this->matrix->matrix() as $r => $row){
 			foreach($row as $c => $pixel){
-				if($pixel){
-					imagefilledrectangle($image,
-						$this->options->marginSize +  $c      * $this->options->pixelSize,
-						$this->options->marginSize +  $r      * $this->options->pixelSize,
-						$this->options->marginSize + ($c + 1) * $this->options->pixelSize - 1,
-						$this->options->marginSize + ($r + 1) * $this->options->pixelSize - 1,
-						$foreground);
-				}
-			}
-		}
-
-		ob_start();
+				list($red, $green, $blue) = $this->options->moduleValues[$pixel];
 
-		switch($this->options->type){
-			case QRCode::OUTPUT_IMAGE_JPG:
-				// @codeCoverageIgnoreStart
-				imagejpeg(
-					$image,
-					$this->options->cachefile,
-					in_array($this->options->jpegQuality, range(0, 100), true)
-						? $this->options->jpegQuality
-						: 85
-				);
-				break;
-				// @codeCoverageIgnoreEnd
-			case QRCode::OUTPUT_IMAGE_GIF: /** Actually, it's pronounced "DJIFF". *hides* */
-				imagegif(
+				imagefilledrectangle(
 					$image,
-					$this->options->cachefile
-				);
-				break;
-			case QRCode::OUTPUT_IMAGE_PNG:
-			default:
-				imagepng(
-					$image,
-					$this->options->cachefile,
-					in_array($this->options->pngCompression, range(-1, 9), true)
-						? $this->options->pngCompression
-						: -1
+					 $c      * $this->options->scale,
+					 $r      * $this->options->scale,
+					($c + 1) * $this->options->scale - 1,
+					($r + 1) * $this->options->scale - 1,
+					imagecolorallocate($image, $red, $green, $blue)
 				);
+
+			}
 		}
 
+		ob_start();
+
+		$this->{$this->options->outputType ?? 'png'}($image);
 		$imageData = ob_get_contents();
 		imagedestroy($image);
+
 		ob_end_clean();
 
-		if((bool)$this->options->base64){
-			$imageData = 'data:image/'.$this->options->type.';base64,'.base64_encode($imageData);
+		if((bool)$this->options->imageBase64){
+			$imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData);
 		}
 
 		return $imageData;
 	}
 
+	protected function png(&$image){
+		imagepng(
+			$image,
+			$this->options->cachefile,
+			in_array($this->options->pngCompression, range(-1, 9), true)
+				? $this->options->pngCompression
+				: -1
+		);
+
+	}
+
+	/**
+	 * Jiff - like... JitHub!
+	 */
+	protected function gif(&$image){
+		imagegif($image, $this->options->cachefile);
+	}
+
+	protected function jpg(&$image){
+		imagejpeg(
+			$image,
+			$this->options->cachefile,
+			in_array($this->options->jpegQuality, range(0, 100), true)
+				? $this->options->jpegQuality
+				: 85
+		);
+	}
+
 }

+ 0 - 101
src/Output/QRImageOptions.php

@@ -1,101 +0,0 @@
-<?php
-/**
- * Class QRImageOptions
- *
- * @filesource   QRImageOptions.php
- * @created      08.12.2015
- * @package      chillerlan\QRCode\Output
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2015 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCode\Output;
-
-use chillerlan\QRCode\QRCode;
-
-/**
- * @property string $type
- * @property bool $base64
- * @property int  $pixelSize
- * @property int  $marginSize
- * @property bool $transparent
- * @property int  $fgRed
- * @property int  $fgGreen
- * @property int  $fgBlue
- * @property int  $bgRed
- * @property int  $bgGreen
- * @property int  $bgBlue
- * @property int  $pngCompression
- * @property int  $jpegQuality
- */
-class QRImageOptions extends QROutputOptionsAbstract{
-
-	/**
-	 * @var string
-	 */
-	protected $type = QRCode::OUTPUT_IMAGE_PNG;
-
-	/**
-	 * @var bool
-	 */
-	protected $base64 = true;
-
-	/**
-	 * @var int
-	 */
-	protected $pixelSize = 5;
-
-	/**
-	 * @var int
-	 */
-	protected $marginSize = 5;
-
-	/**
-	 * not supported by jpg
-	 *
-	 * @var bool
-	 */
-	protected $transparent = true;
-
-	/**
-	 * @var int
-	 */
-	protected $fgRed   = 0;
-
-	/**
-	 * @var int
-	 */
-	protected $fgGreen = 0;
-
-	/**
-	 * @var int
-	 */
-	protected $fgBlue  = 0;
-
-	/**
-	 * @var int
-	 */
-	protected $bgRed   = 255;
-
-	/**
-	 * @var int
-	 */
-	protected $bgGreen = 255;
-
-	/**
-	 * @var int
-	 */
-	protected $bgBlue  = 255;
-
-	/**
-	 * @var int
-	 */
-	protected $pngCompression = -1;
-
-	/**
-	 * @var int
-	 */
-	protected $jpegQuality = 85;
-
-}

+ 34 - 52
src/Output/QRMarkup.php

@@ -19,24 +19,11 @@ use chillerlan\QRCode\QRCode;
  */
 class QRMarkup extends QROutputAbstract{
 
-	/**
-	 * @var string
-	 */
-	protected $optionsInterface = QRMarkupOptions::class;
-
-	/**
-	 * @var array
-	 */
-	protected $types = [
-		QRCode::OUTPUT_MARKUP_HTML,
-		QRCode::OUTPUT_MARKUP_SVG,
-	];
-
 	/**
 	 * @return string
 	 */
 	public function dump() {
-		switch($this->options->type){
+		switch($this->options->outputType){
 			case QRCode::OUTPUT_MARKUP_SVG : return $this->toSVG();
 			case QRCode::OUTPUT_MARKUP_HTML:
 			default:
@@ -50,22 +37,14 @@ class QRMarkup extends QROutputAbstract{
 	protected function toHTML(){
 		$html = '';
 
-		foreach($this->matrix as $row){
-			// in order to not bloat the output too much, we use the shortest possible valid HTML tags
+		foreach($this->matrix->matrix() as $row){
 			$html .= '<'.$this->options->htmlRowTag.'>';
 
-			foreach($row as $col){
-				$tag = $col
-					? 'b'  // dark
-					: 'i'; // light
-
-				$html .= '<'.$tag.'></'.$tag.'>';
-			}
-
-			if(!(bool)$this->options->htmlOmitEndTag){
-				$html .= '</'.$this->options->htmlRowTag.'>';
+			foreach($row as $pixel){
+				$html .= '<b style="background: '.($this->options->moduleValues[$pixel] ?? 'lightgrey').';"></b>';
 			}
 
+			$html .= (bool)$this->options->htmlOmitEndTag ? '</'.$this->options->htmlRowTag.'>' : '';
 			$html .= $this->options->eol;
 		}
 
@@ -84,41 +63,44 @@ class QRMarkup extends QROutputAbstract{
 	 * @return string|bool
 	 */
 	protected function toSVG(){
-		$length = $this->pixelCount * $this->options->pixelSize + $this->options->marginSize * 2;
-		$class  = $this->options->cssClass ?: hash('crc32', microtime(true));
+		$length = ($this->moduleCount + ($this->options->addQuietzone ? 8 : 0)) * $this->options->scale;
 
 		// svg header
-		$svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'.$length.'" height="'.$length.'" viewBox="0 0 '.$length.' '.$length.'" style="background-color:'.$this->options->bgColor.'">'.$this->options->eol.
-		       '<defs><style>.'.$class.'{fill:'.$this->options->fgColor.'} rect{shape-rendering:crispEdges}</style></defs>'.$this->options->eol;
+		$svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'.$length.'" height="'.$length.'" viewBox="0 0 '.$length.' '.$length.'">'.$this->options->eol.
+		       '<defs><style>rect{shape-rendering:crispEdges}</style></defs>'.$this->options->eol;
 
-		// svg body
-		foreach($this->matrix as $r => $row){
-			//we'll combine active blocks within a single row as a lightweight compression technique
-			$from  = -1;
-			$count = 0;
+		// @todo: optimize -> see https://github.com/alexeyten/qr-image/blob/master/lib/vector.js
+		foreach($this->options->moduleValues as $key => $value){
 
-			foreach($row as $c => $pixel){
-				if($pixel){
-					$count++;
+			// svg body
+			foreach($this->matrix->matrix() as $y => $row){
+				//we'll combine active blocks within a single row as a lightweight compression technique
+				$from  = -1;
+				$count = 0;
 
-					if($from < 0){
-						$from = $c;
+				foreach($row as $x => $pixel){
+					if($pixel === $key){
+						$count++;
+
+						if($from < 0){
+							$from = $x;
+						}
 					}
-				}
-				elseif($from >= 0){
-					$svg .= '<rect x="'.($from * $this->options->pixelSize + $this->options->marginSize).'" y="'.($r * $this->options->pixelSize + $this->options->marginSize)
-					        .'" width="'.($this->options->pixelSize * $count).'" height="'.$this->options->pixelSize.'" class="'.$class.'" />'.$this->options->eol;
+					elseif($from >= 0){
+						$svg .= '<rect x="'.($from * $this->options->scale).'" y="'.($y * $this->options->scale)
+						        .'" width="'.($this->options->scale * $count).'" height="'.$this->options->scale.'" class="'.$this->options->cssClass.'" fill="'.$value.'"/>'.$this->options->eol;
 
-					// reset count
-					$from  = -1;
-					$count = 0;
+						// reset count
+						$from  = -1;
+						$count = 0;
+					}
 				}
-			}
 
-			// close off the row, if applicable
-			if($from >= 0){
-				$svg .= '<rect x="'.($from * $this->options->pixelSize + $this->options->marginSize).'" y="'.($r * $this->options->pixelSize + $this->options->marginSize)
-				        .'" width="'.($this->options->pixelSize * $count).'" height="'.$this->options->pixelSize.'" class="'.$class.'" />'.$this->options->eol;
+				// close off the row, if applicable
+				if($from >= 0){
+					$svg .= '<rect x="'.($from * $this->options->scale).'" y="'.($y * $this->options->scale)
+					        .'" width="'.($this->options->scale * $count).'" height="'.$this->options->scale.'" class="'.$this->options->cssClass.'" fill="'.$value.'" />'.$this->options->eol;
+				}
 			}
 		}
 

+ 0 - 85
src/Output/QRMarkupOptions.php

@@ -1,85 +0,0 @@
-<?php
-/**
- * Class QRMarkupOptions
- *
- * @filesource   QRMarkupOptions.php
- * @created      08.12.2015
- * @package      chillerlan\QRCode\Output
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2015 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCode\Output;
-
-use chillerlan\QRCode\QRCode;
-
-/**
- * @property string $type
- * @property string $htmlRowTag
- * @property bool   $htmlOmitEndTag
- * @property string $fgColor
- * @property string $bgColor
- * @property int    $pixelSize
- * @property int    $marginSize
- * @property string $cssClass
- */
-class QRMarkupOptions extends QROutputOptionsAbstract{
-
-	/**
-	 * QRCode::OUTPUT_MARKUP_XXXX where XXXX = HTML, SVG
-	 *
-	 * @var string
-	 */
-	protected $type = QRCode::OUTPUT_MARKUP_SVG;
-
-	/**
-	 * the shortest available semanically correct row (block) tag to not bloat the output
-	 *
-	 * @var string
-	 */
-	protected $htmlRowTag = 'p';
-
-	/**
-	 * the closing tag may be omitted (moar bloat!)
-	 *
-	 * @var bool
-	 */
-	protected $htmlOmitEndTag = true;
-
-	/**
-	 * foreground color
-	 *
-	 * @var string
-	 */
-	protected $fgColor = '#000';
-
-	/**
-	 * background color
-	 *
-	 * @var string
-	 */
-	protected $bgColor = '#fff';
-
-	/**
-	 * size of a QR code pixel
-	 *
-	 * @var int
-	 */
-	protected $pixelSize = 5;
-
-	/**
-	 * margin around the QR code
-	 *
-	 * @var int
-	 */
-	protected $marginSize = 5;
-
-	/**
-	 * a common css class
-	 *
-	 * @var string
-	 */
-	protected $cssClass;
-
-}

+ 14 - 41
src/Output/QROutputAbstract.php

@@ -12,30 +12,24 @@
 
 namespace chillerlan\QRCode\Output;
 
+use chillerlan\QRCode\{
+	Data\QRMatrix, QROptions
+};
+
 /**
  *
  */
 abstract class QROutputAbstract implements QROutputInterface{
 
 	/**
-	 * @var string
-	 */
-	protected $optionsInterface;
-
-	/**
-	 * @var array
-	 */
-	protected $types;
-
-	/**
-	 * @var array
+	 * @param \chillerlan\QRCode\Data\QRMatrix $matrix
 	 */
 	protected $matrix;
 
 	/**
 	 * @var int
 	 */
-	protected $pixelCount;
+	protected $moduleCount;
 
 	/**
 	 * @var object
@@ -43,42 +37,21 @@ abstract class QROutputAbstract implements QROutputInterface{
 	protected $options;
 
 	/**
-	 * @var \chillerlan\QRCode\Output\QROutputOptionsAbstract $outputOptions
-	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
-	 */
-	public function __construct(QROutputOptionsAbstract $outputOptions = null){
-		$this->options = $outputOptions;
-
-		if($this->optionsInterface && !$this->options instanceof $this->optionsInterface){
-			$this->options = new $this->optionsInterface;
-		}
-
-		if(is_array($this->types) && !in_array($this->options->type, $this->types , true)){
-			throw new QRCodeOutputException('Invalid output type!');
-		}
-
-	}
-
-	/**
-	 * @param array $matrix
+	 * @param \chillerlan\QRCode\QROptions      $options
+	 * @param \chillerlan\QRCode\Data\QRMatrix  $matrix
 	 *
-	 * @return \chillerlan\QRCode\Output\QROutputInterface
 	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
 	 */
-	public function setMatrix(array $matrix):QROutputInterface {
-		$this->pixelCount = count($matrix);
+	public function __construct(QROptions $options, QRMatrix $matrix){
+		$this->options = $options;
 
-		// specify valid range?
-		if($this->pixelCount < 2
-			|| !isset($matrix[$this->pixelCount - 1])
-			|| $this->pixelCount !== count($matrix[$this->pixelCount - 1])
-		){
+		$this->moduleCount = $matrix->size();
+
+		if($this->moduleCount < 21){  // minimum QR modules @todo: quet zone
 			throw new QRCodeOutputException('Invalid matrix!');
 		}
 
 		$this->matrix = $matrix;
-
-		return $this;
 	}
 
 	/**
@@ -93,7 +66,7 @@ abstract class QROutputAbstract implements QROutputInterface{
 			return (bool)file_put_contents($this->options->cachefile, $data);
 		}
 		catch(\Exception $e){
-			throw new QRCodeOutputException('Could not write to cache file: '.$e->getMessage());
+			throw new QRCodeOutputException('Could not write data to cache file: '.$e->getMessage());
 		}
 
 	}

+ 0 - 8
src/Output/QROutputInterface.php

@@ -22,12 +22,4 @@ interface QROutputInterface{
 	 */
 	public function dump();
 
-	/**
-	 * @param array $matrix
-	 *
-	 * @return \chillerlan\QRCode\Output\QROutputInterface
-	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
-	 */
-	public function setMatrix(array $matrix):QROutputInterface;
-
 }

+ 0 - 48
src/Output/QROutputOptionsAbstract.php

@@ -1,48 +0,0 @@
-<?php
-/**
- * Class QROutputOptionsAbstract
- *
- * @filesource   QROutputOptionsAbstract.php
- * @created      17.12.2016
- * @package      chillerlan\QRCode\Output
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2016 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCode\Output;
-
-use chillerlan\QRCode\Container;
-
-/**
- * @property string $type
- * @property string $eol
- * @property string $cachefile
- */
-abstract class QROutputOptionsAbstract{
-	use Container;
-
-	/**
-	 * Output type, determined by the used interface
-	 *
-	 * QRCode::OUTPUT_...
-	 *
-	 * @var string
-	 */
-	protected $type;
-
-	/**
-	 * newline string
-	 *
-	 * @var string
-	 */
-	protected $eol = PHP_EOL;
-
-	/**
-	 * /path/to/cache.file
-	 *
-	 * @var string
-	 */
-	protected $cachefile;
-
-}

+ 2 - 9
src/Output/QRString.php

@@ -19,19 +19,12 @@ use chillerlan\QRCode\QRCode;
  */
 class QRString extends QROutputAbstract{
 
-	protected $optionsInterface = QRStringOptions::class;
-
-	protected $types = [
-		QRCode::OUTPUT_STRING_TEXT,
-		QRCode::OUTPUT_STRING_JSON,
-	];
-
 	/**
 	 * @return string
 	 */
 	public function dump():string {
 
-		switch($this->options->type){
+		switch($this->options->outputType){
 			case QRCode::OUTPUT_STRING_TEXT: return $this->toString();
 			case QRCode::OUTPUT_STRING_JSON:
 			default:
@@ -46,7 +39,7 @@ class QRString extends QROutputAbstract{
 	protected function toString():string {
 		$str = '';
 
-		foreach($this->matrix as $row){
+		foreach($this->matrix->matrix() as $row){
 			foreach($row as $col){
 				$str .= $col
 					? $this->options->textDark

+ 0 - 45
src/Output/QRStringOptions.php

@@ -1,45 +0,0 @@
-<?php
-/**
- * Class QRStringOptions
- *
- * @filesource   QRStringOptions.php
- * @created      08.12.2015
- * @package      chillerlan\QRCode\Output
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2015 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCode\Output;
-
-use chillerlan\QRCode\QRCode;
-
-/**
- * @property string $type
- * @property string $textDark
- * @property string $textLight
- */
-class QRStringOptions extends QROutputOptionsAbstract{
-
-	/**
-	 * QRCode::OUTPUT_STRING_XXXX where XXXX = TEXT, JSON
-	 *
-	 * @var string
-	 */
-	protected $type = QRCode::OUTPUT_STRING_JSON;
-
-	/**
-	 * string substitute for dark
-	 *
-	 * @var string
-	 */
-	protected $textDark = '🔴';
-
-	/**
-	 * string substitute for light
-	 *
-	 * @var string
-	 */
-	protected $textLight = '⭕';
-
-}

+ 0 - 158
src/Polynomial.php

@@ -1,158 +0,0 @@
-<?php
-/**
- * Class Polynomial
- *
- * @filesource   Polynomial.php
- * @created      25.11.2015
- * @package      chillerlan\QRCode
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2015 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCode;
-
-/**
- *
- */
-class Polynomial{
-
-	/**
-	 * @var array
-	 */
-	public $num = [];
-
-	/**
-	 * @var array
-	 */
-	protected $expTable = [];
-
-	/**
-	 * @var array
-	 */
-	protected $logTable = [];
-
-	/**
-	 * Polynomial constructor.
-	 *
-	 * @param array $num
-	 * @param int   $shift
-	 */
-	public function __construct(array $num = [1], int $shift = 0){
-		$this->setNum($num, $shift)->setTables();
-	}
-
-	/**
-	 * @param array $num
-	 * @param int   $shift
-	 *
-	 * @return \chillerlan\QRCode\Polynomial
-	 */
-	public function setNum(array $num, int $shift = 0):Polynomial {
-		$offset = 0;
-		$numCount = count($num);
-
-		while($offset < $numCount && $num[$offset] === 0){
-			$offset++;
-		}
-
-		$this->num = array_fill(0, $numCount - $offset + $shift, 0);
-
-		for($i = 0; $i < $numCount - $offset; $i++){
-			$this->num[$i] = $num[$i + $offset];
-		}
-
-		return $this;
-	}
-
-	/**
-	 * @return void
-	 */
-	protected function setTables(){
-		$this->expTable = $this->logTable = array_fill(0, 256, 0);
-
-		for($i = 0; $i < 8; $i++){
-			$this->expTable[$i] = 1 << $i;
-		}
-
-		for($i = 8; $i < 256; $i++){
-			$this->expTable[$i] = $this->expTable[$i - 4]^$this->expTable[$i - 5]^$this->expTable[$i - 6]^$this->expTable[$i - 8];
-		}
-
-		for($i = 0; $i < 255; $i++){
-			$this->logTable[$this->expTable[$i]] = $i;
-		}
-
-	}
-
-	/**
-	 * @param array $e
-	 *
-	 * @return void
-	 */
-	public function multiply(array $e){
-		$n = array_fill(0, count($this->num) + count($e) - 1, 0);
-
-		foreach($this->num as $i => &$vi){
-			foreach($e as $j => &$vj){
-				$n[$i + $j] ^= $this->gexp($this->glog($vi) + $this->glog($vj));
-			}
-		}
-
-		$this->setNum($n);
-	}
-
-	/**
-	 * @param array $e
-	 *
-	 * @return void
-	 */
-	public function mod(array $e){
-		$n = $this->num;
-
-		if(count($n) - count($e) < 0){
-			return;
-		}
-
-		$ratio = $this->glog($n[0]) - $this->glog($e[0]);
-
-		foreach($e as $i => &$v){
-			$n[$i] ^= $this->gexp($this->glog($v) + $ratio);
-		}
-
-		$this->setNum($n)->mod($e);
-	}
-
-	/**
-	 * @param int $n
-	 *
-	 * @return int
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	public function glog(int $n):int {
-
-		if($n < 1){
-			throw new QRCodeException('log('.$n.')');
-		}
-
-		return $this->logTable[$n];
-	}
-
-	/**
-	 * @param int $n
-	 *
-	 * @return int
-	 */
-	public function gexp(int $n):int {
-
-		if($n < 0){
-			$n += 255;
-		}
-		elseif($n >= 256){
-			$n -= 255;
-		}
-
-		return $this->expTable[$n];
-	}
-
-}

+ 247 - 65
src/QRCode.php

@@ -12,120 +12,302 @@
 
 namespace chillerlan\QRCode;
 
-use chillerlan\QRCode\Output\QROutputInterface;
+use chillerlan\QRCode\Data\{
+	AlphaNum, Byte, Kanji, MaskPatternTester, Number, QRCodeDataException, QRDataInterface, QRMatrix
+};
+use chillerlan\QRCode\Output\{
+	QRCodeOutputException, QRImage, QRMarkup, QROutputInterface, QRString
+};
+use chillerlan\Traits\ClassLoader;
 
 /**
  * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
+ * @link http://www.qrcode.com/en/codes/model12.html
  * @link http://www.thonky.com/qr-code-tutorial/
  */
 class QRCode{
+	use ClassLoader;
 
 	/**
 	 * API constants
 	 */
-	const OUTPUT_STRING_TEXT = 'txt';
-	const OUTPUT_STRING_JSON = 'json';
-
-	const OUTPUT_MARKUP_HTML = 'html';
-	const OUTPUT_MARKUP_SVG  = 'svg';
-#	const OUTPUT_MARKUP_XML  = 'xml'; // anyone?
-
-	const OUTPUT_IMAGE_PNG = 'png';
-	const OUTPUT_IMAGE_JPG = 'jpg';
-	const OUTPUT_IMAGE_GIF = 'gif';
-
-	const ERROR_CORRECT_LEVEL_L = 1; // 7%.
-	const ERROR_CORRECT_LEVEL_M = 0; // 15%.
-	const ERROR_CORRECT_LEVEL_Q = 3; // 25%.
-	const ERROR_CORRECT_LEVEL_H = 2; // 30%.
-
-	// max bits @ ec level L:07 M:15 Q:25 H:30 %
-	const TYPE_01 =  1; //  152  128  104   72
-	const TYPE_02 =  2; //  272  224  176  128
-	const TYPE_03 =  3; //  440  352  272  208
-	const TYPE_04 =  4; //  640  512  384  288
-	const TYPE_05 =  5; //  864  688  496  368
-	const TYPE_06 =  6; // 1088  864  608  480
-	const TYPE_07 =  7; // 1248  992  704  528
-	const TYPE_08 =  8; // 1552 1232  880  688
-	const TYPE_09 =  9; // 1856 1456 1056  800
-	const TYPE_10 = 10; // 2192 1728 1232  976
+	const OUTPUT_MARKUP_HTML  = 'html';
+	const OUTPUT_MARKUP_SVG   = 'svg';
+#	const OUTPUT_MARKUP_XML   = 'xml'; // anyone?
+
+	const OUTPUT_IMAGE_PNG    = 'png';
+	const OUTPUT_IMAGE_JPG    = 'jpg';
+	const OUTPUT_IMAGE_GIF    = 'gif';
+
+	const OUTPUT_STRING_JSON  = 'json';
+	const OUTPUT_STRING_TEXT  = 'txt';
+
+	const VERSION_AUTO        = -1;
+	const MASK_PATTERN_AUTO   = -1;
+
+	const ECC_L         = 0b01; // 7%.
+	const ECC_M         = 0b00; // 15%.
+	const ECC_Q         = 0b11; // 25%.
+	const ECC_H         = 0b10; // 30%.
+
+	const DATA_NUMBER   = 0b0001;
+	const DATA_ALPHANUM = 0b0010;
+	const DATA_BYTE     = 0b0100;
+	const DATA_KANJI    = 0b1000;
+
+	const ECC_MODES = [
+		QRCode::ECC_L => 0,
+		QRCode::ECC_M => 1,
+		QRCode::ECC_Q => 2,
+		QRCode::ECC_H => 3,
+	];
+
+	const DATA_MODES = [
+		self::DATA_NUMBER   => 0,
+		self::DATA_ALPHANUM => 1,
+		self::DATA_BYTE     => 2,
+		self::DATA_KANJI    => 3,
+	];
+
+	const OUTPUT_MODES = [
+		QRMarkup::class => [
+			self::OUTPUT_MARKUP_SVG,
+			self::OUTPUT_MARKUP_HTML,
+		],
+		QRImage::class => [
+			self::OUTPUT_IMAGE_PNG,
+			self::OUTPUT_IMAGE_GIF,
+			self::OUTPUT_IMAGE_JPG,
+		],
+		QRString::class => [
+			self::OUTPUT_STRING_JSON,
+			self::OUTPUT_STRING_TEXT,
+		]
+	];
 
 	/**
-	 * @var int
+	 * @var \chillerlan\QRCode\QROptions
 	 */
-	protected $typeNumber;
+	protected $options;
 
 	/**
-	 * @var int
+	 * @var \chillerlan\QRCode\Data\QRDataInterface
 	 */
-	protected $errorCorrectLevel;
+	protected $dataInterface;
 
 	/**
-	 * @var \chillerlan\QRCode\Output\QROutputInterface
+	 * QRCode constructor.
+	 *
+	 * @param \chillerlan\QRCode\QROptions|null $options
 	 */
-	protected $qrOutputInterface;
+	public function __construct(QROptions $options = null){
+		mb_internal_encoding('UTF-8');
+
+		$this->setOptions($options ?? new QROptions);
+	}
 
 	/**
-	 * @var string
+	 * @param \chillerlan\QRCode\QROptions $options
+	 *
+	 * @return \chillerlan\QRCode\QRCode
+	 * @throws \chillerlan\QRCode\QRCodeException
 	 */
-	protected $data;
+	public function setOptions(QROptions $options):QRCode{
+
+		if(!array_key_exists(QRCode::ECC_MODES[$options->eccLevel], QRCode::ECC_MODES)){
+			throw new QRCodeException('Invalid error correct level: '.$options->eccLevel);
+		}
+
+		$options->version = (int)$options->version;
+
+		// clamp min/max version number
+		$options->versionMin = (int)min($options->versionMin, $options->versionMax);
+		$options->versionMax = (int)max($options->versionMin, $options->versionMax);
+
+		$this->options = $options;
+
+		return $this;
+	}
 
 	/**
-	 * QRCode constructor.
+	 * @param string $data
 	 *
-	 * @param string                                      $data
-	 * @param \chillerlan\QRCode\Output\QROutputInterface $output
-	 * @param \chillerlan\QRCode\QROptions|null           $options
+	 * @return mixed
 	 */
-	public function __construct($data, QROutputInterface $output, QROptions $options = null){
-		$this->qrOutputInterface = $output;
+	public function render(string $data){
+		return $this->initOutputInterface($data)->dump();
+	}
+
+	/**
+	 * @param string $data
+	 *
+	 * @return \chillerlan\QRCode\Data\QRMatrix
+	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
+	 */
+	public function getMatrix(string $data):QRMatrix {
+		$data = trim($data);
+
+		if(empty($data)){
+			throw new QRCodeDataException('QRCode::getMatrix() No data given.');
+		}
 
-		$this->setData($data, $options);
+		$this->dataInterface = $this->initDataInterface($data);
+
+		$maskPattern = $this->options->maskPattern === self::MASK_PATTERN_AUTO
+			? $this->getBestMaskPattern()
+			: max(7, min(0, (int)$this->options->maskPattern));
+
+		$matrix = $this
+			->dataInterface
+			->initMatrix($maskPattern)
+		;
+
+		if((bool)$this->options->addQuietzone){
+			$matrix->setQuietZone($this->options->quietzoneSize);
+		}
+
+		return $matrix;
 	}
 
 	/**
-	 * @param string                            $data
-	 * @param \chillerlan\QRCode\QROptions|null $options
+	 * @return int
+	 */
+	protected function getBestMaskPattern():int{
+		$penalties = [];
+
+		$tester = new MaskPatternTester;
+
+		for($testPattern = 0; $testPattern < 8; $testPattern++){
+			$matrix = $this
+				->dataInterface
+				->initMatrix($testPattern, true);
+
+			$tester->setMatrix($matrix);
+
+			$penalties[$testPattern] = $tester->testPattern();
+		}
+
+		return array_search(min($penalties), $penalties, true);
+	}
+
+	/**
+	 * @param string                       $data
 	 *
-	 * @return \chillerlan\QRCode\QRCode
-	 * @throws \chillerlan\QRCode\QRCodeException
+	 * @return \chillerlan\QRCode\Data\QRDataInterface
+	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
 	 */
-	public function setData(string $data, QROptions $options = null):QRCode {
+	protected function initDataInterface(string $data):QRDataInterface{
+
+		$DATA_MODES = [
+			Number::class   => 'Number',
+			AlphaNum::class => 'AlphaNum',
+			Kanji::class    => 'Kanji',
+			Byte::class     => 'Byte',
+		];
+
+		foreach($DATA_MODES as $dataInterface => $mode){
+
+			if(call_user_func_array([$this, 'is'.$mode], [$data]) === true){
+				return $this->loadClass($dataInterface, QRDataInterface::class, $this->options, $data);
+			}
 
-		if(!$options instanceof QROptions){
-			$options = new QROptions;
 		}
 
-		$this->data              = trim($data);
-		$this->errorCorrectLevel = (int)$options->errorCorrectLevel;
-		$this->typeNumber        = (int)$options->typeNumber;
+		throw new QRCodeDataException('invalid data type');
+	}
+
+	/**
+	 * @param string $data
+	 *
+	 * @return \chillerlan\QRCode\Output\QROutputInterface
+	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+	 */
+	protected function initOutputInterface(string $data):QROutputInterface{
+
+		foreach(self::OUTPUT_MODES as $outputInterface => $modes){
+
+			if(in_array($this->options->outputType, $modes, true)){
+				return $this->loadClass($outputInterface, QROutputInterface::class, $this->options, $this->getMatrix($data));
+			}
 
-		if(empty($this->data)){
-			throw new QRCodeException('No data given.');
 		}
 
-		if(!in_array($this->errorCorrectLevel, range(0, 3), true)){
-			throw new QRCodeException('Invalid error correct level: '.$this->errorCorrectLevel);
+		throw new QRCodeOutputException('invalid output type');
+	}
+
+	/**
+	 * @param string $string
+	 *
+	 * @return bool
+	 */
+	public function isNumber(string $string):bool {
+		$len = strlen($string);
+
+		for($i = 0; $i < $len; $i++){
+			$c = ord($string[$i]);
+
+			if(!(ord('0') <= $c && $c <= ord('9'))){
+				return false;
+			}
 		}
 
-		return $this;
+		return true;
 	}
 
 	/**
-	 * @return mixed
+	 * @param string $string
+	 *
+	 * @return bool
 	 */
-	public function output(){
-		$this->qrOutputInterface->setMatrix($this->getRawData());
+	public function isAlphaNum(string $string):bool {
+		$len = strlen($string);
 
-		return $this->qrOutputInterface->dump();
+		for($i = 0; $i < $len; $i++){
+			$c = ord($string[$i]);
+
+			if(
+				   !(ord('0') <= $c && $c <= ord('9'))
+				&& !(ord('A') <= $c && $c <= ord('Z'))
+				&& strpos(' $%*+-./:', $string[$i]) === false
+			){
+				return false;
+			}
+		}
+
+		return true;
 	}
 
 	/**
-	 * @return array
+	 * @param string $string
+	 *
+	 * @return bool
+	 */
+	public function isKanji(string $string):bool {
+		$i   = 0;
+		$len = strlen($string);
+
+		while($i + 1 < $len){
+			$c = ((0xff&ord($string[$i])) << 8)|(0xff&ord($string[$i + 1]));
+
+			if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
+				return false;
+			}
+
+			$i += 2;
+		}
+
+		return !($i < $len);
+	}
+
+	/**
+	 * a dummy
+	 *
+	 * @param $data
+	 *
+	 * @return bool
 	 */
-	public function getRawData():array {
-		return (new QRDataGenerator($this->data, $this->typeNumber, $this->errorCorrectLevel))->getRawData();
+	protected function isByte(string $data):bool{
+		return !empty($data);
 	}
 
 }

+ 0 - 989
src/QRDataGenerator.php

@@ -1,989 +0,0 @@
-<?php
-/**
- * Class QRDataGenerator
- *
- * @filesource   QRDataGenerator.php
- * @created      24.10.2017
- * @package      chillerlan\QRCode
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2017 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCode;
-
-use chillerlan\QRCode\Data\{
-	AlphaNum, Byte, Kanji, Number, QRDataInterface
-};
-
-/**
- * const ALL THE THINGS! ~John Carmack
- */
-class QRDataGenerator{
-
-	const RSBLOCK = [
-		QRCode::ERROR_CORRECT_LEVEL_L => 0,
-		QRCode::ERROR_CORRECT_LEVEL_M => 1,
-		QRCode::ERROR_CORRECT_LEVEL_Q => 2,
-		QRCode::ERROR_CORRECT_LEVEL_H => 3,
-	];
-
-	const DATA_INTERFACES = [
-		QRDataInterface::MODE_ALPHANUM => AlphaNum::class,
-		QRDataInterface::MODE_BYTE     => Byte::class,
-		QRDataInterface::MODE_KANJI    => Kanji::class,
-		QRDataInterface::MODE_NUMBER   => Number::class,
-	];
-
-	const BLOCK_TABLE = [
-		// 1
-		[1, 26, 19], // L
-		[1, 26, 16], // M
-		[1, 26, 13], // Q
-		[1, 26,  9], // H
-		// 2
-		[1, 44, 34],
-		[1, 44, 28],
-		[1, 44, 22],
-		[1, 44, 16],
-		// 3
-		[1, 70, 55],
-		[1, 70, 44],
-		[2, 35, 17],
-		[2, 35, 13],
-		// 4
-		[1, 100, 80],
-		[2,  50, 32],
-		[2,  50, 24],
-		[4,  25,  9],
-		// 5
-		[1, 134, 108],
-		[2,  67,  43],
-		[2,  33,  15, 2, 34, 16],
-		[2,  33,  11, 2, 34, 12],
-		// 6
-		[2, 86, 68],
-		[4, 43, 27],
-		[4, 43, 19],
-		[4, 43, 15],
-		// 7
-		[2, 98, 78],
-		[4, 49, 31],
-		[2, 32, 14, 4, 33, 15],
-		[4, 39, 13, 1, 40, 14],
-		// 8
-		[2, 121, 97],
-		[2,  60, 38, 2, 61, 39],
-		[4,  40, 18, 2, 41, 19],
-		[4,  40, 14, 2, 41, 15],
-		// 9
-		[2, 146, 116],
-		[3,  58,  36, 2, 59, 37],
-		[4,  36,  16, 4, 37, 17],
-		[4,  36,  12, 4, 37, 13],
-		// 10
-		[2, 86, 68, 2, 87, 69],
-		[4, 69, 43, 1, 70, 44],
-		[6, 43, 19, 2, 44, 20],
-		[6, 43, 15, 2, 44, 16],
-	];
-
-	const PATTERN_POSITION = [
-		[],
-		[6, 18],
-		[6, 22],
-		[6, 26],
-		[6, 30],
-		[6, 34],
-		[6, 22, 38],
-		[6, 24, 42],
-		[6, 26, 46],
-		[6, 28, 50],
-		[6, 30, 54],
-		[6, 32, 58],
-		[6, 34, 62],
-		[6, 26, 46, 66],
-		[6, 26, 48, 70],
-		[6, 26, 50, 74],
-		[6, 30, 54, 78],
-		[6, 30, 56, 82],
-		[6, 30, 58, 86],
-		[6, 34, 62, 90],
-		[6, 28, 50, 72,  94],
-		[6, 26, 50, 74,  98],
-		[6, 30, 54, 78, 102],
-		[6, 28, 54, 80, 106],
-		[6, 32, 58, 84, 110],
-		[6, 30, 58, 86, 114],
-		[6, 34, 62, 90, 118],
-		[6, 26, 50, 74,  98, 122],
-		[6, 30, 54, 78, 102, 126],
-		[6, 26, 52, 78, 104, 130],
-		[6, 30, 56, 82, 108, 134],
-		[6, 34, 60, 86, 112, 138],
-		[6, 30, 58, 86, 114, 142],
-		[6, 34, 62, 90, 118, 146],
-		[6, 30, 54, 78, 102, 126, 150],
-		[6, 24, 50, 76, 102, 128, 154],
-		[6, 28, 54, 80, 106, 132, 158],
-		[6, 32, 58, 84, 110, 136, 162],
-		[6, 26, 54, 82, 110, 138, 166],
-		[6, 30, 58, 86, 114, 142, 170],
-	];
-
-	const MAX_BITS = [
-		QRCode::TYPE_01 => [128,  152,   72,  104],
-		QRCode::TYPE_02 => [224,  272,  128,  176],
-		QRCode::TYPE_03 => [352,  440,  208,  272],
-		QRCode::TYPE_04 => [512,  640,  288,  384],
-		QRCode::TYPE_05 => [688,  864,  368,  496],
-		QRCode::TYPE_06 => [864,  1088, 480,  608],
-		QRCode::TYPE_07 => [992,  1248, 528,  704],
-		QRCode::TYPE_08 => [1232, 1552, 688,  880],
-		QRCode::TYPE_09 => [1456, 1856, 800, 1056],
-		QRCode::TYPE_10 => [1728, 2192, 976, 1232],
-	];
-
-	const MAX_LENGTH = [
-		[[ 41,  25,  17,  10], [ 34,  20,  14,   8], [ 27,  16,  11,   7], [ 17,  10,   7,   4]],
-		[[ 77,  47,  32,  20], [ 63,  38,  26,  16], [ 48,  29,  20,  12], [ 34,  20,  14,   8]],
-		[[127,  77,  53,  32], [101,  61,  42,  26], [ 77,  47,  32,  20], [ 58,  35,  24,  15]],
-		[[187, 114,  78,  48], [149,  90,  62,  38], [111,  67,  46,  28], [ 82,  50,  34,  21]],
-		[[255, 154, 106,  65], [202, 122,  84,  52], [144,  87,  60,  37], [106,  64,  44,  27]],
-		[[322, 195, 134,  82], [255, 154, 106,  65], [178, 108,  74,  45], [139,  84,  58,  36]],
-		[[370, 224, 154,  95], [293, 178, 122,  75], [207, 125,  86,  53], [154,  93,  64,  39]],
-		[[461, 279, 192, 118], [365, 221, 152,  93], [259, 157, 108,  66], [202, 122,  84,  52]],
-		[[552, 335, 230, 141], [432, 262, 180, 111], [312, 189, 130,  80], [235, 143,  98,  60]],
-		[[652, 395, 271, 167], [513, 311, 213, 131], [364, 221, 151,  93], [288, 174, 119,  74]],
-	];
-
-	/**
-	 * @var int
-	 */
-	protected $typeNumber;
-
-	/**
-	 * @var int
-	 */
-	protected $errorCorrectLevel;
-
-	/**
-	 * @var int
-	 */
-	protected $lostPoint;
-
-	/**
-	 * @var int
-	 */
-	protected $darkCount;
-
-	/**
-	 * @var float
-	 */
-	protected $minLostPoint;
-
-	/**
-	 * @var int
-	 */
-	protected $maskPattern;
-
-	/**
-	 * @var array
-	 */
-	protected $matrix = [];
-
-	/**
-	 * @var int
-	 */
-	protected $pixelCount = 0;
-
-	/**
-	 * @var \chillerlan\QRCode\Data\QRDataInterface
-	 */
-	protected $qrDataInterface;
-
-	/**
-	 * @var \chillerlan\QRCode\BitBuffer
-	 */
-	protected $bitBuffer;
-
-	/**
-	 * QRDataGenerator constructor.
-	 *
-	 * @param string $data
-	 * @param int    $typeNumber
-	 * @param int    $errorCorrectLevel
-	 *
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	public function __construct(string $data, int $typeNumber, int $errorCorrectLevel){
-		$this->typeNumber        = $typeNumber;
-		$this->errorCorrectLevel = $errorCorrectLevel;
-		$this->bitBuffer         = new BitBuffer;
-
-		switch(true){
-			case $this->isAlphaNum($data):
-				$mode = $this->isNumber($data)
-					? QRDataInterface::MODE_NUMBER
-					: QRDataInterface::MODE_ALPHANUM;
-				break;
-			case $this->isKanji($data):
-				$mode = QRDataInterface::MODE_KANJI;
-				break;
-			default:
-				$mode = QRDataInterface::MODE_BYTE;
-				break;
-		}
-
-		$qrDataInterface       = self::DATA_INTERFACES[$mode];
-		$this->qrDataInterface = new $qrDataInterface($data);
-
-		if($this->typeNumber < 1 || $this->typeNumber > 10){
-			$this->typeNumber = $this->getTypeNumber($this->qrDataInterface, $mode);
-		}
-
-	}
-
-	/**
-	 * @return array
-	 */
-	public function getRawData():array{
-		$this->minLostPoint = 0;
-		$this->maskPattern  = 0;
-
-		for($pattern = 0; $pattern <= 7; $pattern++){
-			$this->testPattern($pattern);
-		}
-
-		$this->getMatrix(false, $this->maskPattern);
-
-		return $this->matrix;
-	}
-
-	/**
-	 * @param string $string
-	 *
-	 * @return bool
-	 */
-	public function isNumber(string $string):bool {
-		$len = strlen($string);
-
-		for($i = 0; $i < $len; $i++){
-			$chr = ord($string[$i]);
-
-			if(!(ord('0') <= $chr && $chr <= ord('9'))){
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	/**
-	 * @param string $string
-	 *
-	 * @return bool
-	 */
-	public function isAlphaNum(string $string):bool {
-		$len = strlen($string);
-
-		for($i = 0; $i < $len; $i++){
-			$chr = ord($string[$i]);
-
-			if(
-				   !(ord('0') <= $chr && $chr <= ord('9'))
-				&& !(ord('A') <= $chr && $chr <= ord('Z'))
-				&& strpos(' $%*+-./:', $string[$i]) === false
-			){
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	/**
-	 * @param string $string
-	 *
-	 * @return bool
-	 */
-	public function isKanji(string $string):bool {
-
-		if(empty($string)){
-			return false;
-		}
-
-		$i      = 0;
-		$len = strlen($string);
-
-		while($i + 1 < $len){
-			$c = ((0xff&ord($string[$i])) << 8)|(0xff&ord($string[$i + 1]));
-
-			if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
-				return false;
-			}
-
-			$i += 2;
-		}
-
-		return !($i < $len);
-	}
-
-	/**
-	 * @param \chillerlan\QRCode\Data\QRDataInterface $qrDataInterface
-	 * @param int                                     $mode
-	 *
-	 * @return int
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	protected function getTypeNumber(QRDataInterface $qrDataInterface, int $mode):int {
-		$length = $qrDataInterface->dataLength;
-
-		if($qrDataInterface->mode === QRDataInterface::MODE_KANJI){
-			$length = floor($length / 2);
-		}
-
-		$maxTypenumber = QRCode::TYPE_10;
-
-		$range = range(1, $maxTypenumber);
-		foreach($range as $type){
-
-			if($length <= $this->getMaxLength($type, $mode, $this->errorCorrectLevel)){
-				$maxTypenumber = $type;
-				break;
-			}
-
-		}
-
-		return $maxTypenumber;
-	}
-
-	/**
-	 * @param int $typeNumber
-	 * @param int $mode
-	 * @param int $errorCorrectLevel
-	 *
-	 * @return int
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	protected function getMaxLength(int $typeNumber, int $mode, int $errorCorrectLevel):int {
-
-		if(!array_key_exists($errorCorrectLevel, self::RSBLOCK)){
-			throw new QRCodeException('Invalid error correct level: '.$errorCorrectLevel);
-		}
-
-		if(!array_key_exists($mode, QRDataInterface::MODE)){
-			throw new QRCodeException('Invalid mode: '.$mode);
-		}
-
-		return self::MAX_LENGTH[$typeNumber - 1][self::RSBLOCK[$errorCorrectLevel]][QRDataInterface::MODE[$mode]];
-	}
-
-	/**
-	 * @param int $data
-	 *
-	 * @return int
-	 */
-	protected function getBCHTypeInfo(int $data):int {
-		$G15_MASK = (1 << 14)|(1 << 12)|(1 << 10)|(1 << 4)|(1 << 1);
-		$G15      = (1 << 10)|(1 << 8)|(1 << 5)|(1 << 4)|(1 << 2)|(1 << 1)|(1 << 0);
-
-		return (($data << 10)|$this->getBCHT($data, 10, $G15))^$G15_MASK;
-	}
-
-	/**
-	 * @param int $data
-	 *
-	 * @return int
-	 */
-	protected function getBCHTypeNumber(int $data):int{
-		$G18 = (1 << 12)|(1 << 11)|(1 << 10)|(1 << 9)|(1 << 8)|(1 << 5)|(1 << 2)|(1 << 0);
-
-		return ($data << 12)|$this->getBCHT($data, 12, $G18);
-	}
-
-	/**
-	 * @param int $data
-	 * @param int $bits
-	 * @param int $mask
-	 *
-	 * @return int
-	 */
-	protected function getBCHT(int $data, int $bits, int $mask):int {
-		$digit = $data << $bits;
-
-		while($this->getBCHDigit($digit) - $this->getBCHDigit($mask) >= 0){
-			$digit ^= ($mask << ($this->getBCHDigit($digit) - $this->getBCHDigit($mask)));
-		}
-
-		return $digit;
-	}
-
-	/**
-	 * @param int $data
-	 *
-	 * @return int
-	 */
-	protected function getBCHDigit(int $data):int {
-		$digit = 0;
-
-		while($data !== 0){
-			$digit++;
-			$data >>= 1;
-		}
-
-		return $digit;
-	}
-
-	/**
-	 * @param int $typeNumber
-	 * @param int $errorCorrectLevel
-	 *
-	 * @return array
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	protected function getRSBlocks(int $typeNumber, int $errorCorrectLevel):array {
-
-		if(!array_key_exists($errorCorrectLevel, self::RSBLOCK)){
-			throw new QRCodeException('$typeNumber: '.$typeNumber.' / $errorCorrectLevel: '.$errorCorrectLevel);
-		}
-
-		$rsBlock = self::BLOCK_TABLE[($typeNumber - 1) * 4 + self::RSBLOCK[$errorCorrectLevel]];
-		$list    = [];
-		$length  = count($rsBlock) / 3;
-
-		for($i = 0; $i < $length; $i++){
-			for($j = 0; $j < $rsBlock[$i * 3 + 0]; $j++){
-				$list[] = [$rsBlock[$i * 3 + 1], $rsBlock[$i * 3 + 2]];
-			}
-		}
-
-		return $list;
-	}
-
-	/**
-	 * @param array $range
-	 *
-	 * @return void
-	 */
-	protected function testLevel1(array $range){
-
-		foreach($range as $row){
-			foreach($range as $col){
-				$sameCount = 0;
-
-				foreach([-1, 0, 1] as $rowRange){
-
-					if($row + $rowRange < 0 || $this->pixelCount <= $row + $rowRange){
-						continue;
-					}
-
-					foreach([-1, 0, 1] as $colRange){
-
-						if(($rowRange === 0 && $colRange === 0) || ($col + $colRange < 0 || $this->pixelCount <= $col + $colRange)){
-							continue;
-						}
-
-						if($this->matrix[$row + $rowRange][$col + $colRange] === $this->matrix[$row][$col]){
-							$sameCount++;
-						}
-
-					}
-				}
-
-				if($sameCount > 5){
-					$this->lostPoint += (3 + $sameCount - 5);
-				}
-
-			}
-		}
-
-	}
-
-	/**
-	 * @param array $range
-	 *
-	 * @return void
-	 */
-	protected function testLevel2(array $range){
-
-		foreach($range as $row){
-			foreach($range as $col){
-				$count = 0;
-
-				if(
-					   $this->matrix[$row    ][$col    ]
-					|| $this->matrix[$row    ][$col + 1]
-					|| $this->matrix[$row + 1][$col    ]
-					|| $this->matrix[$row + 1][$col + 1]
-				){
-					$count++;
-				}
-
-				if($count === 0 || $count === 4){
-					$this->lostPoint += 3;
-				}
-
-			}
-		}
-
-	}
-
-	/**
-	 * @param array $range1
-	 * @param array $range2
-	 *
-	 * @return void
-	 */
-	protected function testLevel3(array $range1, array $range2){
-
-		foreach($range1 as $row){
-			foreach($range2 as $col){
-
-				if(
-					    $this->matrix[$row][$col    ]
-					&& !$this->matrix[$row][$col + 1]
-					&&  $this->matrix[$row][$col + 2]
-					&&  $this->matrix[$row][$col + 3]
-					&&  $this->matrix[$row][$col + 4]
-					&& !$this->matrix[$row][$col + 5]
-					&&  $this->matrix[$row][$col + 6]
-				){
-					$this->lostPoint += 40;
-				}
-
-			}
-		}
-
-		foreach($range1 as $col){
-			foreach($range2 as $row){
-
-				if(
-					    $this->matrix[$row    ][$col]
-					&& !$this->matrix[$row + 1][$col]
-					&&  $this->matrix[$row + 2][$col]
-					&&  $this->matrix[$row + 3][$col]
-					&&  $this->matrix[$row + 4][$col]
-					&& !$this->matrix[$row + 5][$col]
-					&&  $this->matrix[$row + 6][$col]
-				){
-					$this->lostPoint += 40;
-				}
-
-			}
-		}
-
-	}
-
-	/**
-	 * @param array $range
-	 *
-	 * @return void
-	 */
-	protected function testLevel4(array $range){
-
-		foreach($range as $col){
-			foreach($range as $row){
-				if($this->matrix[$row][$col]){
-					$this->darkCount++;
-				}
-			}
-		}
-
-	}
-
-	/**
-	 * @param int $pattern
-	 *
-	 * @return void
-	 */
-	protected function testPattern(int $pattern){
-		$this->getMatrix(true, $pattern);
-		$this->lostPoint = 0;
-		$this->darkCount = 0;
-
-		$range = range(0, $this->pixelCount - 1);
-
-		$this->testLevel1($range);
-		$this->testLevel2(range(0, $this->pixelCount - 2));
-		$this->testLevel3($range, range(0, $this->pixelCount - 7));
-		$this->testLevel4($range);
-
-		$this->lostPoint += (abs(100 * $this->darkCount / $this->pixelCount / $this->pixelCount - 50) / 5) * 10;
-
-		if($pattern === 0 || $this->minLostPoint > $this->lostPoint){
-			$this->minLostPoint = $this->lostPoint;
-			$this->maskPattern  = $pattern;
-		}
-
-	}
-
-	/**
-	 * @param bool $test
-	 *
-	 * @return void
-	 */
-	protected function setTypeNumber(bool $test){
-		$bits = $this->getBCHTypeNumber($this->typeNumber);
-
-		$range = range(0, 17);
-		foreach($range as $i){
-			$v = !$test && (($bits >> $i) & 1) === 1;
-
-			$a = (int)floor($i / 3);
-			$b = $i % 3 + $this->pixelCount - 8 - 3;
-
-			$this->matrix[$a][$b] = $v;
-			$this->matrix[$b][$a] = $v;
-		}
-
-	}
-
-	/**
-	 * @param bool $test
-	 * @param int  $pattern
-	 *
-	 * @return void
-	 */
-	protected function setTypeInfo(bool $test, int $pattern){
-		$this->setPattern();
-		$bits = $this->getBCHTypeInfo(($this->errorCorrectLevel << 3) | $pattern);
-
-		$range = range(0, 14);
-		foreach($range as $i){
-			$mod = !$test && (($bits >> $i) & 1) === 1;
-
-			switch(true){
-				case $i < 6:
-					$this->matrix[$i][8] = $mod;
-					break;
-				case $i < 8:
-					$this->matrix[$i + 1][8] = $mod;
-					break;
-				default:
-					$this->matrix[$this->pixelCount - 15 + $i][8] = $mod;
-			}
-
-			switch(true){
-				case $i < 8:
-					$this->matrix[8][$this->pixelCount - $i - 1] = $mod;
-					break;
-				case $i < 9:
-					$this->matrix[8][15 + 1 - $i - 1] = $mod;
-					break;
-				default:
-					$this->matrix[8][15 - $i - 1] = $mod;
-			}
-
-		}
-
-		$this->matrix[$this->pixelCount - 8][8] = !$test;
-	}
-
-	/**
-	 * @return void
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	protected function createData(){
-		$MAX_BITS = self::MAX_BITS[$this->typeNumber][$this->errorCorrectLevel];
-		$PAD0     = 0xEC;
-		$PAD1     = 0x11;
-
-		$this->bitBuffer
-			->clear()
-			->put($this->qrDataInterface->mode, 4)
-			->put(
-			$this->qrDataInterface->mode === QRDataInterface::MODE_KANJI
-					? floor($this->qrDataInterface->dataLength / 2)
-					: $this->qrDataInterface->dataLength,
-				$this->qrDataInterface->getLengthInBits($this->typeNumber)
-			);
-
-		$this->qrDataInterface->write($this->bitBuffer);
-
-		if($this->bitBuffer->length > $MAX_BITS){
-			throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.' > '.$MAX_BITS.'bit)');
-		}
-
-		// end code.
-		if($this->bitBuffer->length + 4 <= $MAX_BITS){
-			$this->bitBuffer->put(0, 4);
-		}
-
-		// padding
-		while($this->bitBuffer->length % 8 !== 0){
-			$this->bitBuffer->putBit(false);
-		}
-
-		// padding
-		while(true){
-
-			if($this->bitBuffer->length >= $MAX_BITS){
-				break;
-			}
-
-			$this->bitBuffer->put($PAD0, 8);
-
-			if($this->bitBuffer->length >= $MAX_BITS){
-				break;
-			}
-
-			$this->bitBuffer->put($PAD1, 8);
-		}
-
-	}
-
-	/**
-	 * @return array
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	protected function createBytes():array{
-		$rsBlocks       = $this->getRSBlocks($this->typeNumber, $this->errorCorrectLevel);
-		$rsBlockCount   = count($rsBlocks);
-		$ecdata         = array_fill(0, $rsBlockCount, null);
-		$dcdata         = $ecdata;
-		$totalCodeCount = 0;
-		$maxDcCount     = 0;
-		$maxEcCount     = 0;
-		$offset         = 0;
-		$index          = 0;
-
-		foreach($rsBlocks as $key => $block){
-			$rsBlockTotal     = $block[0];
-			$rsBlockDataCount = $block[1];
-			$maxDcCount       = max($maxDcCount, $rsBlockDataCount);
-			$maxEcCount       = max($maxEcCount, $rsBlockTotal - $rsBlockDataCount);
-			$dcdata[$key]     = array_fill(0, $rsBlockDataCount, null);
-
-			foreach($dcdata[$key] as $a => &$_dcdata){
-				$bdata   = $this->bitBuffer->buffer;
-				$_dcdata = 0xff & $bdata[$a + $offset];
-			}
-
-			$offset += $rsBlockDataCount;
-
-			$rsPoly  = new Polynomial;
-			$modPoly = new Polynomial;
-
-			$blockrange = range(0, $rsBlockTotal - $rsBlockDataCount - 1);
-
-			foreach($blockrange as $b){
-				$modPoly->setNum([1, $modPoly->gexp($b)]);
-				$rsPoly->multiply($modPoly->num);
-			}
-
-			$rsPolyCount = count($rsPoly->num);
-
-			$modPoly->setNum($dcdata[$key], $rsPolyCount - 1)->mod($rsPoly->num);
-
-			$ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
-			$add          = count($modPoly->num) - count($ecdata[$key]);
-
-			foreach($ecdata[$key] as $c => &$_ecdata){
-				$modIndex = $c + $add;
-				$_ecdata  = $modIndex >= 0 ? $modPoly->num[$modIndex] : 0;
-			}
-
-			$totalCodeCount += $rsBlockTotal;
-		}
-
-		$data    = array_fill(0, $totalCodeCount, null);
-		$rsrange = range(0, $rsBlockCount - 1);
-		$dcrange = range(0, $maxDcCount - 1);
-		$ecrange = range(0, $maxEcCount - 1);
-
-		foreach($dcrange as $x){
-			foreach($rsrange as $j){
-				if($x < count($dcdata[$j])){
-					$data[$index++] = $dcdata[$j][$x];
-				}
-			}
-		}
-
-		foreach($ecrange as $y){
-			foreach($rsrange as $k){
-				if($y < count($ecdata[$k])){
-					$data[$index++] = $ecdata[$k][$y];
-				}
-			}
-		}
-
-		return $data;
-	}
-
-	/**
-	 * @param int $pattern
-	 *
-	 * @return void
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	protected function mapData(int $pattern){
-		$this->createData();
-
-		$data      = $this->createBytes();
-		$inc       = -1;
-		$row       = $this->pixelCount - 1;
-		$bitIndex  = 7;
-		$byteIndex = 0;
-		$dataCount = count($data);
-
-		for($col = $this->pixelCount - 1; $col > 0; $col -= 2){
-
-			if($col === 6){
-				$col--;
-			}
-
-			while(true){
-				foreach([0, 1] as $c){
-					$_col = $col - $c;
-
-					if($this->matrix[$row][$_col] === null){
-						$dark = false;
-
-						if($byteIndex < $dataCount){
-							$dark = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
-						}
-
-						$a = $row + $_col;
-						$m = $row * $_col;
-
-						$MASK_PATTERN = [
-							0 => $a % 2,
-							1 => $row % 2,
-							2 => $_col % 3,
-							3 => $a % 3,
-							4 => (floor($row / 2) + floor($_col / 3)) % 2,
-							5 => $m % 2 + $m % 3,
-							6 => ($m % 2 + $m % 3) % 2,
-							7 => ($m % 3 + $a % 2) % 2,
-						][$pattern];
-
-						if($MASK_PATTERN === 0){
-							$dark = !$dark;
-						}
-
-						$this->matrix[$row][$_col] = $dark;
-
-						$bitIndex--;
-
-						if($bitIndex === -1){
-							$byteIndex++;
-							$bitIndex = 7;
-						}
-
-					}
-				}
-
-				$row += $inc;
-
-				if($row < 0 || $this->pixelCount <= $row){
-					$row -= $inc;
-					$inc = -$inc;
-
-					break;
-				}
-
-			}
-		}
-
-	}
-
-	/**
-	 * @return void
-	 */
-	protected function setupPositionProbePattern(){
-		$range = range(-1, 7);
-
-		foreach([[0, 0], [$this->pixelCount - 7, 0], [0, $this->pixelCount - 7]] as $grid){
-			$row = $grid[0];
-			$col = $grid[1];
-
-			foreach($range as $r){
-				foreach($range as $c){
-
-					if($row + $r <= -1 || $this->pixelCount <= $row + $r || $col + $c <= -1 || $this->pixelCount <= $col + $c){
-						continue;
-					}
-
-					$this->matrix[$row + $r][$col + $c] =
-						   (0 <= $r && $r <= 6 && ($c === 0 || $c === 6))
-						|| (0 <= $c && $c <= 6 && ($r === 0 || $r === 6))
-						|| (2 <= $c && $c <= 4 && 2 <= $r && $r <= 4);
-				}
-			}
-		}
-
-	}
-
-	/**
-	 * @return void
-	 */
-	protected function setupPositionAdjustPattern(){
-		$range = self::PATTERN_POSITION[$this->typeNumber - 1];
-
-		foreach($range as $i => $posI){
-			foreach($range as $j => $posJ){
-
-				if($this->matrix[$posI][$posJ] !== null){
-					continue;
-				}
-
-				for($row = -2; $row <= 2; $row++){
-					for($col = -2; $col <= 2; $col++){
-						$this->matrix[$posI + $row][$posJ + $col] =
-							    $row === -2 || $row === 2
-							||  $col === -2
-							||  $col === 2
-							|| ($row === 0 && $col === 0);
-					}
-				}
-
-			}
-		}
-
-	}
-
-	/**
-	 * @return void
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	protected function setPattern(){
-		$this->setupPositionProbePattern();
-		$this->setupPositionAdjustPattern();
-
-		// setupTimingPattern
-		$range = range(8, $this->pixelCount - 8 - 1);
-		foreach($range as $i){
-
-			if($this->matrix[$i][6] !== null){
-				continue; // @codeCoverageIgnore
-			}
-
-			$v = $i % 2 === 0;
-
-			$this->matrix[$i][6] = $v;
-			$this->matrix[6][$i] = $v;
-		}
-
-	}
-
-	/**
-	 * @param bool $test
-	 * @param int  $maskPattern
-	 *
-	 * @throws \chillerlan\QRCode\QRCodeException
-	 */
-	protected function getMatrix(bool $test, int $maskPattern){
-		$this->pixelCount = $this->typeNumber * 4 + 17;
-		$this->matrix     = array_fill(0, $this->pixelCount, array_fill(0, $this->pixelCount, null));
-
-		$this->setTypeInfo($test, $maskPattern);
-
-		if($this->typeNumber >= 7){
-			$this->setTypeNumber($test);
-		}
-
-		$this->mapData($maskPattern);
-	}
-
-}

+ 221 - 9
src/QROptions.php

@@ -12,31 +12,243 @@
 
 namespace chillerlan\QRCode;
 
+use chillerlan\QRCode\Data\QRMatrix;
+use chillerlan\Traits\Container;
+
 /**
- * @property int $errorCorrectLevel
- * @property int $typeNumber
+ * @property int    $version
+ * @property int    $versionMin
+ * @property int    $versionMax
+ * @property int    $eccLevel
+ * @property int    $maskPattern
+ * @property bool   $addQuietzone
+ * @property bool   $quietzoneSize
+ *
+ * @property string $outputType
+ * @property string $cachefile
+ *
+ * @property string $eol
+ * @property int    $scale
+ *
+ * @property string $cssClass
+ * @property string $htmlRowTag
+ * @property bool   $htmlOmitEndTag
+ * @property string $svgFgColor
+ * @property string $svgBgColor
+ *
+ * @property string $textDark
+ * @property string $textLight
+ *
+ * @property bool   $imageBase64
+ * @property bool   $imageTransparent
+ * @property int    $pngCompression
+ * @property int    $jpegQuality
+ *
+ * @property array  $moduleValues
  */
 class QROptions{
 	use Container;
 
+	/**
+	 * QR Code version number
+	 *
+	 *   [1 ... 40] or QRCode::VERSION_AUTO
+	 *
+	 * @var int
+	 */
+	protected $version = QRCode::VERSION_AUTO;
+
+	/**
+	 * Minimum QR version (if $version = QRCode::VERSION_AUTO)
+	 *
+	 * @var int
+	 */
+	protected $versionMin = 1;
+
+	/**
+	 * Maximum QR version
+	 *
+	 * @var int
+	 */
+	protected $versionMax = 40;
+
 	/**
 	 * Error correct level
 	 *
-	 *   QRCode::ERROR_CORRECT_LEVEL_X where X is
-	 *    L,   M,   Q,   H
-	 *   7%, 15%, 25%, 30%
+	 *   QRCode::ECC_X where X is
+	 *    L =>  7%
+	 *    M => 15%
+	 *    Q => 25%
+	 *    H => 30%
+	 *
+	 * @var int
+	 */
+	protected $eccLevel = QRCode::ECC_L;
+
+	/**
+	 * Mask Pattern to use
+	 *
+	 *  [0...7] or QRCode::MASK_PATTERN_AUTO
 	 *
 	 * @var int
 	 */
-	protected $errorCorrectLevel = QRCode::ERROR_CORRECT_LEVEL_M;
+	protected $maskPattern = QRCode::MASK_PATTERN_AUTO;
 
 	/**
-	 * Type number
+	 * Add a "quiet zone" (margin) according to the QR code spec
 	 *
-	 *   QRCode::TYPE_XX where XX is 01 ... 10, null = auto
+	 * @var bool
+	 */
+	protected $addQuietzone = true;
+
+	/**
+	 *  Size of the quiet zone
+	 *
+	 *   internally clamped to [0 ... $moduleCount / 2], defaults to 4 modules
 	 *
 	 * @var int
 	 */
-	protected $typeNumber = null;
+	protected $quietzoneSize;
+
+	/**
+	 * QRCode::OUTPUT_MARKUP_XXXX where XXXX = HTML, SVG
+	 * QRCode::OUTPUT_IMAGE_XXX where XXX = PNG, GIF, JPG
+	 * QRCode::OUTPUT_STRING_XXXX where XXXX = TEXT, JSON
+	 *
+	 * @var string
+	 */
+	protected $outputType;
+
+	/**
+	 * /path/to/cache.file
+	 *
+	 * @var string
+	 */
+	protected $cachefile;
+
+
+
+	/**
+	 * newline string [HTML, SVG, TEXT]
+	 *
+	 * @var string
+	 */
+	protected $eol = PHP_EOL;
+
+	/**
+	 * size of a QR code pixel [SVG, IMAGE_*]
+	 * HTML -> via CSS
+	 *
+	 * @var int
+	 */
+	protected $scale = 5;
+
+
+
+
+	/**
+	 * a common css class
+	 *
+	 * @var string
+	 */
+	protected $cssClass;
+
+	/**
+	 * the shortest available semanically correct row (block) tag to not bloat the output
+	 *
+	 * @var string
+	 */
+	protected $htmlRowTag = 'p';
+
+	/**
+	 * the closing tag may be omitted (moar bloat!)
+	 *
+	 * @var bool
+	 */
+	protected $htmlOmitEndTag = true;
+
+	/**
+	 * SVG foreground color
+	 *
+	 * @var string
+	 */
+	protected $svgFgColor = '#000';
+
+	/**
+	 * SVG background color
+	 *
+	 * @var string
+	 */
+	protected $svgBgColor = '#fff';
+
+
+
+	/**
+	 * string substitute for dark
+	 *
+	 * @var string
+	 */
+	protected $textDark = '🔴';
+
+	/**
+	 * string substitute for light
+	 *
+	 * @var string
+	 */
+	protected $textLight = '⭕';
+
+
+
+	/**
+	 * @var bool
+	 */
+	protected $imageBase64 = true;
+
+	/**
+	 * not supported by jpg
+	 *
+	 * @var bool
+	 */
+	protected $imageTransparent = true;
+
+	/**
+	 * @var int
+	 */
+	protected $pngCompression = -1;
+
+	/**
+	 * @var int
+	 */
+	protected $jpegQuality = 85;
+
+	/**
+	 * Module values map
+	 *
+	 *   HTML : #ABCDEF, cssname, rgb(), rgba()...
+	 *   IMAGE: [63, 127, 255] // R, G, B
+	 *
+	 * @var array
+	 */
+	protected $moduleValues = [
+		// light
+		QRMatrix::M_DATA            => false, // 4
+		QRMatrix::M_FINDER          => false, // 6
+		QRMatrix::M_SEPARATOR       => false, // 8
+		QRMatrix::M_ALIGNMENT       => false, // 10
+		QRMatrix::M_TIMING          => false, // 12
+		QRMatrix::M_FORMAT          => false, // 14
+		QRMatrix::M_VERSION         => false, // 16
+		QRMatrix::M_QUIETZONE       => false, // 18
+		QRMatrix::M_TEST            => false, // 255
+		// dark
+		QRMatrix::M_DARKMODULE << 8 => true,  // 512
+		QRMatrix::M_DATA << 8       => true,  // 1024
+		QRMatrix::M_FINDER << 8     => true,  // 1536
+		QRMatrix::M_ALIGNMENT << 8  => true,  // 2560
+		QRMatrix::M_TIMING << 8     => true,  // 3072
+		QRMatrix::M_FORMAT << 8     => true,  // 3584
+		QRMatrix::M_VERSION << 8    => true,  // 4096
+		QRMatrix::M_TEST << 8       => true,  // 65280
+	];
 
 }

+ 13 - 10
tests/BitBufferTest.php → tests/Data/BitBufferTest.php

@@ -1,22 +1,25 @@
 <?php
 /**
+ * Class BitBufferTest
+ *
  * @filesource   BitBufferTest.php
  * @created      08.02.2016
+ * @package      chillerlan\QRCodeTest\Data
  * @author       Smiley <smiley@chillerlan.net>
  * @copyright    2015 Smiley
  * @license      MIT
  */
 
-namespace chillerlan\QRCodeTest;
+namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\BitBuffer;
-use chillerlan\QRCode\Data\QRDataInterface;
-use PHPUnit\Framework\TestCase;
+use chillerlan\QRCode\Helpers\BitBuffer;
+use chillerlan\QRCode\QRCode;
+use chillerlan\QRCodeTest\QRTestAbstract;
 
-class BitBufferTest extends TestCase{
+class BitBufferTest extends QRTestAbstract{
 
 	/**
-	 * @var \chillerlan\QRCode\BitBuffer
+	 * @var \chillerlan\QRCode\Helpers\BitBuffer
 	 */
 	protected $bitBuffer;
 
@@ -26,10 +29,10 @@ class BitBufferTest extends TestCase{
 
 	public function bitProvider(){
 		return [
-			[QRDataInterface::MODE_NUMBER,    16],
-			[QRDataInterface::MODE_ALPHANUM,  32],
-			[QRDataInterface::MODE_BYTE,      64],
-			[QRDataInterface::MODE_KANJI,    128],
+			[QRCode::DATA_NUMBER, 16],
+			[QRCode::DATA_ALPHANUM, 32],
+			[QRCode::DATA_BYTE, 64],
+			[QRCode::DATA_KANJI, 128],
 		];
 	}
 

+ 0 - 120
tests/Data/DataTest.php

@@ -1,120 +0,0 @@
-<?php
-/**
- *
- * @filesource   DataTest.php
- * @created      08.02.2016
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2015 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCodeTest\Data;
-
-use chillerlan\QRCode\BitBuffer;
-use chillerlan\QRCode\Data\AlphaNum;
-use chillerlan\QRCode\Data\Byte;
-use chillerlan\QRCode\Data\Kanji;
-use chillerlan\QRCode\Data\Number;
-use chillerlan\QRCode\Data\QRDataInterface;
-use PHPUnit\Framework\TestCase;
-
-class DataTest extends TestCase{
-
-	/**
-	 * @var \chillerlan\QRCode\BitBuffer
-	 */
-	protected $bitBuffer;
-
-	/**
-	 * @var \chillerlan\QRCode\Data\QRDataInterface
-	 */
-	protected $module;
-
-	public function bitProviderMode(){
-		return [
-			[QRDataInterface::MODE_NUMBER, Number::class, '123456789'],
-			[QRDataInterface::MODE_NUMBER, Number::class, '1234567890'],
-			[QRDataInterface::MODE_NUMBER, Number::class, '12345678901'],
-			[QRDataInterface::MODE_ALPHANUM, AlphaNum::class, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:'],
-			[QRDataInterface::MODE_BYTE, Byte::class, '#\\'],
-			[QRDataInterface::MODE_KANJI, Kanji::class, '茗荷'],
-		];
-	}
-
-	protected function setUp(){
-		$this->bitBuffer = new BitBuffer;
-	}
-
-	/**
-	 * @dataProvider bitProviderMode
-	 */
-	public function testMode($mode, $class, $data){
-		$this->module = new $class($data);
-		$this->assertEquals($mode, $this->module->mode);
-	}
-
-
-	public function bitProviderWrite(){
-		return [
-			[Number::class, '123456789', [30, 220, 140, 84]],
-			[Number::class, '1234567890', [30, 220, 140, 84, 0]],
-			[Number::class, '12345678901', [30, 220, 140, 84, 8]],
-			[AlphaNum::class, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', [57, 168, 165, 66, 174, 22, 122, 230, 95, 172, 81, 149, 180, 38, 178, 220, 28, 58, 11, 196, 88, 231, 40, 102, 87, 60, 237, 94, 99, 227, 108]],
-			[Byte::class, '#\\', [35, 92]],
-			[Kanji::class, '茗荷', [236, 100, 74, 18, 238]],
-		];
-	}
-
-	/**
-	 * @dataProvider bitProviderWrite
-	 */
-	public function testWrite($class, $data, $expected){
-		$this->bitBuffer->clear();
-		$this->module = new $class($data);
-		$this->module->write($this->bitBuffer);
-
-		$this->assertSame($expected, $this->bitBuffer->buffer);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\Data\QRCodeDataException
-	 * @expectedExceptionMessage illegal char: 92
-	 */
-	public function testAlphaNumCharException(){
-		$this->bitBuffer->clear();
-		$this->module = new AlphaNum('\\');
-		$this->module->write($this->bitBuffer);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\Data\QRCodeDataException
-	 * @expectedExceptionMessage illegal char at 7 (50051)
-	 */
-	public function testKanjiCharExceptionA(){
-		$this->bitBuffer->clear();
-		$this->module = new Kanji('茗荷Ã');
-		$this->module->write($this->bitBuffer);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\Data\QRCodeDataException
-	 * @expectedExceptionMessage illegal char at 7
-	 */
-	public function testKanjiCharExceptionB(){
-		$this->bitBuffer->clear();
-		$this->module = new Kanji('茗荷\\');
-		$this->module->write($this->bitBuffer);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\Data\QRCodeDataException
-	 * @expectedExceptionMessage illegal char: 92
-	 */
-	public function testNumberCharException(){
-		$this->bitBuffer->clear();
-		$this->module = new Number('\\');
-		$this->module->write($this->bitBuffer);
-	}
-
-
-}

+ 9 - 8
tests/PolynomialTest.php → tests/Data/PolynomialTest.php

@@ -1,32 +1,33 @@
 <?php
 /**
+ * Class PolynomialTest
  *
  * @filesource   PolynomialTest.php
  * @created      09.02.2016
- * @package      chillerlan\QRCodeTest
+ * @package      chillerlan\QRCodeTest\Data
  * @author       Smiley <smiley@chillerlan.net>
  * @copyright    2015 Smiley
  * @license      MIT
  */
 
-namespace chillerlan\QRCodeTest;
+namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\Polynomial;
-use PHPUnit\Framework\TestCase;
+use chillerlan\QRCode\Helpers\Polynomial;
+use chillerlan\QRCodeTest\QRTestAbstract;
 
-class PolynomialTest extends TestCase{
+class PolynomialTest extends QRTestAbstract{
 
 	/**
-	 * @var \chillerlan\QRCode\Polynomial
+	 * @var \chillerlan\QRCode\Helpers\Polynomial
 	 */
 	protected $polynomial;
 
 	protected function setUp(){
-		$this->polynomial = new Polynomial;
+		$this->polynomial = new \chillerlan\QRCode\Helpers\Polynomial;
 	}
 
 	public function testGexp(){
-		$this->assertEquals(142, $this->polynomial->gexp( -1));
+		$this->assertEquals(142, $this->polynomial->gexp(-1));
 		$this->assertEquals(133, $this->polynomial->gexp(128));
 		$this->assertEquals(2,   $this->polynomial->gexp(256));
 	}

+ 49 - 0
tests/Data/QRMatrixTest.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * Class QRMatrixTest
+ *
+ * @filesource   QRMatrixTest.php
+ * @created      17.11.2017
+ * @package      chillerlan\QRCodeTest\Data
+ * @author       Smiley <smiley@chillerlan.net>
+ * @copyright    2017 Smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Data;
+
+use chillerlan\QRCode\Data\QRMatrix;
+use chillerlan\QRCodeTest\QRTestAbstract;
+
+class QRMatrixTest extends QRTestAbstract{
+
+	protected $FQCN = QRMatrix::class;
+
+	public function testInstance(){
+		$q = $this->reflection->newInstanceArgs([1, 0]);
+		$this->assertCount($q->size(), $q->matrix());
+		$this->assertInstanceOf($this->FQCN, $q);
+	}
+
+	/**
+	 * @expectedException \chillerlan\QRCode\Data\QRCodeDataException
+	 * @expectedExceptionMessage invalid QR Code version
+	 */
+	public function testInvalidVersionException(){
+		$this->reflection->newInstanceArgs([42, 0]);
+	}
+
+	/**
+	 * @expectedException \chillerlan\QRCode\Data\QRCodeDataException
+	 * @expectedExceptionMessage invalid ecc level
+	 */
+	public function testInvalidEccException(){
+		$this->reflection->newInstanceArgs([1, 42]);
+	}
+
+/*	public function testSetFinderPattern(){
+		$q = $this->reflection->newInstanceArgs([1, 0]);
+		$q->setFinderPattern();
+		var_dump($q->matrix());
+	}*/
+}

+ 0 - 59
tests/Output/ImageTest.php

@@ -1,59 +0,0 @@
-<?php
-/**
- *
- * @filesource   ImageTest.php
- * @created      08.02.2016
- * @package      chillerlan\QRCodeTest\Output
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2015 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCodeTest\Output;
-
-use chillerlan\QRCode\Output\QRImage;
-use chillerlan\QRCode\Output\QRImageOptions;
-use chillerlan\QRCode\QRCode;
-
-class ImageTest extends OutputTestAbstract{
-
-	protected $outputInterfaceClass = QRImage::class;
-	protected $outputOptionsClass   = QRImageOptions::class;
-
-	public function testOptions(){
-		$this->assertEquals(QRCode::OUTPUT_IMAGE_PNG, $this->options->type);
-		$this->assertEquals(true, $this->options->base64);
-	}
-
-	public function imageDataProvider(){
-		return [
-			[QRCode::OUTPUT_IMAGE_PNG, 'foobar', 'img1.png.uri'],
-			[QRCode::OUTPUT_IMAGE_GIF, 'foobar', 'img1.gif.uri'],
-			[QRCode::OUTPUT_IMAGE_JPG, 'foobar', 'img1.jpg.uri'],
-			[QRCode::OUTPUT_IMAGE_PNG, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'img2.png.uri'],
-			[QRCode::OUTPUT_IMAGE_GIF, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'img2.gif.uri'],
-			[QRCode::OUTPUT_IMAGE_JPG, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'img2.jpg.uri'],
-		];
-	}
-
-	/**
-	 * @dataProvider imageDataProvider
-	 */
-	public function testImageOutput($type, $data, $expected){
-		$this->options->type = $type;
-		$output = (new QRCode($data, new $this->outputInterfaceClass($this->options)))->output();
-
-		// jpeg test is causing trouble
-		if($type === QRCode::OUTPUT_IMAGE_JPG){
-			$this->markTestSkipped('jpeg test skipped');
-		}
-		// skip png for PHP 7.2 https://travis-ci.org/codemasher/php-qrcode/jobs/292110988
-		else if($type === QRCode::OUTPUT_IMAGE_PNG && PHP_VERSION_ID >= 70200){
-			$this->markTestSkipped('png test skipped, PHP version: '.PHP_VERSION);
-		}
-		else{
-			$this->assertEquals(file_get_contents(__DIR__.'/image/'.$expected), $output);
-		}
-	}
-
-}

+ 0 - 87
tests/Output/MarkupTest.php

@@ -1,87 +0,0 @@
-<?php
-/**
- *
- * @filesource   MarkupTest.php
- * @created      17.12.2016
- * @package      chillerlan\QRCodeTest\Output
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2016 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCodeTest\Output;
-
-use chillerlan\QRCode\Output\QRMarkup;
-use chillerlan\QRCode\Output\QRMarkupOptions;
-use chillerlan\QRCode\QRCode;
-
-/**
- * Class MarkupTest
- */
-class MarkupTest extends OutputTestAbstract{
-
-	protected $outputInterfaceClass = QRMarkup::class;
-	protected $outputOptionsClass   = QRMarkupOptions::class;
-
-	public function testOptions(){
-		$this->assertEquals(QRCode::OUTPUT_MARKUP_SVG, $this->options->type);
-	}
-
-	public function markupDataProvider(){
-		return [
-			[QRCode::OUTPUT_MARKUP_HTML, true,  'foobar', 'str1.html'],
-			[QRCode::OUTPUT_MARKUP_HTML, false, 'foobar', 'str2.html'],
-			[QRCode::OUTPUT_MARKUP_HTML, true,  'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str3.html'],
-			[QRCode::OUTPUT_MARKUP_HTML, false, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str4.html'],
-			[QRCode::OUTPUT_MARKUP_SVG , null,  'foobar', 'str1.svg'],
-			[QRCode::OUTPUT_MARKUP_SVG , null,  'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str2.svg'],
-		];
-	}
-
-	/**
-	 * @dataProvider markupDataProvider
-	 */
-	public function testMarkupOutput($type, $omitEndTag, $data, $expected){
-		$this->options->type = $type;
-		$this->options->htmlOmitEndTag = $omitEndTag;
-		$this->options->cssClass = 'test';
-		$this->assertEquals(file_get_contents(__DIR__.'/markup/'.$expected), (new QRCode($data, new $this->outputInterfaceClass($this->options)))->output());
-	}
-
-	public function markupTestDataProvider(){
-		return [
-			[QRCode::OUTPUT_MARKUP_SVG],
-			[QRCode::OUTPUT_MARKUP_HTML],
-		];
-	}
-
-	/**
-	 * @dataProvider markupTestDataProvider
-	 */
-	public function testSaveToFile(string $type){
-		$this->options->type     = $type;
-		$this->options->cssClass = 'foo';
-
-		$data = (new QRCode('foo', new $this->outputInterfaceClass($this->options)))->output();
-
-		$this->options->cachefile = __DIR__.'/markup/save_test.'.$type;
-
-		$this->assertTrue((new QRCode('foo', new $this->outputInterfaceClass($this->options)))->output());
-
-		$this->assertContains($data, file_get_contents($this->options->cachefile));
-	}
-
-	/**
-	 * @dataProvider markupTestDataProvider
-	 *
-	 * @expectedException \chillerlan\QRCode\Output\QRCodeOutputException
-	 * @expectedExceptionMessage Could not write to cache file
-	 */
-	public function testSaveToFileException(string $type){
-		$this->options->type = $type;
-		$this->options->cachefile = __DIR__.'/foo/bar';
-
-		(new QRCode('foo', new $this->outputInterfaceClass($this->options)))->output();
-	}
-
-}

+ 0 - 56
tests/Output/OutputTestAbstract.php

@@ -1,56 +0,0 @@
-<?php
-/**
- *
- * @filesource   OutputTestAbstract.php
- * @created      17.12.2016
- * @package      chillerlan\QRCodeTest\Output
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2016 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCodeTest\Output;
-
-use PHPUnit\Framework\TestCase;
-
-/**
- * Class OutputTestAbstract
- */
-abstract class OutputTestAbstract extends TestCase{
-
-	protected $outputInterfaceClass;
-	protected $outputOptionsClass;
-
-	protected $options;
-	protected $outputInterface;
-
-	protected function setUp(){
-		$this->options         = new $this->outputOptionsClass;
-		$this->outputInterface = new $this->outputInterfaceClass($this->options);
-	}
-
-	public function testInstance(){
-		$this->assertInstanceOf($this->outputInterfaceClass, $this->outputInterface);
-		$this->assertInstanceOf($this->outputOptionsClass, $this->options);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\Output\QRCodeOutputException
-	 * @expectedExceptionMessage Invalid output type!
-	 */
-	public function testOutputTypeException(){
-		$this->options->type = 'foo';
-		new $this->outputInterfaceClass($this->options);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\Output\QRCodeOutputException
-	 * @expectedExceptionMessage Invalid matrix!
-	 */
-	public function testSetMatrixException(){
-		/** @var \chillerlan\QRCode\Output\QROutputInterface  $outputInterface */
-		$outputInterface = new $this->outputInterfaceClass;
-		$outputInterface->setMatrix([]);
-	}
-
-}

+ 0 - 46
tests/Output/StringTest.php

@@ -1,46 +0,0 @@
-<?php
-/**
- *
- * @filesource   StringTest.php
- * @created      08.02.2016
- * @package      chillerlan\QRCodeTest\Output
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2015 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCodeTest\Output;
-
-use chillerlan\QRCode\Output\QRString;
-use chillerlan\QRCode\Output\QRStringOptions;
-use chillerlan\QRCode\QRCode;
-
-class StringTest extends OutputTestAbstract{
-
-	protected $outputInterfaceClass = QRString::class;
-	protected $outputOptionsClass   = QRStringOptions::class;
-
-	public function testOptions(){
-		$this->assertEquals(QRCode::OUTPUT_STRING_JSON, $this->options->type);
-	}
-
-	public function stringDataProvider(){
-		return [
-			[QRCode::OUTPUT_STRING_JSON, 'foobar', 'str1.json'],
-			[QRCode::OUTPUT_STRING_JSON, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str2.json'],
-			[QRCode::OUTPUT_STRING_TEXT, 'foobar', 'str1.txt'],
-			[QRCode::OUTPUT_STRING_TEXT, 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', 'str2.txt'],
-			// https://github.com/codemasher/php-qrcode/issues/4
-			[QRCode::OUTPUT_STRING_TEXT, 'eyJjdCI6IjVPSExXNzZQZUg1NitEUUdtcFwvY2FBPT0iLCJpdiI6ImY0ZTI4MGMyYjc2NDExZmJjMmUzMjc5NzA0MTc3YmI4IiwicyI6ImZjZTZlMTY3YjNjMTQwMDUifQ%3D%3D', 'str3.txt'],
-		];
-	}
-
-	/**
-	 * @dataProvider stringDataProvider
-	 */
-	public function testStringOutput($type, $data, $expected){
-		$this->options->type = $type;
-		$this->assertEquals(file_get_contents(__DIR__.'/string/'.$expected), (new QRCode($data, new $this->outputInterfaceClass($this->options)))->output());
-	}
-
-}

+ 0 - 1
tests/Output/image/img1.gif.uri

@@ -1 +0,0 @@
-data:image/gif;base64,R0lGODlhcwBzAIAAAAQCBP///yH5BAEAAAEALAAAAABzAHMAAAL+jI+py+0Po5y02ouz3rz7D4biSJYXgKbqyrbt5L5ITNerY+c0rCf6z8IBhwBezkcECpM9yQ/JPDaizchzRrUtUZ0bVmpVdcVT8sZ7uIZTY3bZfTYb1BC0xq7AY/D0h/4klxe4F9i3VcTxl1ZYNTf4xfUWuQjnCGY5ucDXGKCIiciwWdnJ6Uk62meqyoha+kiZeaoFCar5+gkVm3orW9u7Q3soHEwLDOub67s7mmxs23rZO3zsLC1ZM/1ZvQo9e3xYndwcI3497lIeypmuTU6s3o39/uxNr+zKPP/r/l3MKtOPGj9c5vyh07dtoDWBBwPaOweQIENdCkXVW/jQYET+jPviWbwXjRvFkP+CaAQ58qJIZIDyOeTILsPKNrFizkzEy5SRmghz8trJ8iUhlxKLCiJK4WYcpB+zyfSZJWE8m1HDnaxKderVqOCqlsya0utKsVpffiTLc6JXsCjbog1qYs3GuHRDWK2Ld+ncvHyH7u0LGKjOwEfDsnUaE7E+p00XOxZaEF7ajGa/RqZcB+rkwm0PX+YMVLHkzj2ZaoYL+nNXIn7lVTYdDaKSliotj77bjjVt10bHKpRtyAk+w9dwdxzSWirx279PAl/nfGvZ3JuVR1kd1rps7G6TcH8+/Thq60y+S19OfXxFrsVLao/OHPbf3t7h2/Mt3+Ro8Bv+jftvrhZj9QXYXn74VefecPrdd9qB6nmkoIMCGrheeMHFh2B4/JH22kXm7fdYeo11yJsfANJHYUMoZnciZ0oZtaFnag1GIGEhJtXihYDRmJmKLrbIF48m+hijjTcK1iCQeQn5IYNEnnddgegN2V+ObyWpoZTdpShWkzBpuZ2WWIF5pH3icTcmhhz2WOWTkFkgoXCxmZnYbl6iiWWGm4VGplwlhgmiXg/O96Ob6YlWQZx+/mcoR8uw6OOIMNJZWm0QRmrbpDViJp6knWbqaJ6DbtkmpKWu+eWmqc0I6qOoKsqpqxNOCaimsSo4K6q1pnorSfnJ6CStn8k6LKi7CnmMVoV6vipqnYGK2KqypP6pWqUlJmvhgGyOWiSJnpYn57LdrrhgqLr5met7qmLrYbVvJvqTmPGiGy5qfMIJHb5IOQvvvvL6S2/Ad8wbbLmt3TswwAUz6e7C7HmbYJaqbgguxJcKOzGUFZPLrq6UspoFWx3ze+a/VIgcMcaHGslyyy6/DHPMMs9Mc80WFAAAOw==

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
tests/Output/image/img1.jpg.uri


+ 0 - 1
tests/Output/image/img1.png.uri

@@ -1 +0,0 @@
-data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHMAAABzCAIAAAAkIaqxAAAABnRSTlMA/wD/AP83WBt9AAACGUlEQVR4nO3cwU4DMQwAUYL4/19e7j54ZTmTuNW8G6K0MIqw0mZ3Pc/zI8Dv7V/ga1mWYlmKZSl/4eu11pkXLk3O0m8VnvnWX+SapViWYlmKZSlxggUbd2j5JOnMqJJjf5FrlmJZimUplqW8TLCAmzP5xil/qtKD85/NlZ7ZNUuxLMWyFMtSahPslnzOzPwozzVLsSzFshTLUqZMsNKMCg/u7ME4rlmKZSmWpViWUptg3HDoHL8Y8rFY4JqlWJZiWYplKS8T7NhBvnwf1flu/kIc1yzFshTLUixLWUPecws+8YOvwDVLsSzFshTLUl6uB9u4F+p8WnXsLOLGF3LNUixLsSzFspS4B9v4Jltnp7TxEuZjbzl6RfMhlqVYlmJZSuskB3eR8sa5WvrZ0vTLuWYplqVYlmJZSus0fekf/K2rkm+dxHfNUixLsSzFspTaXaXy//elWdF5odJ3bx0Kcc1SLEuxLMWylClnEbm90MadYc7PwQ6xLMWyFMtSwJMcuc7o2HjKsfNC+YNdsxTLUixLsSxlyr3pO7h9VIdrlmJZimUplqVMuTf9xtOG+ese23O6ZimWpViWYlnKlDv7Drl0ujMMA9csxbIUy1IsS5kywbi90MZ9Y2lUumYplqVYlmJZytB705ccOy/vXaVGsCzFshTLUj7y3vRB55pl7i1H1yzFshTLUixLmXI92PdxzVIsS7EsxbKUf5cMC/onT8WdAAAAAElFTkSuQmCC

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
tests/Output/image/img2.gif.uri


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
tests/Output/image/img2.jpg.uri


+ 0 - 1
tests/Output/image/img2.png.uri

@@ -1 +0,0 @@
-data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMMAAADDCAIAAACwHTL9AAAABnRSTlMA/wD/AP83WBt9AAAE6UlEQVR4nO3dQY4rKRBF0a5W73/L1XMGWIgLmS6dM/tyOZ22n1CYH8DP7+/vP7Dt36dvgD9CkmhIEg1JovHf8O+fn587Lzyv9HduY7jy0qXmzw1/ncyvPL/ncx/dkuE2jEk0JImGJNGQJBpjxT04V2PuWCo5rxXg4evOLf0OuPYNGpNoSBINSaIhSTQ+VNyDnXL1nJ1CeOlSO3+85Fq1vnQbc8YkGpJEQ5JoSBKNtYr7mnnJea7Enj+6VIF+RWdIyJhEQ5JoSBINSaLx0or7XJvz3LV+lb/HmERDkmhIEg1JorFWcZ9rFNnpTd7plNh5R9e6Wa61wO8wJtGQJBqSREOSaHyouF8yb3uuHn9qMeS11732DRqTaEgSDUmiIUk0ft65H/e5CeKndiV85+ccMibRkCQakkRDkmh82I/7XC9EOG8bLmicu/YGd27jqcLfmERDkmhIEg1JolHOcZ9b0PgVPdHn9gEfXLvU0hs0JtGQJBqSREOSaJRz3IN39kQPwpt8ydYl576yOWMSDUmiIUk0JInGOMd9bkXfud6Pp2bP586ddbnkWv+4MYmGJNGQJBqSRGOc4762mfU55/qal95gOJt8bSeTnbsyJtGQJBqSREOSaGydgLNUkV1b0XetuyM8VfKp/VXCSxmTaEgSDUmiIUk01rpKtl7pWFvFta6SnSsPwnueO9c1ZI6bIySJhiTRkCQaa3PcYfPD0gvNr/xU0/eSp7YfudY/bkyiIUk0JImGJNG4d8r7tRaUa7sDDsKfFEueWrA6MCbRkCQakkRDkmh82I/7XBPCuYnpp5ou5rfxFfssWjnJ8ySJhiTRkCQaH+a4l0qwc5uEnCv8l1536a6eWu54bVJ7YEyiIUk0JImGJNEoV06eK/2eOonm2p7a4UrRp37ZGJNoSBINSaIhSTQ+7Md9rt14aUZ4x7W55qfWZIZXnr+F+aPGJBqSREOSaEgSjQ993B+efKtjeunKg3ce2PhUg/y5SxmTaEgSDUmiIUk0Dp7y/geK6JfMgO8497GruDlCkmhIEg1JorF1yvtSz8m5w8iXnnvtrMu5p2r5c9+vMYmGJNGQJBqSRGOsuM8tSvyKHajPVa/z5+587DvFe7jfoTGJhiTRkCQakkRjaz/u+R8Prm3pNxd2Ls+vPH/utd8fS7exRMXNEZJEQ5JoSBKND3PcO3Xiua31nvqVsPTc8K6eOgFnflcDYxINSaIhSTQkiUY5x73z3GvHuFzb2ePaXt7z515jTKIhSTQkiYYk0Vjr457/8U5NHfZEz1837AFfcm2e+tyaTH3c3CBJNCSJhiTR2NqPO/QVTReDa+0cg6f+42Gg4uYISaIhSTQkicbWHPeO+Rz3udu4dp76ud8Qc9cWuw6MSTQkiYYk0ZAkGmPFPTi3ScjSo0+dAT84t79KuAZ153V39h00JtGQJBqSREOSaHyouAfnlvDtlH4v2bTvWvvK4Nx+3E7A4QGSREOSaEgSjbWK+5ydXoiwj2LJtf0Ol648uPafB8YkGpJEQ5JoSBKNt1TcT02IX9tZcEe4JjP8sWLlJEdIEg1JoiFJNNYq7msbm4RH7VybXN5pgn7JEZRLjw6MSTQkiYYk0ZAkGh8q7mtbl7ykTL52QOU7N+BeurI5bo6QJBqSREOSaLxlP26+nTGJhiTRkCQakkTjf7A1wcz7nDmoAAAAAElFTkSuQmCC

+ 0 - 21
tests/Output/markup/str1.html

@@ -1,21 +0,0 @@
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b>
-<p><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
-<p><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i>
-<p><b></b><b></b><b></b><b></b><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b>
-<p><b></b><b></b><i></i><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b>
-<p><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b>
-<p><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b>
-<p><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b>

+ 0 - 124
tests/Output/markup/str1.svg

@@ -1,124 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="115" height="115" viewBox="0 0 115 115" style="background-color:#fff">
-<defs><style>.test{fill:#000} rect{shape-rendering:crispEdges}</style></defs>
-<rect x="5" y="5" width="35" height="5" class="test" />
-<rect x="55" y="5" width="15" height="5" class="test" />
-<rect x="75" y="5" width="35" height="5" class="test" />
-<rect x="5" y="10" width="5" height="5" class="test" />
-<rect x="35" y="10" width="5" height="5" class="test" />
-<rect x="45" y="10" width="15" height="5" class="test" />
-<rect x="75" y="10" width="5" height="5" class="test" />
-<rect x="105" y="10" width="5" height="5" class="test" />
-<rect x="5" y="15" width="5" height="5" class="test" />
-<rect x="15" y="15" width="15" height="5" class="test" />
-<rect x="35" y="15" width="5" height="5" class="test" />
-<rect x="50" y="15" width="5" height="5" class="test" />
-<rect x="65" y="15" width="5" height="5" class="test" />
-<rect x="75" y="15" width="5" height="5" class="test" />
-<rect x="85" y="15" width="15" height="5" class="test" />
-<rect x="105" y="15" width="5" height="5" class="test" />
-<rect x="5" y="20" width="5" height="5" class="test" />
-<rect x="15" y="20" width="15" height="5" class="test" />
-<rect x="35" y="20" width="5" height="5" class="test" />
-<rect x="50" y="20" width="15" height="5" class="test" />
-<rect x="75" y="20" width="5" height="5" class="test" />
-<rect x="85" y="20" width="15" height="5" class="test" />
-<rect x="105" y="20" width="5" height="5" class="test" />
-<rect x="5" y="25" width="5" height="5" class="test" />
-<rect x="15" y="25" width="15" height="5" class="test" />
-<rect x="35" y="25" width="5" height="5" class="test" />
-<rect x="45" y="25" width="10" height="5" class="test" />
-<rect x="60" y="25" width="10" height="5" class="test" />
-<rect x="75" y="25" width="5" height="5" class="test" />
-<rect x="85" y="25" width="15" height="5" class="test" />
-<rect x="105" y="25" width="5" height="5" class="test" />
-<rect x="5" y="30" width="5" height="5" class="test" />
-<rect x="35" y="30" width="5" height="5" class="test" />
-<rect x="50" y="30" width="5" height="5" class="test" />
-<rect x="60" y="30" width="5" height="5" class="test" />
-<rect x="75" y="30" width="5" height="5" class="test" />
-<rect x="105" y="30" width="5" height="5" class="test" />
-<rect x="5" y="35" width="35" height="5" class="test" />
-<rect x="45" y="35" width="5" height="5" class="test" />
-<rect x="55" y="35" width="5" height="5" class="test" />
-<rect x="65" y="35" width="5" height="5" class="test" />
-<rect x="75" y="35" width="35" height="5" class="test" />
-<rect x="50" y="40" width="10" height="5" class="test" />
-<rect x="5" y="45" width="5" height="5" class="test" />
-<rect x="15" y="45" width="5" height="5" class="test" />
-<rect x="25" y="45" width="5" height="5" class="test" />
-<rect x="35" y="45" width="5" height="5" class="test" />
-<rect x="50" y="45" width="5" height="5" class="test" />
-<rect x="65" y="45" width="5" height="5" class="test" />
-<rect x="85" y="45" width="5" height="5" class="test" />
-<rect x="100" y="45" width="5" height="5" class="test" />
-<rect x="5" y="50" width="25" height="5" class="test" />
-<rect x="50" y="50" width="15" height="5" class="test" />
-<rect x="70" y="50" width="5" height="5" class="test" />
-<rect x="80" y="50" width="5" height="5" class="test" />
-<rect x="95" y="50" width="15" height="5" class="test" />
-<rect x="5" y="55" width="10" height="5" class="test" />
-<rect x="20" y="55" width="25" height="5" class="test" />
-<rect x="55" y="55" width="10" height="5" class="test" />
-<rect x="70" y="55" width="15" height="5" class="test" />
-<rect x="90" y="55" width="5" height="5" class="test" />
-<rect x="100" y="55" width="10" height="5" class="test" />
-<rect x="5" y="60" width="10" height="5" class="test" />
-<rect x="25" y="60" width="10" height="5" class="test" />
-<rect x="45" y="60" width="5" height="5" class="test" />
-<rect x="55" y="60" width="20" height="5" class="test" />
-<rect x="80" y="60" width="10" height="5" class="test" />
-<rect x="100" y="60" width="10" height="5" class="test" />
-<rect x="5" y="65" width="5" height="5" class="test" />
-<rect x="15" y="65" width="5" height="5" class="test" />
-<rect x="30" y="65" width="15" height="5" class="test" />
-<rect x="50" y="65" width="5" height="5" class="test" />
-<rect x="60" y="65" width="5" height="5" class="test" />
-<rect x="70" y="65" width="15" height="5" class="test" />
-<rect x="100" y="65" width="10" height="5" class="test" />
-<rect x="45" y="70" width="5" height="5" class="test" />
-<rect x="75" y="70" width="10" height="5" class="test" />
-<rect x="95" y="70" width="15" height="5" class="test" />
-<rect x="5" y="75" width="35" height="5" class="test" />
-<rect x="50" y="75" width="10" height="5" class="test" />
-<rect x="65" y="75" width="5" height="5" class="test" />
-<rect x="85" y="75" width="10" height="5" class="test" />
-<rect x="100" y="75" width="10" height="5" class="test" />
-<rect x="5" y="80" width="5" height="5" class="test" />
-<rect x="35" y="80" width="5" height="5" class="test" />
-<rect x="50" y="80" width="10" height="5" class="test" />
-<rect x="75" y="80" width="15" height="5" class="test" />
-<rect x="100" y="80" width="10" height="5" class="test" />
-<rect x="5" y="85" width="5" height="5" class="test" />
-<rect x="15" y="85" width="15" height="5" class="test" />
-<rect x="35" y="85" width="5" height="5" class="test" />
-<rect x="45" y="85" width="15" height="5" class="test" />
-<rect x="65" y="85" width="5" height="5" class="test" />
-<rect x="75" y="85" width="5" height="5" class="test" />
-<rect x="85" y="85" width="5" height="5" class="test" />
-<rect x="100" y="85" width="10" height="5" class="test" />
-<rect x="5" y="90" width="5" height="5" class="test" />
-<rect x="15" y="90" width="15" height="5" class="test" />
-<rect x="35" y="90" width="5" height="5" class="test" />
-<rect x="50" y="90" width="5" height="5" class="test" />
-<rect x="60" y="90" width="5" height="5" class="test" />
-<rect x="70" y="90" width="5" height="5" class="test" />
-<rect x="85" y="90" width="10" height="5" class="test" />
-<rect x="100" y="90" width="5" height="5" class="test" />
-<rect x="5" y="95" width="5" height="5" class="test" />
-<rect x="15" y="95" width="15" height="5" class="test" />
-<rect x="35" y="95" width="5" height="5" class="test" />
-<rect x="45" y="95" width="10" height="5" class="test" />
-<rect x="60" y="95" width="5" height="5" class="test" />
-<rect x="70" y="95" width="20" height="5" class="test" />
-<rect x="105" y="95" width="5" height="5" class="test" />
-<rect x="5" y="100" width="5" height="5" class="test" />
-<rect x="35" y="100" width="5" height="5" class="test" />
-<rect x="60" y="100" width="15" height="5" class="test" />
-<rect x="100" y="100" width="5" height="5" class="test" />
-<rect x="5" y="105" width="35" height="5" class="test" />
-<rect x="45" y="105" width="5" height="5" class="test" />
-<rect x="55" y="105" width="10" height="5" class="test" />
-<rect x="70" y="105" width="10" height="5" class="test" />
-<rect x="85" y="105" width="5" height="5" class="test" />
-<rect x="100" y="105" width="10" height="5" class="test" />
-</svg>

+ 0 - 21
tests/Output/markup/str2.html

@@ -1,21 +0,0 @@
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b></p>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b></p>
-<p><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></p>
-<p><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i></p>
-<p><b></b><b></b><b></b><b></b><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b></p>
-<p><b></b><b></b><i></i><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b></p>
-<p><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b></p>
-<p><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b></p>
-<p><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b></p>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i></p>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b></p>

+ 0 - 366
tests/Output/markup/str2.svg

@@ -1,366 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="195" height="195" viewBox="0 0 195 195" style="background-color:#fff">
-<defs><style>.test{fill:#000} rect{shape-rendering:crispEdges}</style></defs>
-<rect x="5" y="5" width="35" height="5" class="test" />
-<rect x="50" y="5" width="20" height="5" class="test" />
-<rect x="75" y="5" width="15" height="5" class="test" />
-<rect x="95" y="5" width="5" height="5" class="test" />
-<rect x="125" y="5" width="5" height="5" class="test" />
-<rect x="135" y="5" width="10" height="5" class="test" />
-<rect x="155" y="5" width="35" height="5" class="test" />
-<rect x="5" y="10" width="5" height="5" class="test" />
-<rect x="35" y="10" width="5" height="5" class="test" />
-<rect x="50" y="10" width="10" height="5" class="test" />
-<rect x="70" y="10" width="5" height="5" class="test" />
-<rect x="80" y="10" width="10" height="5" class="test" />
-<rect x="100" y="10" width="5" height="5" class="test" />
-<rect x="110" y="10" width="5" height="5" class="test" />
-<rect x="120" y="10" width="10" height="5" class="test" />
-<rect x="135" y="10" width="15" height="5" class="test" />
-<rect x="155" y="10" width="5" height="5" class="test" />
-<rect x="185" y="10" width="5" height="5" class="test" />
-<rect x="5" y="15" width="5" height="5" class="test" />
-<rect x="15" y="15" width="15" height="5" class="test" />
-<rect x="35" y="15" width="5" height="5" class="test" />
-<rect x="50" y="15" width="10" height="5" class="test" />
-<rect x="65" y="15" width="5" height="5" class="test" />
-<rect x="75" y="15" width="5" height="5" class="test" />
-<rect x="90" y="15" width="5" height="5" class="test" />
-<rect x="100" y="15" width="10" height="5" class="test" />
-<rect x="120" y="15" width="10" height="5" class="test" />
-<rect x="140" y="15" width="10" height="5" class="test" />
-<rect x="155" y="15" width="5" height="5" class="test" />
-<rect x="165" y="15" width="15" height="5" class="test" />
-<rect x="185" y="15" width="5" height="5" class="test" />
-<rect x="5" y="20" width="5" height="5" class="test" />
-<rect x="15" y="20" width="15" height="5" class="test" />
-<rect x="35" y="20" width="5" height="5" class="test" />
-<rect x="55" y="20" width="10" height="5" class="test" />
-<rect x="70" y="20" width="5" height="5" class="test" />
-<rect x="85" y="20" width="15" height="5" class="test" />
-<rect x="115" y="20" width="10" height="5" class="test" />
-<rect x="135" y="20" width="15" height="5" class="test" />
-<rect x="155" y="20" width="5" height="5" class="test" />
-<rect x="165" y="20" width="15" height="5" class="test" />
-<rect x="185" y="20" width="5" height="5" class="test" />
-<rect x="5" y="25" width="5" height="5" class="test" />
-<rect x="15" y="25" width="15" height="5" class="test" />
-<rect x="35" y="25" width="5" height="5" class="test" />
-<rect x="50" y="25" width="5" height="5" class="test" />
-<rect x="60" y="25" width="5" height="5" class="test" />
-<rect x="85" y="25" width="5" height="5" class="test" />
-<rect x="100" y="25" width="5" height="5" class="test" />
-<rect x="110" y="25" width="15" height="5" class="test" />
-<rect x="135" y="25" width="15" height="5" class="test" />
-<rect x="155" y="25" width="5" height="5" class="test" />
-<rect x="165" y="25" width="15" height="5" class="test" />
-<rect x="185" y="25" width="5" height="5" class="test" />
-<rect x="5" y="30" width="5" height="5" class="test" />
-<rect x="35" y="30" width="5" height="5" class="test" />
-<rect x="45" y="30" width="5" height="5" class="test" />
-<rect x="65" y="30" width="15" height="5" class="test" />
-<rect x="100" y="30" width="10" height="5" class="test" />
-<rect x="115" y="30" width="5" height="5" class="test" />
-<rect x="130" y="30" width="5" height="5" class="test" />
-<rect x="140" y="30" width="10" height="5" class="test" />
-<rect x="155" y="30" width="5" height="5" class="test" />
-<rect x="185" y="30" width="5" height="5" class="test" />
-<rect x="5" y="35" width="35" height="5" class="test" />
-<rect x="45" y="35" width="5" height="5" class="test" />
-<rect x="55" y="35" width="5" height="5" class="test" />
-<rect x="65" y="35" width="5" height="5" class="test" />
-<rect x="75" y="35" width="5" height="5" class="test" />
-<rect x="85" y="35" width="5" height="5" class="test" />
-<rect x="95" y="35" width="5" height="5" class="test" />
-<rect x="105" y="35" width="5" height="5" class="test" />
-<rect x="115" y="35" width="5" height="5" class="test" />
-<rect x="125" y="35" width="5" height="5" class="test" />
-<rect x="135" y="35" width="5" height="5" class="test" />
-<rect x="145" y="35" width="5" height="5" class="test" />
-<rect x="155" y="35" width="35" height="5" class="test" />
-<rect x="50" y="40" width="5" height="5" class="test" />
-<rect x="65" y="40" width="5" height="5" class="test" />
-<rect x="75" y="40" width="5" height="5" class="test" />
-<rect x="90" y="40" width="20" height="5" class="test" />
-<rect x="115" y="40" width="5" height="5" class="test" />
-<rect x="5" y="45" width="5" height="5" class="test" />
-<rect x="20" y="45" width="5" height="5" class="test" />
-<rect x="30" y="45" width="10" height="5" class="test" />
-<rect x="45" y="45" width="10" height="5" class="test" />
-<rect x="60" y="45" width="5" height="5" class="test" />
-<rect x="75" y="45" width="25" height="5" class="test" />
-<rect x="105" y="45" width="5" height="5" class="test" />
-<rect x="120" y="45" width="25" height="5" class="test" />
-<rect x="150" y="45" width="5" height="5" class="test" />
-<rect x="160" y="45" width="5" height="5" class="test" />
-<rect x="25" y="50" width="10" height="5" class="test" />
-<rect x="55" y="50" width="5" height="5" class="test" />
-<rect x="70" y="50" width="5" height="5" class="test" />
-<rect x="80" y="50" width="25" height="5" class="test" />
-<rect x="110" y="50" width="5" height="5" class="test" />
-<rect x="120" y="50" width="15" height="5" class="test" />
-<rect x="145" y="50" width="15" height="5" class="test" />
-<rect x="170" y="50" width="10" height="5" class="test" />
-<rect x="185" y="50" width="5" height="5" class="test" />
-<rect x="5" y="55" width="5" height="5" class="test" />
-<rect x="20" y="55" width="10" height="5" class="test" />
-<rect x="35" y="55" width="5" height="5" class="test" />
-<rect x="45" y="55" width="15" height="5" class="test" />
-<rect x="65" y="55" width="5" height="5" class="test" />
-<rect x="75" y="55" width="10" height="5" class="test" />
-<rect x="90" y="55" width="5" height="5" class="test" />
-<rect x="100" y="55" width="5" height="5" class="test" />
-<rect x="115" y="55" width="20" height="5" class="test" />
-<rect x="145" y="55" width="10" height="5" class="test" />
-<rect x="165" y="55" width="10" height="5" class="test" />
-<rect x="180" y="55" width="10" height="5" class="test" />
-<rect x="5" y="60" width="5" height="5" class="test" />
-<rect x="15" y="60" width="5" height="5" class="test" />
-<rect x="25" y="60" width="10" height="5" class="test" />
-<rect x="50" y="60" width="15" height="5" class="test" />
-<rect x="85" y="60" width="5" height="5" class="test" />
-<rect x="100" y="60" width="15" height="5" class="test" />
-<rect x="120" y="60" width="15" height="5" class="test" />
-<rect x="140" y="60" width="20" height="5" class="test" />
-<rect x="15" y="65" width="5" height="5" class="test" />
-<rect x="35" y="65" width="30" height="5" class="test" />
-<rect x="70" y="65" width="5" height="5" class="test" />
-<rect x="80" y="65" width="5" height="5" class="test" />
-<rect x="110" y="65" width="15" height="5" class="test" />
-<rect x="130" y="65" width="5" height="5" class="test" />
-<rect x="145" y="65" width="5" height="5" class="test" />
-<rect x="155" y="65" width="5" height="5" class="test" />
-<rect x="170" y="65" width="5" height="5" class="test" />
-<rect x="180" y="65" width="10" height="5" class="test" />
-<rect x="15" y="70" width="15" height="5" class="test" />
-<rect x="40" y="70" width="5" height="5" class="test" />
-<rect x="75" y="70" width="10" height="5" class="test" />
-<rect x="105" y="70" width="10" height="5" class="test" />
-<rect x="125" y="70" width="5" height="5" class="test" />
-<rect x="135" y="70" width="5" height="5" class="test" />
-<rect x="145" y="70" width="15" height="5" class="test" />
-<rect x="170" y="70" width="20" height="5" class="test" />
-<rect x="5" y="75" width="5" height="5" class="test" />
-<rect x="35" y="75" width="15" height="5" class="test" />
-<rect x="55" y="75" width="10" height="5" class="test" />
-<rect x="85" y="75" width="5" height="5" class="test" />
-<rect x="115" y="75" width="10" height="5" class="test" />
-<rect x="145" y="75" width="15" height="5" class="test" />
-<rect x="165" y="75" width="5" height="5" class="test" />
-<rect x="175" y="75" width="5" height="5" class="test" />
-<rect x="185" y="75" width="5" height="5" class="test" />
-<rect x="5" y="80" width="5" height="5" class="test" />
-<rect x="15" y="80" width="10" height="5" class="test" />
-<rect x="30" y="80" width="5" height="5" class="test" />
-<rect x="50" y="80" width="5" height="5" class="test" />
-<rect x="60" y="80" width="20" height="5" class="test" />
-<rect x="85" y="80" width="5" height="5" class="test" />
-<rect x="100" y="80" width="20" height="5" class="test" />
-<rect x="130" y="80" width="15" height="5" class="test" />
-<rect x="155" y="80" width="15" height="5" class="test" />
-<rect x="180" y="80" width="10" height="5" class="test" />
-<rect x="5" y="85" width="5" height="5" class="test" />
-<rect x="30" y="85" width="15" height="5" class="test" />
-<rect x="55" y="85" width="5" height="5" class="test" />
-<rect x="70" y="85" width="5" height="5" class="test" />
-<rect x="80" y="85" width="10" height="5" class="test" />
-<rect x="100" y="85" width="10" height="5" class="test" />
-<rect x="125" y="85" width="5" height="5" class="test" />
-<rect x="150" y="85" width="10" height="5" class="test" />
-<rect x="175" y="85" width="15" height="5" class="test" />
-<rect x="10" y="90" width="5" height="5" class="test" />
-<rect x="20" y="90" width="5" height="5" class="test" />
-<rect x="30" y="90" width="5" height="5" class="test" />
-<rect x="40" y="90" width="15" height="5" class="test" />
-<rect x="70" y="90" width="15" height="5" class="test" />
-<rect x="90" y="90" width="5" height="5" class="test" />
-<rect x="100" y="90" width="5" height="5" class="test" />
-<rect x="115" y="90" width="5" height="5" class="test" />
-<rect x="130" y="90" width="10" height="5" class="test" />
-<rect x="145" y="90" width="5" height="5" class="test" />
-<rect x="155" y="90" width="20" height="5" class="test" />
-<rect x="180" y="90" width="10" height="5" class="test" />
-<rect x="15" y="95" width="15" height="5" class="test" />
-<rect x="35" y="95" width="5" height="5" class="test" />
-<rect x="55" y="95" width="5" height="5" class="test" />
-<rect x="65" y="95" width="5" height="5" class="test" />
-<rect x="75" y="95" width="5" height="5" class="test" />
-<rect x="90" y="95" width="20" height="5" class="test" />
-<rect x="125" y="95" width="15" height="5" class="test" />
-<rect x="145" y="95" width="10" height="5" class="test" />
-<rect x="170" y="95" width="5" height="5" class="test" />
-<rect x="180" y="95" width="10" height="5" class="test" />
-<rect x="5" y="100" width="5" height="5" class="test" />
-<rect x="25" y="100" width="5" height="5" class="test" />
-<rect x="45" y="100" width="10" height="5" class="test" />
-<rect x="60" y="100" width="10" height="5" class="test" />
-<rect x="75" y="100" width="20" height="5" class="test" />
-<rect x="100" y="100" width="15" height="5" class="test" />
-<rect x="120" y="100" width="10" height="5" class="test" />
-<rect x="135" y="100" width="10" height="5" class="test" />
-<rect x="155" y="100" width="5" height="5" class="test" />
-<rect x="175" y="100" width="5" height="5" class="test" />
-<rect x="185" y="100" width="5" height="5" class="test" />
-<rect x="10" y="105" width="5" height="5" class="test" />
-<rect x="25" y="105" width="5" height="5" class="test" />
-<rect x="35" y="105" width="5" height="5" class="test" />
-<rect x="60" y="105" width="10" height="5" class="test" />
-<rect x="85" y="105" width="10" height="5" class="test" />
-<rect x="100" y="105" width="10" height="5" class="test" />
-<rect x="120" y="105" width="25" height="5" class="test" />
-<rect x="155" y="105" width="20" height="5" class="test" />
-<rect x="20" y="110" width="15" height="5" class="test" />
-<rect x="40" y="110" width="5" height="5" class="test" />
-<rect x="50" y="110" width="15" height="5" class="test" />
-<rect x="75" y="110" width="20" height="5" class="test" />
-<rect x="125" y="110" width="5" height="5" class="test" />
-<rect x="135" y="110" width="5" height="5" class="test" />
-<rect x="145" y="110" width="5" height="5" class="test" />
-<rect x="155" y="110" width="10" height="5" class="test" />
-<rect x="170" y="110" width="5" height="5" class="test" />
-<rect x="180" y="110" width="10" height="5" class="test" />
-<rect x="5" y="115" width="5" height="5" class="test" />
-<rect x="15" y="115" width="5" height="5" class="test" />
-<rect x="25" y="115" width="5" height="5" class="test" />
-<rect x="35" y="115" width="15" height="5" class="test" />
-<rect x="60" y="115" width="20" height="5" class="test" />
-<rect x="95" y="115" width="25" height="5" class="test" />
-<rect x="160" y="115" width="15" height="5" class="test" />
-<rect x="185" y="115" width="5" height="5" class="test" />
-<rect x="15" y="120" width="10" height="5" class="test" />
-<rect x="40" y="120" width="10" height="5" class="test" />
-<rect x="55" y="120" width="5" height="5" class="test" />
-<rect x="75" y="120" width="10" height="5" class="test" />
-<rect x="95" y="120" width="15" height="5" class="test" />
-<rect x="125" y="120" width="25" height="5" class="test" />
-<rect x="160" y="120" width="5" height="5" class="test" />
-<rect x="180" y="120" width="5" height="5" class="test" />
-<rect x="5" y="125" width="10" height="5" class="test" />
-<rect x="20" y="125" width="5" height="5" class="test" />
-<rect x="35" y="125" width="5" height="5" class="test" />
-<rect x="55" y="125" width="15" height="5" class="test" />
-<rect x="75" y="125" width="15" height="5" class="test" />
-<rect x="95" y="125" width="10" height="5" class="test" />
-<rect x="130" y="125" width="10" height="5" class="test" />
-<rect x="145" y="125" width="20" height="5" class="test" />
-<rect x="170" y="125" width="5" height="5" class="test" />
-<rect x="180" y="125" width="10" height="5" class="test" />
-<rect x="10" y="130" width="5" height="5" class="test" />
-<rect x="25" y="130" width="10" height="5" class="test" />
-<rect x="40" y="130" width="5" height="5" class="test" />
-<rect x="50" y="130" width="5" height="5" class="test" />
-<rect x="80" y="130" width="5" height="5" class="test" />
-<rect x="90" y="130" width="10" height="5" class="test" />
-<rect x="110" y="130" width="10" height="5" class="test" />
-<rect x="125" y="130" width="5" height="5" class="test" />
-<rect x="150" y="130" width="15" height="5" class="test" />
-<rect x="170" y="130" width="20" height="5" class="test" />
-<rect x="5" y="135" width="5" height="5" class="test" />
-<rect x="15" y="135" width="5" height="5" class="test" />
-<rect x="25" y="135" width="5" height="5" class="test" />
-<rect x="35" y="135" width="10" height="5" class="test" />
-<rect x="55" y="135" width="5" height="5" class="test" />
-<rect x="70" y="135" width="5" height="5" class="test" />
-<rect x="80" y="135" width="15" height="5" class="test" />
-<rect x="105" y="135" width="20" height="5" class="test" />
-<rect x="135" y="135" width="5" height="5" class="test" />
-<rect x="145" y="135" width="5" height="5" class="test" />
-<rect x="155" y="135" width="10" height="5" class="test" />
-<rect x="170" y="135" width="5" height="5" class="test" />
-<rect x="185" y="135" width="5" height="5" class="test" />
-<rect x="10" y="140" width="5" height="5" class="test" />
-<rect x="30" y="140" width="5" height="5" class="test" />
-<rect x="45" y="140" width="5" height="5" class="test" />
-<rect x="65" y="140" width="15" height="5" class="test" />
-<rect x="85" y="140" width="10" height="5" class="test" />
-<rect x="100" y="140" width="5" height="5" class="test" />
-<rect x="110" y="140" width="5" height="5" class="test" />
-<rect x="120" y="140" width="15" height="5" class="test" />
-<rect x="140" y="140" width="15" height="5" class="test" />
-<rect x="5" y="145" width="10" height="5" class="test" />
-<rect x="20" y="145" width="5" height="5" class="test" />
-<rect x="30" y="145" width="10" height="5" class="test" />
-<rect x="75" y="145" width="15" height="5" class="test" />
-<rect x="100" y="145" width="5" height="5" class="test" />
-<rect x="110" y="145" width="5" height="5" class="test" />
-<rect x="125" y="145" width="5" height="5" class="test" />
-<rect x="135" y="145" width="45" height="5" class="test" />
-<rect x="185" y="145" width="5" height="5" class="test" />
-<rect x="45" y="150" width="5" height="5" class="test" />
-<rect x="60" y="150" width="5" height="5" class="test" />
-<rect x="70" y="150" width="5" height="5" class="test" />
-<rect x="80" y="150" width="25" height="5" class="test" />
-<rect x="110" y="150" width="20" height="5" class="test" />
-<rect x="145" y="150" width="5" height="5" class="test" />
-<rect x="165" y="150" width="5" height="5" class="test" />
-<rect x="180" y="150" width="10" height="5" class="test" />
-<rect x="5" y="155" width="35" height="5" class="test" />
-<rect x="50" y="155" width="15" height="5" class="test" />
-<rect x="70" y="155" width="5" height="5" class="test" />
-<rect x="85" y="155" width="10" height="5" class="test" />
-<rect x="105" y="155" width="5" height="5" class="test" />
-<rect x="115" y="155" width="5" height="5" class="test" />
-<rect x="130" y="155" width="5" height="5" class="test" />
-<rect x="140" y="155" width="10" height="5" class="test" />
-<rect x="155" y="155" width="5" height="5" class="test" />
-<rect x="165" y="155" width="15" height="5" class="test" />
-<rect x="185" y="155" width="5" height="5" class="test" />
-<rect x="5" y="160" width="5" height="5" class="test" />
-<rect x="35" y="160" width="5" height="5" class="test" />
-<rect x="45" y="160" width="10" height="5" class="test" />
-<rect x="70" y="160" width="5" height="5" class="test" />
-<rect x="100" y="160" width="5" height="5" class="test" />
-<rect x="110" y="160" width="5" height="5" class="test" />
-<rect x="145" y="160" width="5" height="5" class="test" />
-<rect x="165" y="160" width="5" height="5" class="test" />
-<rect x="175" y="160" width="15" height="5" class="test" />
-<rect x="5" y="165" width="5" height="5" class="test" />
-<rect x="15" y="165" width="15" height="5" class="test" />
-<rect x="35" y="165" width="5" height="5" class="test" />
-<rect x="50" y="165" width="15" height="5" class="test" />
-<rect x="70" y="165" width="15" height="5" class="test" />
-<rect x="90" y="165" width="5" height="5" class="test" />
-<rect x="100" y="165" width="5" height="5" class="test" />
-<rect x="115" y="165" width="25" height="5" class="test" />
-<rect x="145" y="165" width="30" height="5" class="test" />
-<rect x="180" y="165" width="5" height="5" class="test" />
-<rect x="5" y="170" width="5" height="5" class="test" />
-<rect x="15" y="170" width="15" height="5" class="test" />
-<rect x="35" y="170" width="5" height="5" class="test" />
-<rect x="45" y="170" width="5" height="5" class="test" />
-<rect x="60" y="170" width="10" height="5" class="test" />
-<rect x="80" y="170" width="5" height="5" class="test" />
-<rect x="90" y="170" width="5" height="5" class="test" />
-<rect x="100" y="170" width="20" height="5" class="test" />
-<rect x="125" y="170" width="5" height="5" class="test" />
-<rect x="145" y="170" width="5" height="5" class="test" />
-<rect x="160" y="170" width="10" height="5" class="test" />
-<rect x="180" y="170" width="5" height="5" class="test" />
-<rect x="5" y="175" width="5" height="5" class="test" />
-<rect x="15" y="175" width="15" height="5" class="test" />
-<rect x="35" y="175" width="5" height="5" class="test" />
-<rect x="65" y="175" width="15" height="5" class="test" />
-<rect x="85" y="175" width="10" height="5" class="test" />
-<rect x="105" y="175" width="10" height="5" class="test" />
-<rect x="125" y="175" width="5" height="5" class="test" />
-<rect x="140" y="175" width="10" height="5" class="test" />
-<rect x="165" y="175" width="10" height="5" class="test" />
-<rect x="180" y="175" width="10" height="5" class="test" />
-<rect x="5" y="180" width="5" height="5" class="test" />
-<rect x="35" y="180" width="5" height="5" class="test" />
-<rect x="50" y="180" width="5" height="5" class="test" />
-<rect x="70" y="180" width="5" height="5" class="test" />
-<rect x="80" y="180" width="5" height="5" class="test" />
-<rect x="110" y="180" width="25" height="5" class="test" />
-<rect x="140" y="180" width="10" height="5" class="test" />
-<rect x="155" y="180" width="15" height="5" class="test" />
-<rect x="5" y="185" width="35" height="5" class="test" />
-<rect x="45" y="185" width="5" height="5" class="test" />
-<rect x="55" y="185" width="5" height="5" class="test" />
-<rect x="70" y="185" width="5" height="5" class="test" />
-<rect x="85" y="185" width="5" height="5" class="test" />
-<rect x="100" y="185" width="10" height="5" class="test" />
-<rect x="120" y="185" width="5" height="5" class="test" />
-<rect x="130" y="185" width="5" height="5" class="test" />
-<rect x="140" y="185" width="30" height="5" class="test" />
-<rect x="180" y="185" width="10" height="5" class="test" />
-</svg>

+ 0 - 37
tests/Output/markup/str3.html

@@ -1,37 +0,0 @@
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b>
-<p><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
-<p><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i>
-<p><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b>
-<p><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><b></b>
-<p><b></b><i></i><b></b><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i>
-<p><i></i><i></i><b></b><i></i><i></i><i></i><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b>
-<p><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b>
-<p><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b>
-<p><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><b></b>
-<p><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><i></i><b></b><i></i><b></b><b></b>
-<p><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><b></b><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b>
-<p><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i>
-<p><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b>
-<p><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b>
-<p><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i>
-<p><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b>
-<p><i></i><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b>
-<p><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b>
-<p><i></i><b></b><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
-<p><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b>
-<p><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b><b></b><b></b>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b>

+ 0 - 37
tests/Output/markup/str4.html

@@ -1,37 +0,0 @@
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b></p>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b></p>
-<p><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></p>
-<p><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i></p>
-<p><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b></p>
-<p><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><b></b></p>
-<p><b></b><i></i><b></b><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i></p>
-<p><i></i><i></i><b></b><i></i><i></i><i></i><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b></p>
-<p><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b></p>
-<p><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b></p>
-<p><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><b></b></p>
-<p><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><i></i><b></b><i></i><b></b><b></b></p>
-<p><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><b></b><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b></p>
-<p><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i></p>
-<p><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b></p>
-<p><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b></p>
-<p><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i></p>
-<p><b></b><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><b></b></p>
-<p><i></i><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b></p>
-<p><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><i></i><b></b></p>
-<p><i></i><b></b><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i></p>
-<p><b></b><b></b><i></i><b></b><i></i><b></b><b></b><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b></p>
-<p><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b></p>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><b></b><b></b><i></i><b></b><i></i><b></b><b></b><b></b><i></i><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><b></b><i></i><i></i><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b><b></b><b></b></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i></p>
-<p><b></b><i></i><b></b><b></b><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><i></i><b></b><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><i></i><b></b><b></b><i></i><b></b><b></b></p>
-<p><b></b><i></i><i></i><i></i><i></i><i></i><b></b><i></i><i></i><b></b><i></i><i></i><i></i><b></b><i></i><b></b><i></i><i></i><i></i><i></i><i></i><b></b><b></b><b></b><b></b><b></b><i></i><b></b><b></b><i></i><b></b><b></b><b></b><i></i><i></i><i></i><i></i></p>
-<p><b></b><b></b><b></b><b></b><b></b><b></b><b></b><i></i><b></b><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><i></i><i></i><b></b><b></b><i></i><i></i><b></b><i></i><b></b><i></i><b></b><b></b><b></b><b></b><b></b><b></b><i></i><i></i><b></b><b></b></p>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
tests/Output/string/str1.json


+ 0 - 21
tests/Output/string/str1.txt

@@ -1,21 +0,0 @@
-🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕🔴🔴🔴⭕🔴🔴🔴🔴🔴🔴🔴
-🔴⭕⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕⭕🔴⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴🔴⭕⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴⭕🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴⭕⭕⭕⭕⭕🔴
-🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴
-⭕⭕⭕⭕⭕⭕⭕⭕⭕🔴🔴⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
-🔴⭕🔴⭕🔴⭕🔴⭕⭕🔴⭕⭕🔴⭕⭕⭕🔴⭕⭕🔴⭕
-🔴🔴🔴🔴🔴⭕⭕⭕⭕🔴🔴🔴⭕🔴⭕🔴⭕⭕🔴🔴🔴
-🔴🔴⭕🔴🔴🔴🔴🔴⭕⭕🔴🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴
-🔴🔴⭕⭕🔴🔴⭕⭕🔴⭕🔴🔴🔴🔴⭕🔴🔴⭕⭕🔴🔴
-🔴⭕🔴⭕⭕🔴🔴🔴⭕🔴⭕🔴⭕🔴🔴🔴⭕⭕⭕🔴🔴
-⭕⭕⭕⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴🔴⭕⭕🔴🔴🔴
-🔴🔴🔴🔴🔴🔴🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕🔴🔴⭕🔴🔴
-🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴🔴⭕⭕⭕🔴🔴🔴⭕⭕🔴🔴
-🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕⭕🔴🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕🔴⭕⭕🔴🔴⭕🔴⭕
-🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴⭕🔴⭕🔴🔴🔴🔴⭕⭕⭕🔴
-🔴⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕🔴🔴🔴⭕⭕⭕⭕⭕🔴⭕
-🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴🔴⭕🔴🔴⭕🔴⭕⭕🔴🔴

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
tests/Output/string/str2.json


+ 0 - 37
tests/Output/string/str2.txt

@@ -1,37 +0,0 @@
-🔴🔴🔴🔴🔴🔴🔴⭕⭕🔴🔴🔴🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕⭕🔴⭕🔴🔴⭕⭕🔴🔴🔴🔴🔴🔴🔴
-🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴🔴⭕⭕🔴⭕🔴🔴⭕⭕🔴⭕🔴⭕🔴🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴⭕🔴⭕🔴⭕⭕🔴⭕🔴🔴⭕⭕🔴🔴⭕⭕🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕🔴🔴⭕🔴⭕⭕🔴🔴🔴⭕⭕⭕🔴🔴⭕⭕🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕⭕🔴⭕⭕🔴⭕🔴🔴🔴⭕⭕🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕⭕⭕⭕⭕🔴⭕🔴⭕⭕⭕🔴🔴🔴⭕⭕⭕⭕🔴🔴⭕🔴⭕⭕🔴⭕🔴🔴⭕🔴⭕⭕⭕⭕⭕🔴
-🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴
-⭕⭕⭕⭕⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴🔴🔴🔴⭕🔴⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕
-🔴⭕⭕🔴⭕🔴🔴⭕🔴🔴⭕🔴⭕⭕🔴🔴🔴🔴🔴⭕🔴⭕⭕🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕⭕⭕⭕⭕
-⭕⭕⭕⭕🔴🔴⭕⭕⭕⭕🔴⭕⭕🔴⭕🔴🔴🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕⭕🔴🔴🔴⭕⭕🔴🔴⭕🔴
-🔴⭕⭕🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴⭕🔴⭕🔴⭕⭕🔴🔴🔴🔴⭕⭕🔴🔴⭕⭕🔴🔴⭕🔴🔴
-🔴⭕🔴⭕🔴🔴⭕⭕⭕🔴🔴🔴⭕⭕⭕⭕🔴⭕⭕🔴🔴🔴⭕🔴🔴🔴⭕🔴🔴🔴🔴⭕⭕⭕⭕⭕⭕
-⭕⭕🔴⭕⭕⭕🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕⭕⭕⭕⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴⭕🔴🔴
-⭕⭕🔴🔴🔴⭕⭕🔴⭕⭕⭕⭕⭕⭕🔴🔴⭕⭕⭕⭕🔴🔴⭕⭕🔴⭕🔴⭕🔴🔴🔴⭕⭕🔴🔴🔴🔴
-🔴⭕⭕⭕⭕⭕🔴🔴🔴⭕🔴🔴⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴🔴⭕⭕⭕⭕🔴🔴🔴⭕🔴⭕🔴⭕🔴
-🔴⭕🔴🔴⭕🔴⭕⭕⭕🔴⭕🔴🔴🔴🔴⭕🔴⭕⭕🔴🔴🔴🔴⭕⭕🔴🔴🔴⭕⭕🔴🔴🔴⭕⭕🔴🔴
-🔴⭕⭕⭕⭕🔴🔴🔴⭕⭕🔴⭕⭕🔴⭕🔴🔴⭕⭕🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕🔴🔴⭕⭕⭕🔴🔴🔴
-⭕🔴⭕🔴⭕🔴⭕🔴🔴🔴⭕⭕⭕🔴🔴🔴⭕🔴⭕🔴⭕⭕🔴⭕⭕🔴🔴⭕🔴⭕🔴🔴🔴🔴⭕🔴🔴
-⭕⭕🔴🔴🔴⭕🔴⭕⭕⭕🔴⭕🔴⭕🔴⭕⭕🔴🔴🔴🔴⭕⭕⭕🔴🔴🔴⭕🔴🔴⭕⭕⭕🔴⭕🔴🔴
-🔴⭕⭕⭕🔴⭕⭕⭕🔴🔴⭕🔴🔴⭕🔴🔴🔴🔴⭕🔴🔴🔴⭕🔴🔴⭕🔴🔴⭕⭕🔴⭕⭕⭕🔴⭕🔴
-⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕⭕🔴🔴⭕⭕⭕🔴🔴⭕🔴🔴⭕⭕🔴🔴🔴🔴🔴⭕⭕🔴🔴🔴🔴⭕⭕⭕
-⭕⭕⭕🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕⭕🔴🔴🔴🔴⭕⭕⭕⭕⭕⭕🔴⭕🔴⭕🔴⭕🔴🔴⭕🔴⭕🔴🔴
-🔴⭕🔴⭕🔴⭕🔴🔴🔴⭕⭕🔴🔴🔴🔴⭕⭕⭕🔴🔴🔴🔴🔴⭕⭕⭕⭕⭕⭕⭕⭕🔴🔴🔴⭕⭕🔴
-⭕⭕🔴🔴⭕⭕⭕🔴🔴⭕🔴⭕⭕⭕🔴🔴⭕⭕🔴🔴🔴⭕⭕⭕🔴🔴🔴🔴🔴⭕⭕🔴⭕⭕⭕🔴⭕
-🔴🔴⭕🔴⭕⭕🔴⭕⭕⭕🔴🔴🔴⭕🔴🔴🔴⭕🔴🔴⭕⭕⭕⭕⭕🔴🔴⭕🔴🔴🔴🔴⭕🔴⭕🔴🔴
-⭕🔴⭕⭕🔴🔴⭕🔴⭕🔴⭕⭕⭕⭕⭕🔴⭕🔴🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕⭕🔴🔴🔴⭕🔴🔴🔴🔴
-🔴⭕🔴⭕🔴⭕🔴🔴⭕⭕🔴⭕⭕🔴⭕🔴🔴🔴⭕⭕🔴🔴🔴🔴⭕⭕🔴⭕🔴⭕🔴🔴⭕🔴⭕⭕🔴
-⭕🔴⭕⭕⭕🔴⭕⭕🔴⭕⭕⭕🔴🔴🔴⭕🔴🔴⭕🔴⭕🔴⭕🔴🔴🔴⭕🔴🔴🔴⭕⭕⭕⭕⭕⭕⭕
-🔴🔴⭕🔴⭕🔴🔴⭕⭕⭕⭕⭕⭕⭕🔴🔴🔴⭕⭕🔴⭕🔴⭕⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴🔴🔴⭕🔴
-⭕⭕⭕⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴⭕🔴🔴🔴🔴⭕⭕⭕🔴⭕⭕⭕🔴⭕⭕🔴🔴
-🔴🔴🔴🔴🔴🔴🔴⭕⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴⭕⭕🔴⭕🔴⭕⭕🔴⭕🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕⭕⭕⭕⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕🔴⭕⭕⭕⭕⭕⭕🔴⭕⭕⭕🔴⭕🔴🔴🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴🔴⭕🔴🔴🔴⭕🔴⭕🔴⭕⭕🔴🔴🔴🔴🔴⭕🔴🔴🔴🔴🔴🔴⭕🔴⭕
-🔴⭕🔴🔴🔴⭕🔴⭕🔴⭕⭕🔴🔴⭕⭕🔴⭕🔴⭕🔴🔴🔴🔴⭕🔴⭕⭕⭕🔴⭕⭕🔴🔴⭕⭕🔴⭕
-🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕⭕🔴🔴🔴⭕🔴🔴⭕⭕🔴🔴⭕⭕🔴⭕⭕🔴🔴⭕⭕⭕🔴🔴⭕🔴🔴
-🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕⭕⭕🔴⭕🔴⭕⭕⭕⭕⭕🔴🔴🔴🔴🔴⭕🔴🔴⭕🔴🔴🔴⭕⭕⭕⭕
-🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕⭕🔴⭕⭕🔴⭕⭕🔴🔴⭕⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴⭕⭕🔴🔴

+ 0 - 49
tests/Output/string/str3.txt

@@ -1,49 +0,0 @@
-🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕⭕🔴⭕⭕🔴⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴⭕🔴🔴🔴🔴⭕⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴
-🔴⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕⭕🔴⭕🔴🔴⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕🔴⭕🔴⭕🔴⭕⭕⭕🔴🔴🔴🔴🔴⭕🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕⭕⭕⭕🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴⭕⭕🔴⭕🔴⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕🔴⭕⭕⭕🔴⭕⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴🔴⭕🔴⭕🔴🔴🔴🔴🔴⭕🔴🔴🔴🔴🔴🔴⭕🔴🔴⭕🔴🔴⭕🔴⭕🔴⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕⭕⭕⭕⭕🔴⭕🔴⭕⭕⭕⭕🔴🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕⭕⭕🔴🔴🔴⭕🔴🔴⭕🔴⭕⭕⭕⭕🔴⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴
-🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴
-⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕🔴⭕🔴🔴🔴⭕⭕🔴🔴⭕⭕⭕🔴🔴⭕⭕⭕🔴🔴⭕⭕🔴🔴⭕⭕🔴⭕⭕⭕⭕⭕⭕⭕⭕⭕
-🔴⭕⭕🔴⭕🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴🔴🔴🔴🔴🔴🔴🔴🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕🔴🔴⭕🔴⭕⭕⭕⭕⭕
-⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕🔴🔴⭕⭕🔴🔴⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕🔴🔴⭕🔴🔴⭕
-🔴⭕⭕⭕🔴🔴🔴🔴🔴⭕🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕🔴🔴⭕🔴⭕⭕⭕🔴⭕🔴🔴⭕🔴🔴⭕🔴⭕🔴🔴
-🔴🔴🔴⭕⭕🔴⭕⭕🔴⭕⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕⭕⭕⭕🔴🔴⭕🔴⭕⭕🔴⭕🔴🔴⭕🔴⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕⭕⭕🔴
-🔴⭕🔴⭕⭕⭕🔴⭕🔴⭕🔴🔴⭕🔴⭕⭕⭕⭕🔴⭕🔴🔴🔴🔴🔴⭕⭕🔴🔴🔴🔴🔴🔴🔴⭕🔴🔴⭕⭕🔴⭕🔴⭕🔴🔴⭕⭕🔴🔴
-🔴🔴🔴🔴🔴⭕⭕🔴🔴🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴🔴⭕⭕🔴🔴🔴⭕🔴🔴⭕⭕⭕🔴⭕🔴🔴⭕🔴⭕⭕🔴⭕⭕🔴⭕🔴🔴⭕🔴
-🔴⭕⭕⭕🔴⭕🔴⭕⭕🔴🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕🔴⭕⭕🔴⭕⭕🔴🔴🔴⭕⭕⭕🔴🔴🔴🔴⭕⭕⭕⭕⭕🔴🔴🔴🔴🔴⭕⭕🔴
-🔴⭕⭕🔴🔴🔴⭕⭕🔴🔴🔴🔴⭕🔴🔴🔴⭕🔴🔴⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕⭕🔴🔴⭕⭕⭕⭕⭕⭕🔴⭕🔴🔴🔴🔴🔴⭕⭕⭕⭕
-⭕⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴🔴⭕⭕🔴🔴🔴⭕🔴🔴⭕⭕⭕🔴🔴⭕🔴🔴⭕🔴🔴🔴🔴🔴⭕🔴🔴⭕🔴🔴
-🔴⭕🔴⭕🔴⭕⭕⭕🔴🔴⭕🔴⭕🔴🔴⭕⭕⭕⭕⭕🔴🔴⭕⭕🔴⭕🔴🔴🔴🔴🔴⭕⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴⭕🔴🔴🔴🔴🔴🔴
-⭕⭕🔴⭕⭕🔴🔴🔴⭕⭕⭕⭕🔴🔴🔴⭕🔴🔴⭕⭕🔴🔴⭕🔴⭕⭕🔴🔴⭕⭕🔴⭕🔴🔴⭕🔴🔴⭕🔴⭕⭕⭕🔴⭕🔴🔴🔴⭕🔴
-⭕⭕⭕⭕⭕⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴🔴🔴🔴🔴⭕⭕⭕⭕⭕⭕🔴🔴🔴⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕⭕
-🔴🔴🔴⭕🔴🔴🔴🔴🔴🔴⭕⭕🔴🔴⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕🔴⭕🔴🔴🔴⭕⭕🔴⭕⭕⭕⭕🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕🔴🔴⭕
-⭕🔴🔴⭕🔴⭕⭕⭕🔴⭕⭕🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕🔴⭕⭕⭕⭕⭕⭕⭕🔴⭕🔴⭕🔴⭕
-🔴⭕🔴🔴🔴🔴🔴🔴🔴🔴⭕⭕🔴🔴🔴⭕⭕⭕⭕⭕🔴⭕🔴🔴🔴🔴🔴⭕🔴🔴⭕⭕🔴🔴🔴🔴🔴⭕🔴🔴🔴🔴🔴🔴🔴⭕🔴🔴🔴
-⭕⭕⭕🔴🔴⭕⭕⭕🔴⭕🔴🔴🔴🔴⭕⭕⭕🔴🔴⭕⭕⭕🔴⭕⭕⭕🔴⭕⭕🔴🔴🔴🔴🔴🔴⭕⭕⭕🔴🔴🔴⭕⭕⭕🔴⭕⭕⭕🔴
-🔴⭕🔴⭕🔴⭕🔴⭕🔴🔴⭕⭕🔴🔴🔴🔴⭕🔴🔴🔴🔴⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕🔴🔴🔴⭕⭕
-⭕🔴🔴⭕🔴⭕⭕⭕🔴🔴⭕⭕⭕⭕🔴🔴🔴⭕🔴🔴⭕⭕🔴⭕⭕⭕🔴⭕🔴⭕⭕⭕⭕🔴🔴🔴🔴🔴⭕⭕🔴⭕⭕⭕🔴⭕⭕🔴⭕
-🔴🔴🔴⭕🔴🔴🔴🔴🔴🔴🔴⭕🔴🔴⭕⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴⭕⭕🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴🔴🔴⭕🔴
-⭕🔴🔴⭕🔴🔴⭕⭕🔴🔴🔴🔴⭕🔴⭕🔴🔴⭕🔴⭕⭕⭕🔴⭕⭕🔴⭕⭕🔴⭕⭕⭕⭕🔴🔴⭕🔴🔴⭕⭕🔴🔴🔴⭕⭕⭕⭕🔴🔴
-🔴⭕🔴⭕⭕🔴🔴🔴⭕⭕⭕🔴⭕⭕🔴🔴⭕🔴🔴⭕🔴🔴⭕⭕🔴⭕🔴🔴⭕🔴⭕⭕🔴🔴⭕🔴🔴⭕🔴⭕🔴🔴⭕🔴⭕🔴⭕⭕🔴
-🔴🔴⭕🔴🔴⭕⭕⭕🔴⭕🔴🔴🔴⭕⭕⭕⭕⭕🔴🔴🔴🔴⭕🔴🔴⭕🔴⭕⭕🔴⭕⭕🔴⭕⭕🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴🔴⭕🔴
-🔴⭕⭕🔴⭕⭕🔴🔴⭕🔴🔴🔴🔴🔴⭕⭕⭕🔴🔴🔴🔴⭕⭕⭕⭕🔴🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴🔴🔴⭕⭕🔴🔴🔴⭕⭕🔴🔴🔴🔴
-🔴🔴🔴🔴⭕⭕⭕⭕🔴🔴🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴🔴⭕⭕🔴🔴⭕🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕⭕⭕🔴🔴⭕🔴🔴⭕
-🔴🔴⭕🔴🔴🔴🔴⭕⭕🔴🔴⭕🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕🔴⭕🔴⭕⭕⭕🔴⭕🔴⭕⭕🔴⭕⭕🔴⭕⭕⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕
-🔴🔴⭕⭕🔴⭕⭕⭕🔴🔴⭕⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕🔴🔴⭕⭕⭕🔴⭕⭕🔴⭕⭕⭕⭕⭕⭕⭕⭕⭕🔴🔴⭕⭕⭕⭕🔴🔴⭕⭕
-⭕🔴🔴⭕🔴⭕🔴⭕🔴⭕⭕⭕🔴🔴⭕⭕⭕🔴🔴🔴⭕⭕⭕🔴🔴🔴🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕⭕🔴🔴⭕⭕🔴🔴🔴
-🔴🔴⭕🔴⭕⭕⭕🔴⭕⭕⭕🔴⭕⭕🔴🔴🔴🔴⭕🔴⭕🔴🔴🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕🔴🔴🔴⭕🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕⭕⭕
-⭕⭕⭕🔴🔴🔴🔴⭕⭕🔴🔴⭕🔴⭕⭕⭕⭕🔴🔴⭕⭕🔴⭕⭕🔴⭕⭕🔴🔴🔴🔴⭕🔴⭕⭕🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕🔴🔴
-⭕⭕🔴⭕🔴⭕⭕🔴🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕🔴🔴⭕🔴🔴⭕⭕🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕⭕🔴⭕⭕🔴🔴⭕⭕⭕🔴🔴🔴🔴
-⭕🔴⭕⭕⭕🔴🔴⭕🔴⭕🔴🔴🔴🔴🔴⭕🔴⭕⭕⭕🔴🔴⭕🔴⭕🔴⭕⭕🔴🔴🔴⭕🔴🔴🔴⭕⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕🔴⭕🔴
-⭕🔴🔴🔴⭕⭕⭕⭕⭕🔴🔴🔴⭕⭕⭕🔴🔴⭕🔴🔴🔴⭕🔴🔴⭕🔴🔴⭕🔴⭕⭕⭕⭕🔴⭕⭕🔴🔴🔴🔴🔴🔴⭕⭕⭕⭕⭕🔴🔴
-🔴🔴🔴⭕⭕⭕🔴🔴🔴🔴🔴⭕⭕🔴⭕⭕⭕🔴🔴⭕⭕⭕🔴🔴🔴🔴🔴🔴⭕🔴⭕⭕🔴⭕⭕⭕🔴⭕🔴⭕🔴🔴🔴🔴🔴🔴⭕⭕⭕
-⭕⭕⭕⭕⭕⭕⭕⭕🔴⭕🔴🔴⭕🔴🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕⭕⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕🔴⭕⭕🔴🔴🔴⭕⭕⭕🔴⭕⭕🔴🔴
-🔴🔴🔴🔴🔴🔴🔴⭕⭕🔴⭕⭕🔴🔴⭕🔴⭕🔴🔴🔴⭕⭕🔴⭕🔴⭕🔴🔴⭕⭕🔴⭕⭕🔴🔴⭕⭕⭕🔴🔴🔴⭕🔴⭕🔴🔴🔴⭕🔴
-🔴⭕⭕⭕⭕⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕🔴⭕🔴🔴⭕⭕⭕🔴🔴⭕🔴🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕🔴⭕⭕⭕🔴⭕🔴⭕⭕
-🔴⭕🔴🔴🔴⭕🔴⭕⭕🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕⭕🔴🔴🔴🔴🔴🔴🔴🔴⭕🔴🔴⭕🔴🔴⭕🔴⭕⭕⭕🔴🔴🔴🔴🔴🔴⭕🔴⭕
-🔴⭕🔴🔴🔴⭕🔴⭕🔴🔴🔴🔴⭕🔴⭕🔴🔴⭕🔴⭕🔴⭕⭕🔴🔴🔴⭕🔴🔴⭕⭕🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕🔴⭕🔴🔴🔴🔴🔴⭕
-🔴⭕🔴🔴🔴⭕🔴⭕⭕⭕⭕🔴⭕⭕🔴🔴⭕⭕🔴🔴⭕🔴⭕🔴🔴⭕🔴🔴🔴⭕⭕🔴⭕⭕⭕⭕🔴🔴🔴🔴⭕🔴⭕⭕🔴⭕🔴⭕⭕
-🔴⭕⭕⭕⭕⭕🔴⭕⭕🔴⭕⭕🔴⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕🔴⭕🔴🔴🔴⭕🔴🔴🔴⭕⭕⭕⭕🔴⭕🔴🔴⭕⭕⭕🔴⭕⭕⭕⭕⭕
-🔴🔴🔴🔴🔴🔴🔴⭕🔴⭕⭕⭕⭕⭕🔴⭕🔴⭕⭕🔴🔴🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕⭕🔴⭕🔴🔴🔴🔴🔴🔴🔴🔴⭕⭕⭕⭕🔴🔴🔴

+ 16 - 97
tests/QRCodeTest.php

@@ -1,118 +1,37 @@
 <?php
-
 /**
+ * Class QRCodeTest
  *
  * @filesource   QRCodeTest.php
- * @created      08.02.2016
+ * @created      17.11.2017
+ * @package      chillerlan\QRCodeTest
  * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2015 Smiley
+ * @copyright    2017 Smiley
  * @license      MIT
  */
 
 namespace chillerlan\QRCodeTest;
 
-use chillerlan\GoogleAuth\Authenticator;
-use chillerlan\QRCode\Output\{QRImage, QRImageOptions, QRString};
-use chillerlan\QRCode\{QRCode, QROptions};
-use ReflectionClass;
-use PHPUnit\Framework\TestCase;
-
-class QRCodeTest extends TestCase{
-
-	/**
-	 * @var \chillerlan\QRCode\QROptions
-	 */
-	protected $options;
-
-	/**
-	 * @var \chillerlan\QRCode\Output\QROutputInterface
-	 */
-	protected $output;
-
-	/**
-	 * @var \ReflectionClass
-	 */
-	protected $reflectionClass;
+use chillerlan\QRCode\Output\QRMarkupOptions;
+use chillerlan\QRCode\QRCode;
 
-	protected function setUp(){
-		$this->options = new QROptions;
-		$this->output = new QRString;
-		$this->reflectionClass = new ReflectionClass(QRCode::class);
-	}
+abstract class QRCodeTest extends QRTestAbstract{
 
-	public function testInstance(){
-		$this->assertInstanceOf(QRString::class, $this->output);
-		$this->assertInstanceOf(QROptions::class, $this->options);
-		$this->assertInstanceOf(QRCode::class, $this->reflectionClass->newInstanceArgs(['foobar', $this->output, $this->options]));
-	}
+	protected $FQCN = QRCode::class;
 
-	public function stringDataProvider(){
+	public function optionsDataProvider(){
 		return [
-			['1234567890', QRCode::TYPE_01],
-			['ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', QRCode::TYPE_03],
-			['#\\', QRCode::TYPE_01],
-			['茗荷', QRCode::TYPE_01],
+			[QRMarkupOptions::class],
+#			[],
 		];
 	}
 
 	/**
-	 * @dataProvider stringDataProvider
-	 */
-	public function testDataCoverage($data){
-		(new QRCode($data, $this->output))->getRawData();
-		$this->markTestSkipped('code coverage');
-	}
-
-	/**
-	 * @dataProvider stringDataProvider
-	 */
-	public function testTypeAndErrorcorrectlevelCoverage($data){
-
-		foreach(range(1, 10) as $type){
-			foreach(range(0, 3) as $eclevel){
-				$this->options->typeNumber = $type;
-				$this->options->errorCorrectLevel = $eclevel;
-				$this->assertInstanceOf(QRCode::class, new QRCode($data, $this->output, $this->options));
-			}
-		}
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\QRCodeException
-	 * @expectedExceptionMessage No data given.
-	 */
-	public function testNoDataException(){
-		$this->reflectionClass->newInstanceArgs(['', $this->output]);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\QRCodeException
-	 * @expectedExceptionMessage Invalid error correct level: 42
-	 */
-	public function testErrorCorrectLevelException(){
-		$this->options->errorCorrectLevel = 42;
-		$this->reflectionClass->newInstanceArgs(['foobar', $this->output, $this->options]);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\QRCodeException
-	 * @expectedExceptionMessage code length overflow. (261 > 72bit)
+	 * @dataProvider optionsDataProvider
 	 */
-	public function testCodeLengthOverflowException(){
-		$this->options->typeNumber = QRCode::TYPE_01;
-		$this->options->errorCorrectLevel = QRCode::ERROR_CORRECT_LEVEL_H;
-
-		$method = $this->reflectionClass->getMethod('getRawData');
-		$method->invoke($this->reflectionClass->newInstanceArgs(['ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', $this->output, $this->options]));
+	public function testInstance($options){
+		$q = $this->reflection->newInstanceArgs([new $options]);
+		$this->assertInstanceOf($this->FQCN, $q);
+#		print_r($q->render('test'));
 	}
-
-	public function testAuthenticatorExample(){
-		$authenticator = new Authenticator;
-
-		$data   = $authenticator->getUri($authenticator->createSecret(), 'test', 'chillerlan.net');
-		$qrcode = $this->reflectionClass->newInstanceArgs([$data, new QRImage(new QRImageOptions(['type' => QRCode::OUTPUT_IMAGE_GIF]))]);
-
-		$this->markTestSkipped(print_r($qrcode->output(), true));
-	}
-
 }

+ 0 - 172
tests/QRDataGeneratorTest.php

@@ -1,172 +0,0 @@
-<?php
-/**
- * Class QRDataGeneratorTest
- *
- * @filesource   QRDataGeneratorTest.php
- * @created      24.10.2017
- * @package      chillerlan\QRCodeTest
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2017 Smiley
- * @license      MIT
- */
-
-namespace chillerlan\QRCodeTest;
-
-use chillerlan\QRCode\Data\AlphaNum;
-use chillerlan\QRCode\Data\QRDataInterface;
-use chillerlan\QRCode\Output\QRString;
-use chillerlan\QRCode\QRCode;
-use chillerlan\QRCode\QRDataGenerator;
-use chillerlan\QRCode\QROptions;
-use PHPUnit\Framework\TestCase;
-use ReflectionClass;
-use ReflectionMethod;
-use ReflectionProperty;
-
-class QRDataGeneratorTest extends TestCase{
-
-	/**
-	 * @var \chillerlan\QRCode\QRDataGenerator
-	 */
-	protected $qrTest;
-
-	/**
-	 * @var \ReflectionClass
-	 */
-	protected $reflectionClass;
-
-
-	protected function setUp(){
-		$this->reflectionClass = new ReflectionClass(QRDataGenerator::class);
-
-		$this->qrTest = new QRDataGenerator('test', QRCode::TYPE_05, QRCode::ERROR_CORRECT_LEVEL_L);
-	}
-
-	private function getMethod(string $method):ReflectionMethod {
-		$method = $this->reflectionClass->getMethod($method);
-		$method->setAccessible(true);
-
-		return $method;
-	}
-
-	private function getProperty(string $property):ReflectionProperty{
-		$property = $this->reflectionClass->getProperty($property);
-		$property->setAccessible(true);
-
-		return $property;
-	}
-
-	public function stringDataProvider(){
-		return [
-			['1234567890', QRCode::TYPE_01],
-			['ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', QRCode::TYPE_03],
-			['#\\', QRCode::TYPE_01],
-			['茗荷', QRCode::TYPE_01],
-		];
-	}
-
-	/**
-	 * @dataProvider stringDataProvider
-	 */
-	public function testTypeAutoOverride($data, $type){
-		$qrcode = $this->reflectionClass->newInstanceArgs([$data, $type, QRCode::ERROR_CORRECT_LEVEL_L]);
-
-		$this->assertSame($type, $this->getProperty('typeNumber')->getValue($qrcode));
-	}
-
-	public function getMatrixDataProvider(){
-		return [
-			[QRCode::TYPE_01, 'foobar', 21],
-			[QRCode::TYPE_05, 'foobar', 37],
-			[QRCode::TYPE_10, 'foobar', 57],
-			[QRCode::TYPE_05, '1234567890', 37],
-			[QRCode::TYPE_10, '1234567890', 57],
-			[QRCode::TYPE_03, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 29],
-			[QRCode::TYPE_05, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 37],
-			[QRCode::TYPE_10, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 57],
-			[QRCode::TYPE_05, '茗荷', 37],
-			[QRCode::TYPE_10, '茗荷', 57],
-		];
-	}
-
-	/**
-	 * @dataProvider getMatrixDataProvider
-	 */
-	public function testInternalGetMatrix($type, $data, $pixelCount){
-		$method   = $this->getMethod('getMatrix');
-		$property = $this->getProperty('pixelCount');
-
-		for($i = 0; $i <= 7; $i++){
-			$qrcode = $this->reflectionClass->newInstanceArgs([$data, $type, QRCode::ERROR_CORRECT_LEVEL_L]);
-			$method->invokeArgs($qrcode, [false, $i]);
-
-			$this->assertSame($pixelCount, $property->getValue($qrcode));
-		}
-	}
-
-	public function testIsNumber(){
-		$this->assertTrue($this->qrTest->isNumber('1234567890'));
-		$this->assertFalse($this->qrTest->isNumber('abc'));
-	}
-
-	public function testIsAlphaNum(){
-		$this->assertTrue($this->qrTest->isAlphaNum('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:'));
-		$this->assertFalse($this->qrTest->isAlphaNum('#'));
-	}
-
-	// http://stackoverflow.com/a/24755772
-	public function testIsKanji(){
-		$this->assertTrue($this->qrTest->isKanji('茗荷'));
-		$this->assertFalse($this->qrTest->isKanji(''));
-		$this->assertFalse($this->qrTest->isKanji('ÃÃÃ')); // non-kanji
-		$this->assertFalse($this->qrTest->isKanji('荷')); // kanji forced into byte mode due to length
-	}
-
-	// coverage
-	public function testGetBCHTypeNumber(){
-		$this->assertSame(7973, $this->getMethod('getBCHTypeNumber')->invokeArgs($this->qrTest, [1]));
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\QRCodeException
-	 * @expectedExceptionMessage $typeNumber: 1 / $errorCorrectLevel: 42
-	 */
-	public function testGetRSBlocksException(){
-		$this->getMethod('getRSBlocks')->invokeArgs($this->qrTest, [1, 42]);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\QRCodeException
-	 * @expectedExceptionMessage Invalid error correct level: 42
-	 */
-	public function testGetMaxLengthECLevelException(){
-		$this->getMethod('getMaxLength')->invokeArgs($this->qrTest, [QRCode::TYPE_01, QRDataInterface::MODE_BYTE, 42]);
-	}
-
-	/**
-	 * @expectedException \chillerlan\QRCode\QRCodeException
-	 * @expectedExceptionMessage Invalid mode: 1337
-	 */
-	public function testGetMaxLengthModeException(){
-		$this->getMethod('getMaxLength')->invokeArgs($this->qrTest, [QRCode::TYPE_01, 1337, QRCode::ERROR_CORRECT_LEVEL_H]);
-	}
-
-	public function getTypeNumberDataProvider(){
-		return [
-			[QRDataInterface::MODE_ALPHANUM, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 $%*+-./:', 2],
-			[QRDataInterface::MODE_BYTE, '#\\', 1],
-			[QRDataInterface::MODE_KANJI, '茗荷', 1],
-			[QRDataInterface::MODE_NUMBER, '1234567890', 1],
-		];
-	}
-
-	/**
-	 * @dataProvider getTypeNumberDataProvider
-	 */
-	public function testGetTypeNumber($mode, $data, $expected){
-		$i = QRDataGenerator::DATA_INTERFACES[$mode];
-
-		$this->assertSame($expected, $this->getMethod('getTypeNumber')->invokeArgs($this->qrTest, [new $i($data), $mode]));
-	}
-
-}

+ 74 - 0
tests/QRTestAbstract.php

@@ -0,0 +1,74 @@
+<?php
+/**
+ * Class QRTestAbstract
+ *
+ * @filesource   QRTestAbstract.php
+ * @created      17.11.2017
+ * @package      chillerlan\QRCodeTest
+ * @author       Smiley <smiley@chillerlan.net>
+ * @copyright    2017 Smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest;
+
+use PHPUnit\Framework\TestCase;
+use ReflectionClass;
+use ReflectionMethod;
+use ReflectionProperty;
+
+abstract class QRTestAbstract extends TestCase{
+
+	/**
+	 * @var \ReflectionClass
+	 */
+	protected $reflection;
+
+	/**
+	 * @var string
+	 */
+	protected $FQCN;
+
+	protected function setUp(){
+		$this->reflection = new ReflectionClass($this->FQCN);
+	}
+
+	/**
+	 * @param string $method
+	 *
+	 * @return \ReflectionMethod
+	 */
+	protected function getMethod(string $method):ReflectionMethod {
+		$method = $this->reflection->getMethod($method);
+		$method->setAccessible(true);
+
+		return $method;
+	}
+
+	/**
+	 * @param string $property
+	 *
+	 * @return \ReflectionProperty
+	 */
+	protected function getProperty(string $property):ReflectionProperty{
+		$property = $this->reflection->getProperty($property);
+		$property->setAccessible(true);
+
+		return $property;
+	}
+
+	/**
+	 * @param        $object
+	 * @param string $property
+	 * @param        $value
+	 *
+	 * @return void
+	 */
+	protected function setProperty($object, string $property, $value){
+		$property = $this->getProperty($property);
+		$property->setAccessible(true);
+		$property->setValue($object, $value);
+	}
+
+
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов