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