1 module ithox.qrcode.common.formatinformation;
2 
3 import ithox.qrcode.common.errorcorrectionlevel;
4 import ithox.qrcode.common.bitutils;
5 
6 /**
7 * Encapsulates a QR Code's format information, including the data mask used and
8 * error correction level.
9 */
10 class FormatInformation
11 {
12     /**
13 	* Mask for format information.
14 	*/
15     enum int FORMAT_INFO_MASK_QR = 0x5412;
16 
17     /**
18 	* Lookup table for decoding format information.
19 	*
20 	* See ISO 18004:2006, Annex C, Table C.1
21 	*
22 	* @var array
23 	*/
24     protected static int[][] formatInfoDecodeLookup = [
25         [0x5412, 0x00
26     ], [0x5125, 0x01], [
27         0x5e7c, 0x02
28     ], [0x5b4b, 0x03], [
29         0x45f9, 0x04
30     ], [0x40ce, 0x05], [
31         0x4f97, 0x06
32     ], [0x4aa0, 0x07], [
33         0x77c4, 0x08
34     ], [0x72f3, 0x09], [
35         0x7daa, 0x0a
36     ], [0x789d, 0x0b], [
37         0x662f, 0x0c
38     ], [0x6318, 0x0d], [
39         0x6c41, 0x0e
40     ], [0x6976, 0x0f], [
41         0x1689, 0x10
42     ], [0x13be, 0x11], [
43         0x1ce7, 0x12
44     ], [0x19d0, 0x13], [
45         0x0762, 0x14
46     ], [0x0255, 0x15], [
47         0x0d0c, 0x16
48     ], [0x083b, 0x17], [
49         0x355f, 0x18
50     ], [0x3068, 0x19], [
51         0x3f31, 0x1a
52     ], [0x3a06, 0x1b], [0x24b4, 0x1c], [0x2183, 0x1d], [0x2eda, 0x1e], [0x2bed, 0x1f],];
53 
54     /**
55 	* Offset i holds the number of 1 bits in the binary representation of i.
56 	*
57 	* @var array
58 	*/
59     protected static int[] bitsSetInHalfByte = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
60 
61     /**
62 	* Error correction level.
63 	*
64 	* @var ErrorCorrectionLevel
65 	*/
66     protected ErrorCorrectionLevel ecLevel;
67     /**
68 	* Data mask.
69 	*
70 	* @var integer
71 	*/
72     protected int dataMask;
73 
74     this(int formatInfo)
75     {
76         this.ecLevel = cast(ErrorCorrectionLevel)((formatInfo >> 3) & 0x3);
77         this.dataMask = formatInfo & 0x7;
78     }
79 
80     /**
81 	* Checks how many bits are different between two integers.
82 	*
83 	* @param  integer a
84 	* @param  integer b
85 	* @return integer
86 	*/
87     public static int numBitsDiffering(int a, int b)
88     {
89         a ^= b;
90         return (bitsSetInHalfByte[a & 0xf] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a,
91                 4) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a,
92                 8) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a,
93                 12) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a,
94                 16) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a,
95                 20) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a,
96                 24) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a, 28) & 0xf)]);
97     }
98 
99     /**
100 	* Decodes format information.
101 	*
102 	* @param  integer $maskedFormatInfo1
103 	* @param  integer $maskedFormatInfo2
104 	* @return FormatInformation|null
105 	*/
106     public static FormatInformation decodeFormatInformation(int maskedFormatInfo1,
107             int maskedFormatInfo2)
108     {
109         auto formatInfo = doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2);
110         if (formatInfo !is null)
111         {
112             return formatInfo;
113         }
114         // Should return null, but, some QR codes apparently do not mask this
115         // info. Try again by actually masking the pattern first.
116         return doDecodeFormatInformation(maskedFormatInfo1 ^ FORMAT_INFO_MASK_QR,
117                 maskedFormatInfo2 ^ FORMAT_INFO_MASK_QR);
118     }
119 
120     /**
121 	* Internal method for decoding format information.
122 	*
123 	* @param  integer $maskedFormatInfo1
124 	* @param  integer $maskedFormatInfo2
125 	* @return FormatInformation|null
126 	*/
127     protected static FormatInformation doDecodeFormatInformation(int maskedFormatInfo1,
128             int maskedFormatInfo2)
129     {
130         int bestDifference = int.max;
131         int bestFormatInfo = 0;
132         foreach (decodeInfo; formatInfoDecodeLookup)
133         {
134             auto targetInfo = decodeInfo[0];
135             if (targetInfo == maskedFormatInfo1 || targetInfo == maskedFormatInfo2)
136             {
137                 // Found an exact match
138                 return new FormatInformation(decodeInfo[1]);
139             }
140             auto bitsDifference = numBitsDiffering(maskedFormatInfo1, targetInfo);
141             if (bitsDifference < bestDifference)
142             {
143                 bestFormatInfo = decodeInfo[1];
144                 bestDifference = bitsDifference;
145             }
146             if (maskedFormatInfo1 != maskedFormatInfo2)
147             {
148                 // Also try the other option
149                 bitsDifference = numBitsDiffering(maskedFormatInfo2, targetInfo);
150                 if (bitsDifference < bestDifference)
151                 {
152                     bestFormatInfo = decodeInfo[1];
153                     bestDifference = bitsDifference;
154                 }
155             }
156         }
157         // Hamming distance of the 32 masked codes is 7, by construction, so
158         // <= 3 bits differing means we found a match.
159         if (bestDifference <= 3)
160         {
161             return new FormatInformation(bestFormatInfo);
162         }
163         return null;
164     }
165 
166     /**
167 	* Gets the error correction level.
168 	*
169 	* @return ErrorCorrectionLevel
170 	*/
171     public ErrorCorrectionLevel getErrorCorrectionLevel()
172     {
173         return this.ecLevel;
174     }
175     /**
176 	* Gets the data mask.
177 	*
178 	* @return integer
179 	*/
180     public int getDataMask()
181     {
182         return this.dataMask;
183     }
184     /**
185 	* Hashes the code of the EC level.
186 	*
187 	* @return integer
188 	*/
189     public int hashCode()
190     {
191         return (this.ecLevel << 3) | this.dataMask;
192     }
193     /**
194 	* Verifies if this instance equals another one.
195 	*
196 	* @param  mixed $other
197 	* @return boolean
198 	*/
199     public bool equals(FormatInformation other)
200     {
201         if (typeid(FormatInformation) != typeid(other))
202         {
203             return false;
204         }
205         return (ecLevel == other.getErrorCorrectionLevel() && dataMask == other.getDataMask());
206     }
207 }