1 /*
2     SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3 
4     SPDX-License-Identifier: MIT
5 */
6 
7 #include "code128barcode.h"
8 #include "bitvector_p.h"
9 #include "prison_debug.h"
10 
11 #include <QImage>
12 #include <QPainter>
13 
14 using namespace Prison;
15 
16 enum {
17     SymbolSize = 11,
18     StopPatternSize = 13,
19     StopPattern = 108,
20     QuietZone = 10,
21 };
22 
23 enum CodeSet : uint8_t {
24     CodeSetA = 0,
25     CodeSetB = 1,
26     CodeSetC = 2,
27     CodeSetUnknown = 3,
28 };
29 
30 enum CodeSetOp : uint8_t {
31     None = 255,
32     StartA = 103,
33     StartB = 104,
34     StartC = 105,
35     Shift = 98,
36     LatchA = 101,
37     LatchB = 100,
38     LatchC = 99,
39 };
40 
Code128Barcode()41 Code128Barcode::Code128Barcode()
42     : AbstractBarcode(AbstractBarcode::OneDimension)
43 {
44 }
45 Code128Barcode::~Code128Barcode() = default;
46 
paintImage(const QSizeF & size)47 QImage Code128Barcode::paintImage(const QSizeF &size)
48 {
49     Q_UNUSED(size);
50 
51     const auto bits = encode(data().isEmpty() ? byteArrayData() : data().toLatin1());
52     const auto width = bits.size() + 2 * QuietZone;
53 
54     QImage img(width, 1, QImage::Format_ARGB32);
55     img.fill(backgroundColor());
56     QPainter p(&img);
57     for (int i = 0; i < bits.size(); ++i) {
58         if (bits.at(i)) {
59             img.setPixel(QuietZone + i, 0, foregroundColor().rgb());
60         }
61     }
62 
63     return img;
64 }
65 
66 // Code 128 symbol table
67 // ### this is the perfect use-case for binary literals (as the binary pattern
68 // corresponds to the line pattern), adjust this once KF5 moves to C++14
69 static const uint16_t code128_symbols[] = {
70     1740, // 0b11011001100 // 0
71     1644, // 0b11001101100
72     1638, // 0b11001100110
73     1176, // 0b10010011000
74     1164, // 0b10010001100
75     1100, // 0b10001001100
76     1224, // 0b10011001000
77     1220, // 0b10011000100
78     1124, // 0b10001100100
79     1608, // 0b11001001000
80     1604, // 0b11001000100 // 10
81     1572, // 0b11000100100
82     1436, // 0b10110011100
83     1244, // 0b10011011100
84     1230, // 0b10011001110
85     1484, // 0b10111001100
86     1260, // 0b10011101100
87     1254, // 0b10011100110
88     1650, // 0b11001110010
89     1628, // 0b11001011100
90     1614, // 0b11001001110 // 20
91     1764, // 0b11011100100
92     1652, // 0b11001110100
93     1902, // 0b11101101110
94     1868, // 0b11101001100
95     1836, // 0b11100101100
96     1830, // 0b11100100110
97     1892, // 0b11101100100
98     1844, // 0b11100110100
99     1842, // 0b11100110010
100     1752, // 0b11011011000 // 30
101     1734, // 0b11011000110
102     1590, // 0b11000110110
103     1304, // 0b10100011000
104     1112, // 0b10001011000
105     1094, // 0b10001000110
106     1416, // 0b10110001000
107     1128, // 0b10001101000
108     1122, // 0b10001100010
109     1672, // 0b11010001000
110     1576, // 0b11000101000 // 40
111     1570, // 0b11000100010
112     1464, // 0b10110111000
113     1422, // 0b10110001110
114     1134, // 0b10001101110
115     1496, // 0b10111011000
116     1478, // 0b10111000110
117     1142, // 0b10001110110
118     1910, // 0b11101110110
119     1678, // 0b11010001110
120     1582, // 0b11000101110 // 50
121     1768, // 0b11011101000
122     1762, // 0b11011100010
123     1774, // 0b11011101110
124     1880, // 0b11101011000
125     1862, // 0b11101000110
126     1814, // 0b11100010110
127     1896, // 0b11101101000
128     1890, // 0b11101100010
129     1818, // 0b11100011010
130     1914, // 0b11101111010 // 60
131     1602, // 0b11001000010
132     1930, // 0b11110001010
133     1328, // 0b10100110000
134     1292, // 0b10100001100
135     1200, // 0b10010110000
136     1158, // 0b10010000110
137     1068, // 0b10000101100
138     1062, // 0b10000100110
139     1424, // 0b10110010000
140     1412, // 0b10110000100 // 70
141     1232, // 0b10011010000
142     1218, // 0b10011000010
143     1076, // 0b10000110100
144     1074, // 0b10000110010
145     1554, // 0b11000010010
146     1616, // 0b11001010000
147     1978, // 0b11110111010
148     1556, // 0b11000010100
149     1146, // 0b10001111010
150     1340, // 0b10100111100 // 80
151     1212, // 0b10010111100
152     1182, // 0b10010011110
153     1508, // 0b10111100100
154     1268, // 0b10011110100
155     1266, // 0b10011110010
156     1956, // 0b11110100100
157     1940, // 0b11110010100
158     1938, // 0b11110010010
159     1758, // 0b11011011110
160     1782, // 0b11011110110 // 90
161     1974, // 0b11110110110
162     1400, // 0b10101111000
163     1310, // 0b10100011110
164     1118, // 0b10001011110
165     1512, // 0b10111101000
166     1506, // 0b10111100010
167     1960, // 0b11110101000
168     1954, // 0b11110100010
169     1502, // 0b10111011110
170     1518, // 0b10111101110 // 100
171     1886, // 0b11101011110
172     1966, // 0b11110101110
173     1668, // 0b11010000100
174     1680, // 0b11010010000
175     1692, // 0b11010011100
176     1594, // 0b11000111010
177     1720, // 0b11010111000
178     6379 // 0b1100011101011
179 };
180 
symbolForCharacter(const QByteArray & data,int index,CodeSet set)181 static uint8_t symbolForCharacter(const QByteArray &data, int index, CodeSet set)
182 {
183     const auto c1 = data.at(index);
184     switch (set) {
185     case CodeSetA:
186         return (c1 < ' ') ? c1 + 64 : c1 - ' ';
187     case CodeSetB:
188         return c1 - ' ';
189     case CodeSetC: {
190         const auto c2 = data.at(index + 1);
191         return ((c1 - '0') * 10) + c2 - '0';
192     }
193     case CodeSetUnknown:
194         Q_UNREACHABLE();
195     }
196 
197     Q_UNREACHABLE();
198     return {};
199 }
200 
201 struct CodeSetChange {
202     CodeSet set;
203     CodeSetOp symbol;
204 };
205 
isInCodeSetA(char c)206 static bool isInCodeSetA(char c)
207 {
208     return c <= 95;
209 }
210 
isInCodeSetB(char c)211 static bool isInCodeSetB(char c)
212 {
213     // ### this does not consider FNC4 high byte encoding
214     return c >= 32;
215 }
216 
opForData(const QByteArray & data,int index,CodeSet currentSet)217 static CodeSetChange opForData(const QByteArray &data, int index, CodeSet currentSet)
218 {
219     // determine if Code C makes sense at this point
220     int codeC = 0;
221     for (int i = index; i < data.size(); ++i, ++codeC) {
222         if (data.at(i) < '0' || data.at(i) > '9') {
223             break;
224         }
225     }
226     if (currentSet == CodeSetC && codeC >= 2) { // already in C
227         return {CodeSetC, None};
228     }
229     if (codeC >= 6 // that's always good enough
230         || (index == 0 && codeC >= 4) // beginning of data
231         || (index + codeC == data.size() && codeC >= 4) // end of data
232         || (codeC == data.size() && codeC == 2) // 2 ...
233         || (codeC == data.size() && codeC == 4)) // ... or 4 as the entire data
234     {
235         return currentSet == CodeSetUnknown ? CodeSetChange{CodeSetC, StartC} : CodeSetChange{CodeSetC, LatchC};
236     }
237 
238     // if we are in Code A or Code B, check if we need to switch for the next char
239     // this is a shortcut to prevent the below more extensive search from making this O(n²) in the common case
240     if ((currentSet == CodeSetA && isInCodeSetA(data.at(index))) || (currentSet == CodeSetB && isInCodeSetB(data.at(index)))) {
241         return {currentSet, None};
242     }
243 
244     // we need to switch to A or B, select which one, and select whether to use start, shift or latch
245     const auto nextA = isInCodeSetA(data.at(index));
246     const auto nextB = isInCodeSetB(data.at(index));
247 
248     // count how many following characters we could encode in A or B
249     int countA = 0;
250     for (int i = index + 1; i < data.size(); ++i, ++countA) {
251         if (!isInCodeSetA(data.at(i))) {
252             break;
253         }
254     }
255     int countB = 0;
256     for (int i = index + 1; i < data.size(); ++i, ++countB) {
257         if (!isInCodeSetB(data.at(i))) {
258             break;
259         }
260     }
261 
262     // select how we want to switch to Code A or Code B, biased to B as that's the more useful one in general
263     switch (currentSet) {
264     case CodeSetUnknown:
265         // if we are at the start, take whichever code will get us further, or the only one that works
266         if (nextA && nextB) {
267             return countA > countB ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB};
268         }
269         return nextA ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB};
270     case CodeSetC:
271         // same for Code C
272         if (nextA && nextB) {
273             return countA > countB ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB};
274         }
275         return nextA ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB};
276     case CodeSetA:
277         // switch or latch to B?
278         return CodeSetChange{CodeSetB, countB >= countA ? LatchB : Shift};
279     case CodeSetB:
280         // switch or latch to A?
281         return CodeSetChange{CodeSetA, countA > countB ? LatchA : Shift};
282     }
283 
284     Q_UNREACHABLE();
285     return CodeSetChange{currentSet, None};
286 }
287 
encode(const QByteArray & data) const288 BitVector Code128Barcode::encode(const QByteArray &data) const
289 {
290     BitVector v;
291     if (data.isEmpty()) {
292         return v;
293     }
294 
295     // determine code set for start
296     const auto op = opForData(data, 0, CodeSetUnknown);
297     auto currentSet = op.set;
298 
299     // write start code
300     qCDebug(Log) << "start symbol:" << op.symbol << code128_symbols[op.symbol];
301     v.appendMSB(code128_symbols[op.symbol], SymbolSize);
302 
303     uint32_t checksum = op.symbol;
304     uint32_t checksumWeight = 1;
305 
306     for (int i = 0; i < data.size(); i += currentSet == CodeSetC ? 2 : 1) {
307         if (static_cast<uint8_t>(data.at(i)) > 127) { // FNC4 encoding not implemented yet
308             continue;
309         }
310 
311         // perform code switch if needed
312         const auto op = opForData(data, i, currentSet);
313         if (op.symbol != None) {
314             qCDebug(Log) << "op symbol:" << op.symbol << code128_symbols[op.symbol];
315             v.appendMSB(code128_symbols[op.symbol], SymbolSize);
316             checksum += op.symbol * checksumWeight++;
317         }
318 
319         // encode current symbol
320         const auto symbol = symbolForCharacter(data, i, op.set);
321         qCDebug(Log) << "data symbol:" << symbol << code128_symbols[symbol];
322         v.appendMSB(code128_symbols[symbol], SymbolSize);
323         checksum += symbol * checksumWeight++;
324 
325         // update current code set
326         if (op.symbol != Shift) {
327             currentSet = op.set;
328         }
329     }
330 
331     // encode checksum
332     qCDebug(Log) << "checksum:" << checksum << code128_symbols[checksum % 103];
333     v.appendMSB(code128_symbols[checksum % 103], SymbolSize);
334 
335     // add stop pattern
336     v.appendMSB(code128_symbols[StopPattern], StopPatternSize);
337     return v;
338 }
339