1 module barcode.qr.qrsegment;
2 
3 import std.algorithm;
4 import std.exception;
5 import std.ascii;
6 
7 import barcode.qr.util;
8 
9 struct QrSegment
10 {
11 pure @safe:
12     static struct Mode
13     {
14         enum numeric = Mode(1, [10, 12, 14]),
15         alphanumeric = Mode(2, [ 9, 11, 13]),
16                bytes = Mode(4, [ 8, 16, 16]),
17                kanji = Mode(8, [ 8, 10, 12]);
18 
19         ubyte bits;
20         private ubyte[3] cc;
21 
22     pure @safe:
23         ubyte numCharCountBits(int ver) const
24         {
25                  if ( 1 <= ver && ver <=  9) return cc[0];
26             else if (10 <= ver && ver <= 26) return cc[1];
27             else if (27 <= ver && ver <= 40) return cc[2];
28             else throw new Exception("Version number out of range");
29         }
30     }
31 
32     Mode mode;
33     int numChars;
34     ubyte[] data;
35     int bitLength;
36 
37     static QrSegment makeBytes(const(ubyte)[] d)
38     { return QrSegment(Mode.bytes, cast(int)d.length, d, cast(int)d.length*8); }
39 
40     static QrSegment makeNumeric(string digits)
41     {
42         BitBuffer bb;
43         int accumData = 0;
44         int accumCount = 0;
45         int charCount = 0;
46         foreach (char c; digits)
47         {
48             if (c < '0' || c > '9')
49                 throw new Exception("String contains non-numeric " ~
50                                      "characters in numeric mode");
51             accumData = accumData * 10 + (c - '0');
52             accumCount++;
53             if (accumCount == 3)
54             {
55                 bb.appendBits(accumData, 10);
56                 accumData = 0;
57                 accumCount = 0;
58             }
59             charCount++;
60         }
61         if (accumCount > 0)  // 1 or 2 digits remaining
62             bb.appendBits(accumData, accumCount * 3 + 1);
63         return QrSegment(Mode.numeric, charCount, bb.getBytes, cast(int)bb.length);
64     }
65 
66     static QrSegment makeAlphanumeric(string text)
67     {
68         BitBuffer bb;
69         int accumData = 0;
70         int accumCount = 0;
71         int charCount = 0;
72             foreach (char c; text)
73             {
74                 if (c < ' ' || c > 'Z')
75                     throw new Exception("String contains unencodable " ~
76                                         "characters in alphanumeric mode");
77                 accumData = accumData * 45 + encodingTable[c - ' '];
78                 accumCount++;
79                 if (accumCount == 2)
80                 {
81                     bb.appendBits(accumData, 11);
82                     accumData = 0;
83                     accumCount = 0;
84                 }
85                 charCount++;
86         }
87         if (accumCount > 0)  // 1 character remaining
88                 bb.appendBits(accumData, 6);
89         return QrSegment(Mode.alphanumeric, charCount, bb.getBytes, cast(int)bb.length);
90     }
91 
92     static QrSegment[] makeSegments(string text)
93     {
94         // Select the most efficient segment encoding automatically
95         if (text.length == 0) return [];
96         else if (QrSegment.isNumeric(text))
97             return [QrSegment.makeNumeric(text)];
98         else if (QrSegment.isAlphanumeric(text))
99             return [QrSegment.makeAlphanumeric(text)];
100         else
101             return [QrSegment.makeBytes(cast(ubyte[])text.dup)];
102     }
103 
104     static bool isAlphanumeric(string text)
105     {
106         return text.map!(a=>cast(int)a)
107             .all!(c => ' ' <= c && c <= 'Z' && encodingTable[c - ' '] != -1);
108     }
109 
110     static bool isNumeric(string text) { return text.all!isDigit; }
111 
112     pure this(Mode md, int nc, const(ubyte)[] d, int bl)
113     {
114         mode = md;
115         numChars = nc;
116         data = d.dup;
117         bitLength = bl;
118     }
119 
120     static int getTotalBits(const(QrSegment)[] segs, int vers)
121     {
122         enforce(1 <= vers && vers <= 40, "unknown vers");
123         int result = 0;
124         foreach (seg; segs)
125         {
126             int ccbits = seg.mode.numCharCountBits(vers);
127             if (seg.numChars >= (1 << ccbits))
128                 return -1;
129             result += 4 + ccbits + seg.bitLength;
130         }
131         return result;
132     }
133 
134 private:
135     enum byte[59] encodingTable = [
136         // SP,  !,  ",  #,  $,  %,  &,  ',  (,  ),  *,  +,  ,,  -,  .,  /,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  :,  ;,  <,  =,  >,  ?,  @,  // ASCII codes 32 to 64
137             36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 44, -1, -1, -1, -1, -1, -1,  // Array indices 0 to 32
138             10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,  // Array indices 33 to 58
139         //  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,  // ASCII codes 65 to 90
140         ];
141 }