Prison

code128barcode.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7#include "code128barcode_p.h"
8
9#include "barcodeutil_p.h"
10#include "bitvector_p.h"
11#include "prison_debug.h"
12
13#include <QImage>
14#include <QPainter>
15
16using namespace Prison;
17
18enum {
19 SymbolSize = 11,
20 StopPatternSize = 13,
21 StopPattern = 108,
22 QuietZone = 10,
23};
24
25enum CodeSet : uint8_t {
26 CodeSetA = 0,
27 CodeSetB = 1,
28 CodeSetC = 2,
29 CodeSetUnknown = 3,
30};
31
32enum CodeSetOp : uint8_t {
33 None = 255,
34 StartA = 103,
35 StartB = 104,
36 StartC = 105,
37 Shift = 98,
38 LatchA = 101,
39 LatchB = 100,
40 LatchC = 99,
41};
42
43Code128Barcode::Code128Barcode()
44 : AbstractBarcodePrivate(Barcode::OneDimension)
45{
46}
47Code128Barcode::~Code128Barcode() = default;
48
49QImage Code128Barcode::paintImage()
50{
51 const auto bits = encode(BarCodeUtil::asLatin1ByteArray(m_data));
52 const auto width = bits.size() + 2 * QuietZone;
53
54 QImage img(width, 1, QImage::Format_ARGB32);
55 img.fill(m_background);
56 QPainter p(&img);
57 for (int i = 0; i < bits.size(); ++i) {
58 if (bits.at(i)) {
59 img.setPixel(QuietZone + i, 0, m_foreground.rgb());
60 }
61 }
62
63 return img;
64}
65
66// Code 128 symbol table
67static const uint16_t code128_symbols[] = {
68 0b11011001100, // 0
69 0b11001101100,
70 0b11001100110,
71 0b10010011000,
72 0b10010001100,
73 0b10001001100,
74 0b10011001000,
75 0b10011000100,
76 0b10001100100,
77 0b11001001000,
78 0b11001000100, // 10
79 0b11000100100,
80 0b10110011100,
81 0b10011011100,
82 0b10011001110,
83 0b10111001100,
84 0b10011101100,
85 0b10011100110,
86 0b11001110010,
87 0b11001011100,
88 0b11001001110, // 20
89 0b11011100100,
90 0b11001110100,
91 0b11101101110,
92 0b11101001100,
93 0b11100101100,
94 0b11100100110,
95 0b11101100100,
96 0b11100110100,
97 0b11100110010,
98 0b11011011000, // 30
99 0b11011000110,
100 0b11000110110,
101 0b10100011000,
102 0b10001011000,
103 0b10001000110,
104 0b10110001000,
105 0b10001101000,
106 0b10001100010,
107 0b11010001000,
108 0b11000101000, // 40
109 0b11000100010,
110 0b10110111000,
111 0b10110001110,
112 0b10001101110,
113 0b10111011000,
114 0b10111000110,
115 0b10001110110,
116 0b11101110110,
117 0b11010001110,
118 0b11000101110, // 50
119 0b11011101000,
120 0b11011100010,
121 0b11011101110,
122 0b11101011000,
123 0b11101000110,
124 0b11100010110,
125 0b11101101000,
126 0b11101100010,
127 0b11100011010,
128 0b11101111010, // 60
129 0b11001000010,
130 0b11110001010,
131 0b10100110000,
132 0b10100001100,
133 0b10010110000,
134 0b10010000110,
135 0b10000101100,
136 0b10000100110,
137 0b10110010000,
138 0b10110000100, // 70
139 0b10011010000,
140 0b10011000010,
141 0b10000110100,
142 0b10000110010,
143 0b11000010010,
144 0b11001010000,
145 0b11110111010,
146 0b11000010100,
147 0b10001111010,
148 0b10100111100, // 80
149 0b10010111100,
150 0b10010011110,
151 0b10111100100,
152 0b10011110100,
153 0b10011110010,
154 0b11110100100,
155 0b11110010100,
156 0b11110010010,
157 0b11011011110,
158 0b11011110110, // 90
159 0b11110110110,
160 0b10101111000,
161 0b10100011110,
162 0b10001011110,
163 0b10111101000,
164 0b10111100010,
165 0b11110101000,
166 0b11110100010,
167 0b10111011110,
168 0b10111101110, // 100
169 0b11101011110,
170 0b11110101110,
171 0b11010000100,
172 0b11010010000,
173 0b11010011100,
174 0b11000111010,
175 0b11010111000,
176 0b1100011101011,
177};
178
179static uint8_t symbolForCharacter(const QByteArray &data, int index, CodeSet set)
180{
181 const auto c1 = data.at(index);
182 switch (set) {
183 case CodeSetA:
184 return (c1 < ' ') ? c1 + 64 : c1 - ' ';
185 case CodeSetB:
186 return c1 - ' ';
187 case CodeSetC: {
188 const auto c2 = data.at(index + 1);
189 return ((c1 - '0') * 10) + c2 - '0';
190 }
191 case CodeSetUnknown:
192 Q_UNREACHABLE();
193 }
194
195 Q_UNREACHABLE();
196 return {};
197}
198
199struct CodeSetChange {
200 CodeSet set;
201 CodeSetOp symbol;
202};
203
204static bool isInCodeSetA(char c)
205{
206 return c <= 95;
207}
208
209static bool isInCodeSetB(char c)
210{
211 // ### this does not consider FNC4 high byte encoding
212 return c >= 32;
213}
214
215static CodeSetChange opForData(const QByteArray &data, int index, CodeSet currentSet)
216{
217 // determine if Code C makes sense at this point
218 int codeC = 0;
219 for (int i = index; i < data.size(); ++i, ++codeC) {
220 if (data.at(i) < '0' || data.at(i) > '9') {
221 break;
222 }
223 }
224 if (currentSet == CodeSetC && codeC >= 2) { // already in C
225 return {CodeSetC, None};
226 }
227 if (codeC >= 6 // that's always good enough
228 || (index == 0 && codeC >= 4) // beginning of data
229 || (index + codeC == data.size() && codeC >= 4) // end of data
230 || (codeC == data.size() && codeC == 2) // 2 ...
231 || (codeC == data.size() && codeC == 4)) // ... or 4 as the entire data
232 {
233 return currentSet == CodeSetUnknown ? CodeSetChange{CodeSetC, StartC} : CodeSetChange{CodeSetC, LatchC};
234 }
235
236 // if we are in Code A or Code B, check if we need to switch for the next char
237 // this is a shortcut to prevent the below more extensive search from making this O(n²) in the common case
238 if ((currentSet == CodeSetA && isInCodeSetA(data.at(index))) || (currentSet == CodeSetB && isInCodeSetB(data.at(index)))) {
239 return {currentSet, None};
240 }
241
242 // we need to switch to A or B, select which one, and select whether to use start, shift or latch
243 const auto nextA = isInCodeSetA(data.at(index));
244 const auto nextB = isInCodeSetB(data.at(index));
245
246 // count how many following characters we could encode in A or B
247 int countA = 0;
248 for (int i = index + 1; i < data.size(); ++i, ++countA) {
249 if (!isInCodeSetA(data.at(i))) {
250 break;
251 }
252 }
253 int countB = 0;
254 for (int i = index + 1; i < data.size(); ++i, ++countB) {
255 if (!isInCodeSetB(data.at(i))) {
256 break;
257 }
258 }
259
260 // select how we want to switch to Code A or Code B, biased to B as that's the more useful one in general
261 switch (currentSet) {
262 case CodeSetUnknown:
263 // if we are at the start, take whichever code will get us further, or the only one that works
264 if (nextA && nextB) {
265 return countA > countB ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB};
266 }
267 return nextA ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB};
268 case CodeSetC:
269 // same for Code C
270 if (nextA && nextB) {
271 return countA > countB ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB};
272 }
273 return nextA ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB};
274 case CodeSetA:
275 // switch or latch to B?
276 return CodeSetChange{CodeSetB, countB >= countA ? LatchB : Shift};
277 case CodeSetB:
278 // switch or latch to A?
279 return CodeSetChange{CodeSetA, countA > countB ? LatchA : Shift};
280 }
281
282 Q_UNREACHABLE();
283 return CodeSetChange{currentSet, None};
284}
285
286BitVector Code128Barcode::encode(const QByteArray &data) const
287{
288 BitVector v;
289 if (data.isEmpty()) {
290 return v;
291 }
292
293 // determine code set for start
294 const auto op = opForData(data, 0, CodeSetUnknown);
295 auto currentSet = op.set;
296
297 // write start code
298 qCDebug(Log) << "start symbol:" << op.symbol << code128_symbols[op.symbol];
299 v.appendMSB(code128_symbols[op.symbol], SymbolSize);
300
301 uint32_t checksum = op.symbol;
302 uint32_t checksumWeight = 1;
303
304 for (int i = 0; i < data.size(); i += currentSet == CodeSetC ? 2 : 1) {
305 if (static_cast<uint8_t>(data.at(i)) > 127) { // FNC4 encoding not implemented yet
306 continue;
307 }
308
309 // perform code switch if needed
310 const auto op = opForData(data, i, currentSet);
311 if (op.symbol != None) {
312 qCDebug(Log) << "op symbol:" << op.symbol << code128_symbols[op.symbol];
313 v.appendMSB(code128_symbols[op.symbol], SymbolSize);
314 checksum += op.symbol * checksumWeight++;
315 }
316
317 // encode current symbol
318 const auto symbol = symbolForCharacter(data, i, op.set);
319 qCDebug(Log) << "data symbol:" << symbol << code128_symbols[symbol];
320 v.appendMSB(code128_symbols[symbol], SymbolSize);
321 checksum += symbol * checksumWeight++;
322
323 // update current code set
324 if (op.symbol != Shift) {
325 currentSet = op.set;
326 }
327 }
328
329 // encode checksum
330 qCDebug(Log) << "checksum:" << checksum << code128_symbols[checksum % 103];
331 v.appendMSB(code128_symbols[checksum % 103], SymbolSize);
332
333 // add stop pattern
334 v.appendMSB(code128_symbols[StopPattern], StopPatternSize);
335 return v;
336}
A barcode generator for a fixed barcode format.
Definition barcode.h:40
Provides classes and methods for generating barcodes.
Definition barcode.h:24
char at(qsizetype i) const const
bool isEmpty() const const
qsizetype size() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:07 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.