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 }