File indexing completed on 2024-05-12 04:01:31
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: MIT 0005 */ 0006 0007 #include "code128barcode_p.h" 0008 0009 #include "barcodeutil_p.h" 0010 #include "bitvector_p.h" 0011 #include "prison_debug.h" 0012 0013 #include <QImage> 0014 #include <QPainter> 0015 0016 using namespace Prison; 0017 0018 enum { 0019 SymbolSize = 11, 0020 StopPatternSize = 13, 0021 StopPattern = 108, 0022 QuietZone = 10, 0023 }; 0024 0025 enum CodeSet : uint8_t { 0026 CodeSetA = 0, 0027 CodeSetB = 1, 0028 CodeSetC = 2, 0029 CodeSetUnknown = 3, 0030 }; 0031 0032 enum CodeSetOp : uint8_t { 0033 None = 255, 0034 StartA = 103, 0035 StartB = 104, 0036 StartC = 105, 0037 Shift = 98, 0038 LatchA = 101, 0039 LatchB = 100, 0040 LatchC = 99, 0041 }; 0042 0043 Code128Barcode::Code128Barcode() 0044 : AbstractBarcodePrivate(Barcode::OneDimension) 0045 { 0046 } 0047 Code128Barcode::~Code128Barcode() = default; 0048 0049 QImage Code128Barcode::paintImage() 0050 { 0051 const auto bits = encode(BarCodeUtil::asLatin1ByteArray(m_data)); 0052 const auto width = bits.size() + 2 * QuietZone; 0053 0054 QImage img(width, 1, QImage::Format_ARGB32); 0055 img.fill(m_background); 0056 QPainter p(&img); 0057 for (int i = 0; i < bits.size(); ++i) { 0058 if (bits.at(i)) { 0059 img.setPixel(QuietZone + i, 0, m_foreground.rgb()); 0060 } 0061 } 0062 0063 return img; 0064 } 0065 0066 // Code 128 symbol table 0067 static const uint16_t code128_symbols[] = { 0068 0b11011001100, // 0 0069 0b11001101100, 0070 0b11001100110, 0071 0b10010011000, 0072 0b10010001100, 0073 0b10001001100, 0074 0b10011001000, 0075 0b10011000100, 0076 0b10001100100, 0077 0b11001001000, 0078 0b11001000100, // 10 0079 0b11000100100, 0080 0b10110011100, 0081 0b10011011100, 0082 0b10011001110, 0083 0b10111001100, 0084 0b10011101100, 0085 0b10011100110, 0086 0b11001110010, 0087 0b11001011100, 0088 0b11001001110, // 20 0089 0b11011100100, 0090 0b11001110100, 0091 0b11101101110, 0092 0b11101001100, 0093 0b11100101100, 0094 0b11100100110, 0095 0b11101100100, 0096 0b11100110100, 0097 0b11100110010, 0098 0b11011011000, // 30 0099 0b11011000110, 0100 0b11000110110, 0101 0b10100011000, 0102 0b10001011000, 0103 0b10001000110, 0104 0b10110001000, 0105 0b10001101000, 0106 0b10001100010, 0107 0b11010001000, 0108 0b11000101000, // 40 0109 0b11000100010, 0110 0b10110111000, 0111 0b10110001110, 0112 0b10001101110, 0113 0b10111011000, 0114 0b10111000110, 0115 0b10001110110, 0116 0b11101110110, 0117 0b11010001110, 0118 0b11000101110, // 50 0119 0b11011101000, 0120 0b11011100010, 0121 0b11011101110, 0122 0b11101011000, 0123 0b11101000110, 0124 0b11100010110, 0125 0b11101101000, 0126 0b11101100010, 0127 0b11100011010, 0128 0b11101111010, // 60 0129 0b11001000010, 0130 0b11110001010, 0131 0b10100110000, 0132 0b10100001100, 0133 0b10010110000, 0134 0b10010000110, 0135 0b10000101100, 0136 0b10000100110, 0137 0b10110010000, 0138 0b10110000100, // 70 0139 0b10011010000, 0140 0b10011000010, 0141 0b10000110100, 0142 0b10000110010, 0143 0b11000010010, 0144 0b11001010000, 0145 0b11110111010, 0146 0b11000010100, 0147 0b10001111010, 0148 0b10100111100, // 80 0149 0b10010111100, 0150 0b10010011110, 0151 0b10111100100, 0152 0b10011110100, 0153 0b10011110010, 0154 0b11110100100, 0155 0b11110010100, 0156 0b11110010010, 0157 0b11011011110, 0158 0b11011110110, // 90 0159 0b11110110110, 0160 0b10101111000, 0161 0b10100011110, 0162 0b10001011110, 0163 0b10111101000, 0164 0b10111100010, 0165 0b11110101000, 0166 0b11110100010, 0167 0b10111011110, 0168 0b10111101110, // 100 0169 0b11101011110, 0170 0b11110101110, 0171 0b11010000100, 0172 0b11010010000, 0173 0b11010011100, 0174 0b11000111010, 0175 0b11010111000, 0176 0b1100011101011, 0177 }; 0178 0179 static uint8_t symbolForCharacter(const QByteArray &data, int index, CodeSet set) 0180 { 0181 const auto c1 = data.at(index); 0182 switch (set) { 0183 case CodeSetA: 0184 return (c1 < ' ') ? c1 + 64 : c1 - ' '; 0185 case CodeSetB: 0186 return c1 - ' '; 0187 case CodeSetC: { 0188 const auto c2 = data.at(index + 1); 0189 return ((c1 - '0') * 10) + c2 - '0'; 0190 } 0191 case CodeSetUnknown: 0192 Q_UNREACHABLE(); 0193 } 0194 0195 Q_UNREACHABLE(); 0196 return {}; 0197 } 0198 0199 struct CodeSetChange { 0200 CodeSet set; 0201 CodeSetOp symbol; 0202 }; 0203 0204 static bool isInCodeSetA(char c) 0205 { 0206 return c <= 95; 0207 } 0208 0209 static bool isInCodeSetB(char c) 0210 { 0211 // ### this does not consider FNC4 high byte encoding 0212 return c >= 32; 0213 } 0214 0215 static CodeSetChange opForData(const QByteArray &data, int index, CodeSet currentSet) 0216 { 0217 // determine if Code C makes sense at this point 0218 int codeC = 0; 0219 for (int i = index; i < data.size(); ++i, ++codeC) { 0220 if (data.at(i) < '0' || data.at(i) > '9') { 0221 break; 0222 } 0223 } 0224 if (currentSet == CodeSetC && codeC >= 2) { // already in C 0225 return {CodeSetC, None}; 0226 } 0227 if (codeC >= 6 // that's always good enough 0228 || (index == 0 && codeC >= 4) // beginning of data 0229 || (index + codeC == data.size() && codeC >= 4) // end of data 0230 || (codeC == data.size() && codeC == 2) // 2 ... 0231 || (codeC == data.size() && codeC == 4)) // ... or 4 as the entire data 0232 { 0233 return currentSet == CodeSetUnknown ? CodeSetChange{CodeSetC, StartC} : CodeSetChange{CodeSetC, LatchC}; 0234 } 0235 0236 // if we are in Code A or Code B, check if we need to switch for the next char 0237 // this is a shortcut to prevent the below more extensive search from making this O(n²) in the common case 0238 if ((currentSet == CodeSetA && isInCodeSetA(data.at(index))) || (currentSet == CodeSetB && isInCodeSetB(data.at(index)))) { 0239 return {currentSet, None}; 0240 } 0241 0242 // we need to switch to A or B, select which one, and select whether to use start, shift or latch 0243 const auto nextA = isInCodeSetA(data.at(index)); 0244 const auto nextB = isInCodeSetB(data.at(index)); 0245 0246 // count how many following characters we could encode in A or B 0247 int countA = 0; 0248 for (int i = index + 1; i < data.size(); ++i, ++countA) { 0249 if (!isInCodeSetA(data.at(i))) { 0250 break; 0251 } 0252 } 0253 int countB = 0; 0254 for (int i = index + 1; i < data.size(); ++i, ++countB) { 0255 if (!isInCodeSetB(data.at(i))) { 0256 break; 0257 } 0258 } 0259 0260 // select how we want to switch to Code A or Code B, biased to B as that's the more useful one in general 0261 switch (currentSet) { 0262 case CodeSetUnknown: 0263 // if we are at the start, take whichever code will get us further, or the only one that works 0264 if (nextA && nextB) { 0265 return countA > countB ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB}; 0266 } 0267 return nextA ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB}; 0268 case CodeSetC: 0269 // same for Code C 0270 if (nextA && nextB) { 0271 return countA > countB ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB}; 0272 } 0273 return nextA ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB}; 0274 case CodeSetA: 0275 // switch or latch to B? 0276 return CodeSetChange{CodeSetB, countB >= countA ? LatchB : Shift}; 0277 case CodeSetB: 0278 // switch or latch to A? 0279 return CodeSetChange{CodeSetA, countA > countB ? LatchA : Shift}; 0280 } 0281 0282 Q_UNREACHABLE(); 0283 return CodeSetChange{currentSet, None}; 0284 } 0285 0286 BitVector Code128Barcode::encode(const QByteArray &data) const 0287 { 0288 BitVector v; 0289 if (data.isEmpty()) { 0290 return v; 0291 } 0292 0293 // determine code set for start 0294 const auto op = opForData(data, 0, CodeSetUnknown); 0295 auto currentSet = op.set; 0296 0297 // write start code 0298 qCDebug(Log) << "start symbol:" << op.symbol << code128_symbols[op.symbol]; 0299 v.appendMSB(code128_symbols[op.symbol], SymbolSize); 0300 0301 uint32_t checksum = op.symbol; 0302 uint32_t checksumWeight = 1; 0303 0304 for (int i = 0; i < data.size(); i += currentSet == CodeSetC ? 2 : 1) { 0305 if (static_cast<uint8_t>(data.at(i)) > 127) { // FNC4 encoding not implemented yet 0306 continue; 0307 } 0308 0309 // perform code switch if needed 0310 const auto op = opForData(data, i, currentSet); 0311 if (op.symbol != None) { 0312 qCDebug(Log) << "op symbol:" << op.symbol << code128_symbols[op.symbol]; 0313 v.appendMSB(code128_symbols[op.symbol], SymbolSize); 0314 checksum += op.symbol * checksumWeight++; 0315 } 0316 0317 // encode current symbol 0318 const auto symbol = symbolForCharacter(data, i, op.set); 0319 qCDebug(Log) << "data symbol:" << symbol << code128_symbols[symbol]; 0320 v.appendMSB(code128_symbols[symbol], SymbolSize); 0321 checksum += symbol * checksumWeight++; 0322 0323 // update current code set 0324 if (op.symbol != Shift) { 0325 currentSet = op.set; 0326 } 0327 } 0328 0329 // encode checksum 0330 qCDebug(Log) << "checksum:" << checksum << code128_symbols[checksum % 103]; 0331 v.appendMSB(code128_symbols[checksum % 103], SymbolSize); 0332 0333 // add stop pattern 0334 v.appendMSB(code128_symbols[StopPattern], StopPatternSize); 0335 return v; 0336 }