QRMatrixTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. <?php
  2. /**
  3. * Class QRMatrixTest
  4. *
  5. * @created 17.11.2017
  6. * @author Smiley <smiley@chillerlan.net>
  7. * @copyright 2017 Smiley
  8. * @license MIT
  9. */
  10. namespace chillerlan\QRCodeTest\Data;
  11. use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
  12. use chillerlan\QRCode\{QRCode, QROptions};
  13. use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
  14. use PHPUnit\Framework\TestCase;
  15. /**
  16. * Tests the QRMatix class
  17. */
  18. final class QRMatrixTest extends TestCase{
  19. /** @internal */
  20. protected const version = 40;
  21. /** @internal */
  22. protected QRMatrix $matrix;
  23. /**
  24. * invokes a QRMatrix object
  25. *
  26. * @internal
  27. */
  28. protected function setUp():void{
  29. $this->matrix = $this->getMatrix($this::version);
  30. }
  31. /**
  32. * shortcut
  33. *
  34. * @internal
  35. */
  36. protected function getMatrix(int $version):QRMatrix{
  37. return new QRMatrix(new Version($version), new EccLevel(EccLevel::L));
  38. }
  39. /**
  40. * Validates the QRMatrix instance
  41. */
  42. public function testInstance():void{
  43. $this::assertInstanceOf(QRMatrix::class, $this->matrix);
  44. }
  45. /**
  46. * Tests if size() returns the actual matrix size/count
  47. */
  48. public function testSize():void{
  49. $this::assertCount($this->matrix->size(), $this->matrix->matrix());
  50. }
  51. /**
  52. * Tests if version() returns the current (given) version
  53. */
  54. public function testVersion():void{
  55. $this::assertSame($this::version, $this->matrix->version()->getVersionNumber());
  56. }
  57. /**
  58. * Tests if eccLevel() returns the current (given) ECC level
  59. */
  60. public function testECC():void{
  61. $this::assertSame(EccLevel::MODES[EccLevel::L], $this->matrix->eccLevel()->getOrdinal());
  62. }
  63. /**
  64. * Tests if maskPattern() returns the current (or default) mask pattern
  65. */
  66. public function testMaskPattern():void{
  67. $this::assertSame(null, $this->matrix->maskPattern());
  68. $matrix = (new QRCode)->addByteSegment('testdata')->getMatrix();
  69. $this::assertInstanceOf(MaskPattern::class, $matrix->maskPattern());
  70. $this::assertSame(MaskPattern::PATTERN_010, $matrix->maskPattern()->getPattern());
  71. }
  72. /**
  73. * Tests the set(), get() and check() methods
  74. */
  75. public function testGetSetCheck():void{
  76. $this->matrix->set(10, 10, true, QRMatrix::M_TEST);
  77. $this::assertSame(QRMatrix::M_TEST | QRMatrix::IS_DARK, $this->matrix->get(10, 10));
  78. $this::assertTrue($this->matrix->check(10, 10));
  79. $this->matrix->set(20, 20, false, QRMatrix::M_TEST);
  80. $this::assertSame(QRMatrix::M_TEST, $this->matrix->get(20, 20));
  81. $this::assertFalse($this->matrix->check(20, 20));
  82. }
  83. /**
  84. * Version data provider for several pattern tests
  85. *
  86. * @return int[][]
  87. * @internal
  88. */
  89. public function versionProvider():array{
  90. $versions = [];
  91. for($i = 1; $i <= 40; $i++){
  92. $versions[] = [$i];
  93. }
  94. return $versions;
  95. }
  96. /**
  97. * Tests setting the dark module and verifies its position
  98. *
  99. * @dataProvider versionProvider
  100. */
  101. public function testSetDarkModule(int $version):void{
  102. $matrix = $this->getMatrix($version)->setDarkModule();
  103. $this::assertSame(QRMatrix::M_DARKMODULE | QRMatrix::IS_DARK, $matrix->get(8, $matrix->size() - 8));
  104. }
  105. /**
  106. * Tests setting the finder patterns and verifies their positions
  107. *
  108. * @dataProvider versionProvider
  109. */
  110. public function testSetFinderPattern(int $version):void{
  111. $matrix = $this->getMatrix($version)->setFinderPattern();
  112. $this::assertSame(QRMatrix::M_FINDER | QRMatrix::IS_DARK, $matrix->get(0, 0));
  113. $this::assertSame(QRMatrix::M_FINDER | QRMatrix::IS_DARK, $matrix->get(0, $matrix->size() - 1));
  114. $this::assertSame(QRMatrix::M_FINDER | QRMatrix::IS_DARK, $matrix->get($matrix->size() - 1, 0));
  115. }
  116. /**
  117. * Tests the separator patterns and verifies their positions
  118. *
  119. * @dataProvider versionProvider
  120. */
  121. public function testSetSeparators(int $version):void{
  122. $matrix = $this->getMatrix($version)->setSeparators();
  123. $this::assertSame(QRMatrix::M_SEPARATOR, $matrix->get(7, 0));
  124. $this::assertSame(QRMatrix::M_SEPARATOR, $matrix->get(0, 7));
  125. $this::assertSame(QRMatrix::M_SEPARATOR, $matrix->get(0, $matrix->size() - 8));
  126. $this::assertSame(QRMatrix::M_SEPARATOR, $matrix->get($matrix->size() - 8, 0));
  127. }
  128. /**
  129. * Tests the alignment patterns and verifies their positions - version 1 (no pattern) skipped
  130. *
  131. * @dataProvider versionProvider
  132. */
  133. public function testSetAlignmentPattern(int $version):void{
  134. if($version === 1){
  135. $this->markTestSkipped('N/A');
  136. /** @noinspection PhpUnreachableStatementInspection */
  137. return;
  138. }
  139. $matrix = $this
  140. ->getMatrix($version)
  141. ->setFinderPattern()
  142. ->setAlignmentPattern()
  143. ;
  144. $alignmentPattern = (new Version($version))->getAlignmentPattern();
  145. foreach($alignmentPattern as $py){
  146. foreach($alignmentPattern as $px){
  147. if($matrix->get($px, $py) === (QRMatrix::M_FINDER | QRMatrix::IS_DARK)){
  148. $this::assertSame(QRMatrix::M_FINDER | QRMatrix::IS_DARK, $matrix->get($px, $py), 'skipped finder pattern');
  149. continue;
  150. }
  151. $this::assertSame(QRMatrix::M_ALIGNMENT | QRMatrix::IS_DARK, $matrix->get($px, $py));
  152. }
  153. }
  154. }
  155. /**
  156. * Tests the timing patterns and verifies their positions
  157. *
  158. * @dataProvider versionProvider
  159. */
  160. public function testSetTimingPattern(int $version):void{
  161. $matrix = $this
  162. ->getMatrix($version)
  163. ->setAlignmentPattern()
  164. ->setTimingPattern()
  165. ;
  166. $size = $matrix->size();
  167. for($i = 7; $i < $size - 7; $i++){
  168. if($i % 2 === 0){
  169. $p1 = $matrix->get(6, $i);
  170. if($p1 === (QRMatrix::M_ALIGNMENT | QRMatrix::IS_DARK)){
  171. $this::assertSame(QRMatrix::M_ALIGNMENT | QRMatrix::IS_DARK, $p1, 'skipped alignment pattern');
  172. continue;
  173. }
  174. $this::assertSame(QRMatrix::M_TIMING | QRMatrix::IS_DARK, $p1);
  175. $this::assertSame(QRMatrix::M_TIMING | QRMatrix::IS_DARK, $matrix->get($i, 6));
  176. }
  177. }
  178. }
  179. /**
  180. * Tests the version patterns and verifies their positions - version < 7 skipped
  181. *
  182. * @dataProvider versionProvider
  183. */
  184. public function testSetVersionNumber(int $version):void{
  185. if($version < 7){
  186. $this->markTestSkipped('N/A');
  187. /** @noinspection PhpUnreachableStatementInspection */
  188. return;
  189. }
  190. $matrix = $this->getMatrix($version)->setVersionNumber(true);
  191. $this::assertSame(QRMatrix::M_VERSION, $matrix->get($matrix->size() - 9, 0));
  192. $this::assertSame(QRMatrix::M_VERSION, $matrix->get($matrix->size() - 11, 5));
  193. $this::assertSame(QRMatrix::M_VERSION, $matrix->get(0, $matrix->size() - 9));
  194. $this::assertSame(QRMatrix::M_VERSION, $matrix->get(5, $matrix->size() - 11));
  195. }
  196. /**
  197. * Tests the format patterns and verifies their positions
  198. *
  199. * @dataProvider versionProvider
  200. */
  201. public function testSetFormatInfo(int $version):void{
  202. $matrix = $this->getMatrix($version)->setFormatInfo(new MaskPattern(MaskPattern::PATTERN_000), true);
  203. $this::assertSame(QRMatrix::M_FORMAT, $matrix->get(8, 0));
  204. $this::assertSame(QRMatrix::M_FORMAT, $matrix->get(0, 8));
  205. $this::assertSame(QRMatrix::M_FORMAT, $matrix->get($matrix->size() - 1, 8));
  206. $this::assertSame(QRMatrix::M_FORMAT, $matrix->get($matrix->size() - 8, 8));
  207. }
  208. /**
  209. * Tests the quiet zone pattern and verifies its position
  210. *
  211. * @dataProvider versionProvider
  212. */
  213. public function testSetQuietZone(int $version):void{
  214. $matrix = $this->getMatrix($version);
  215. $size = $matrix->size();
  216. $q = 5;
  217. $matrix->set(0, 0, true, QRMatrix::M_TEST);
  218. $matrix->set($size - 1, $size - 1, true, QRMatrix::M_TEST);
  219. $matrix->setQuietZone($q);
  220. $this::assertCount($size + 2 * $q, $matrix->matrix());
  221. $this::assertCount($size + 2 * $q, $matrix->matrix()[$size - 1]);
  222. $size = $matrix->size();
  223. $this::assertSame(QRMatrix::M_QUIETZONE, $matrix->get(0, 0));
  224. $this::assertSame(QRMatrix::M_QUIETZONE, $matrix->get($size - 1, $size - 1));
  225. $this::assertSame(QRMatrix::M_TEST | QRMatrix::IS_DARK, $matrix->get($q, $q));
  226. $this::assertSame(QRMatrix::M_TEST | QRMatrix::IS_DARK, $matrix->get($size - 1 - $q, $size - 1 - $q));
  227. }
  228. /**
  229. * Tests if an exception is thrown in an attempt to create it before data was written
  230. */
  231. public function testSetQuietZoneException():void{
  232. $this->expectException(QRCodeDataException::class);
  233. $this->expectExceptionMessage('use only after writing data');
  234. $this->matrix->setQuietZone();
  235. }
  236. public function testSetLogoSpaceOrientation():void{
  237. $o = new QROptions;
  238. $o->version = 10;
  239. $o->eccLevel = EccLevel::H;
  240. $o->addQuietzone = false;
  241. $matrix = (new QRCode($o))->addByteSegment('testdata')->getMatrix();
  242. // also testing size adjustment to uneven numbers
  243. $matrix->setLogoSpace(20, 14);
  244. // NW corner
  245. $this::assertNotSame(QRMatrix::M_LOGO, $matrix->get(17, 20));
  246. $this::assertSame(QRMatrix::M_LOGO, $matrix->get(18, 21));
  247. // SE corner
  248. $this::assertSame(QRMatrix::M_LOGO, $matrix->get(38, 35));
  249. $this::assertNotSame(QRMatrix::M_LOGO, $matrix->get(39, 36));
  250. }
  251. public function testSetLogoSpacePosition():void{
  252. $o = new QROptions;
  253. $o->version = 10;
  254. $o->eccLevel = EccLevel::H;
  255. $o->addQuietzone = true;
  256. $o->quietzoneSize = 10;
  257. $m = (new QRCode($o))->addByteSegment('testdata')->getMatrix();
  258. // logo space should not overwrite quiet zone & function patterns
  259. $m->setLogoSpace(21, 21, -10, -10);
  260. $this::assertSame(QRMatrix::M_QUIETZONE, $m->get(9, 9));
  261. $this::assertSame(QRMatrix::M_FINDER | QRMatrix::IS_DARK, $m->get(10, 10));
  262. $this::assertSame(QRMatrix::M_FINDER | QRMatrix::IS_DARK, $m->get(16, 16));
  263. $this::assertSame(QRMatrix::M_SEPARATOR, $m->get(17, 17));
  264. $this::assertSame(QRMatrix::M_FORMAT | QRMatrix::IS_DARK, $m->get(18, 18));
  265. $this::assertSame(QRMatrix::M_LOGO, $m->get(19, 19));
  266. $this::assertSame(QRMatrix::M_LOGO, $m->get(20, 20));
  267. $this::assertNotSame(QRMatrix::M_LOGO, $m->get(21, 21));
  268. // i just realized that setLogoSpace() could be called multiple times
  269. // on the same instance and i'm not going to do anything about it :P
  270. $m->setLogoSpace(21, 21, 45, 45);
  271. $this::assertNotSame(QRMatrix::M_LOGO, $m->get(54, 54));
  272. $this::assertSame(QRMatrix::M_LOGO, $m->get(55, 55));
  273. $this::assertSame(QRMatrix::M_QUIETZONE, $m->get(67, 67));
  274. }
  275. public function testSetLogoSpaceInvalidEccException():void{
  276. $this->expectException(QRCodeDataException::class);
  277. $this->expectExceptionMessage('ECC level "H" required to add logo space');
  278. (new QRCode)->addByteSegment('testdata')->getMatrix()->setLogoSpace(50, 50);
  279. }
  280. public function testSetLogoSpaceMaxSizeException():void{
  281. $this->expectException(QRCodeDataException::class);
  282. $this->expectExceptionMessage('logo space exceeds the maximum error correction capacity');
  283. $o = new QROptions;
  284. $o->version = 5;
  285. $o->eccLevel = EccLevel::H;
  286. (new QRCode($o))->addByteSegment('testdata')->getMatrix()->setLogoSpace(50, 50);
  287. }
  288. /**
  289. * Tests flipping the value of a module
  290. */
  291. public function testFlip():void{
  292. // using the dark module here because i'm lazy
  293. $matrix = $this->getMatrix(10)->setDarkModule();
  294. $x = 8;
  295. $y = $matrix->size() - 8;
  296. // cover checkType()
  297. $this::assertTrue($matrix->checkType($x, $y, QRMatrix::M_DARKMODULE));
  298. // verify the current state (dark)
  299. $this::assertSame(QRMatrix::M_DARKMODULE | QRMatrix::IS_DARK, $matrix->get($x, $y));
  300. // flip
  301. $matrix->flip($x, $y);
  302. // verify flip
  303. $this::assertSame(QRMatrix::M_DARKMODULE, $matrix->get($x, $y));
  304. // flip again
  305. $matrix->flip($x, $y);
  306. // verify flip
  307. $this::assertSame(QRMatrix::M_DARKMODULE | QRMatrix::IS_DARK, $matrix->get($x, $y));
  308. }
  309. }