Number   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 61
dl 0
loc 158
rs 10
c 3
b 1
f 0
wmc 23

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getLengthInBits() 0 2 1
A validateString() 0 13 4
B decodeSegment() 0 66 11
A parseInt() 0 8 2
A write() 0 30 5
1
<?php
2
/**
3
 * Class Number
4
 *
5
 * @created      26.11.2015
6
 * @author       Smiley <[email protected]>
7
 * @copyright    2015 Smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\QRCode\Data;
12
13
use chillerlan\QRCode\Common\{BitBuffer, Mode};
14
15
use function array_flip, ceil, ord, str_split, substr;
16
17
/**
18
 * Numeric mode: decimal digits 0 to 9
19
 *
20
 * ISO/IEC 18004:2000 Section 8.3.2
21
 * ISO/IEC 18004:2000 Section 8.4.2
22
 */
23
final class Number extends QRDataModeAbstract{
24
25
	/**
26
	 * @var int[]
27
	 */
28
	private const NUMBER_TO_ORD = [
29
		'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
30
	];
31
32
	/**
33
	 * @inheritDoc
34
	 */
35
	public const DATAMODE = Mode::NUMBER;
36
37
	/**
38
	 * @inheritDoc
39
	 */
40
	public function getLengthInBits():int{
41
		return (int)ceil($this->getCharCount() * (10 / 3));
42
	}
43
44
	/**
45
	 * @inheritDoc
46
	 */
47
	public static function validateString(string $string):bool{
48
49
		if($string === ''){
50
			return false;
51
		}
52
53
		foreach(str_split($string) as $chr){
54
			if(!isset(self::NUMBER_TO_ORD[$chr])){
55
				return false;
56
			}
57
		}
58
59
		return true;
60
	}
61
62
	/**
63
	 * @inheritDoc
64
	 */
65
	public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{
66
		$len = $this->getCharCount();
67
68
		$bitBuffer
69
			->put(self::DATAMODE, 4)
70
			->put($len, $this::getLengthBits($versionNumber))
71
		;
72
73
		$i = 0;
74
75
		// encode numeric triplets in 10 bits
76
		while(($i + 2) < $len){
77
			$bitBuffer->put($this->parseInt(substr($this->data, $i, 3)), 10);
78
			$i += 3;
79
		}
80
81
		if($i < $len){
82
83
			// encode 2 remaining numbers in 7 bits
84
			if(($len - $i) === 2){
85
				$bitBuffer->put($this->parseInt(substr($this->data, $i, 2)), 7);
86
			}
87
			// encode one remaining number in 4 bits
88
			elseif(($len - $i) === 1){
89
				$bitBuffer->put($this->parseInt(substr($this->data, $i, 1)), 4);
90
			}
91
92
		}
93
94
		return $this;
95
	}
96
97
	/**
98
	 * get the code for the given numeric string
99
	 */
100
	private function parseInt(string $string):int{
101
		$num = 0;
102
103
		foreach(str_split($string) as $chr){
104
			$num = ($num * 10 + ord($chr) - 48);
105
		}
106
107
		return $num;
108
	}
109
110
	/**
111
	 * @inheritDoc
112
	 *
113
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
114
	 */
115
	public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
116
		$length  = $bitBuffer->read(self::getLengthBits($versionNumber));
117
		$charmap = array_flip(self::NUMBER_TO_ORD);
118
119
		// @todo
120
		$toNumericChar = function(int $ord) use ($charmap):string{
121
122
			if(isset($charmap[$ord])){
123
				return $charmap[$ord];
124
			}
125
126
			throw new QRCodeDataException('invalid character value: '.$ord);
127
		};
128
129
		$result = '';
130
		// Read three digits at a time
131
		while($length >= 3){
132
			// Each 10 bits encodes three digits
133
			if($bitBuffer->available() < 10){
134
				throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
135
			}
136
137
			$threeDigitsBits = $bitBuffer->read(10);
138
139
			if($threeDigitsBits >= 1000){
140
				throw new QRCodeDataException('error decoding numeric value');
141
			}
142
143
			$result .= $toNumericChar((int)($threeDigitsBits / 100));
144
			$result .= $toNumericChar((int)($threeDigitsBits / 10) % 10);
145
			$result .= $toNumericChar($threeDigitsBits % 10);
146
147
			$length -= 3;
148
		}
149
150
		if($length === 2){
151
			// Two digits left over to read, encoded in 7 bits
152
			if($bitBuffer->available() < 7){
153
				throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
154
			}
155
156
			$twoDigitsBits = $bitBuffer->read(7);
157
158
			if($twoDigitsBits >= 100){
159
				throw new QRCodeDataException('error decoding numeric value');
160
			}
161
162
			$result .= $toNumericChar((int)($twoDigitsBits / 10));
163
			$result .= $toNumericChar($twoDigitsBits % 10);
164
		}
165
		elseif($length === 1){
166
			// One digit left over to read
167
			if($bitBuffer->available() < 4){
168
				throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
169
			}
170
171
			$digitBits = $bitBuffer->read(4);
172
173
			if($digitBits >= 10){
174
				throw new QRCodeDataException('error decoding numeric value');
175
			}
176
177
			$result .= $toNumericChar($digitBits);
178
		}
179
180
		return $result;
181
	}
182
183
}
184