143 lines
5.3 KiB
C++
143 lines
5.3 KiB
C++
|
|
/*
|
||
|
|
* Copyright 2016 Nu-book Inc.
|
||
|
|
* Copyright 2016 ZXing authors
|
||
|
|
*/
|
||
|
|
// SPDX-License-Identifier: Apache-2.0
|
||
|
|
|
||
|
|
#include "ODCode39Reader.h"
|
||
|
|
|
||
|
|
#include "ReaderOptions.h"
|
||
|
|
#include "Barcode.h"
|
||
|
|
#include "ZXAlgorithms.h"
|
||
|
|
|
||
|
|
#include <array>
|
||
|
|
|
||
|
|
namespace ZXing::OneD {
|
||
|
|
|
||
|
|
static const char ALPHABET[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%*";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Each character consists of 5 bars and 4 spaces, 3 of which are wide (i.e. 6 are narrow).
|
||
|
|
* Each character is followed by a narrow space. The narrow to wide ratio is between 1:2 and 1:3.
|
||
|
|
* These represent the encodings of characters, as patterns of wide and narrow bars.
|
||
|
|
* The 9 least-significant bits of each int correspond to the pattern of wide and narrow,
|
||
|
|
* with 1s representing "wide" and 0s representing "narrow".
|
||
|
|
*/
|
||
|
|
static const int CHARACTER_ENCODINGS[] = {
|
||
|
|
0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9
|
||
|
|
0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J
|
||
|
|
0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T
|
||
|
|
0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$
|
||
|
|
0x0A2, 0x08A, 0x02A, 0x094 // /-% , *
|
||
|
|
};
|
||
|
|
|
||
|
|
static_assert(Size(ALPHABET) - 1 == Size(CHARACTER_ENCODINGS), "table size mismatch");
|
||
|
|
|
||
|
|
static const char PERCENTAGE_MAPPING[26] = {
|
||
|
|
'A' - 38, 'B' - 38, 'C' - 38, 'D' - 38, 'E' - 38, // %A to %E map to control codes ESC to USep
|
||
|
|
'F' - 11, 'G' - 11, 'H' - 11, 'I' - 11, 'J' - 11, // %F to %J map to ; < = > ?
|
||
|
|
'K' + 16, 'L' + 16, 'M' + 16, 'N' + 16, 'O' + 16, // %K to %O map to [ \ ] ^ _
|
||
|
|
'P' + 43, 'Q' + 43, 'R' + 43, 'S' + 43, 'T' + 43, // %P to %T map to { | } ~ DEL
|
||
|
|
'\0', '@', '`', // %U map to NUL, %V map to @, %W map to `
|
||
|
|
127, 127, 127 // %X to %Z all map to DEL (127)
|
||
|
|
};
|
||
|
|
|
||
|
|
using CounterContainer = std::array<int, 9>;
|
||
|
|
|
||
|
|
// each character has 5 bars and 4 spaces
|
||
|
|
constexpr int CHAR_LEN = 9;
|
||
|
|
|
||
|
|
/** Decode the full ASCII string. Return empty string if FormatError occurred.
|
||
|
|
* ctrl is either "$%/+" for code39 or "abcd" for code93. */
|
||
|
|
std::string DecodeCode39AndCode93FullASCII(std::string encoded, const char ctrl[4])
|
||
|
|
{
|
||
|
|
auto out = encoded.begin();
|
||
|
|
for (auto in = encoded.cbegin(); in != encoded.cend(); ++in) {
|
||
|
|
char c = *in;
|
||
|
|
if (Contains(ctrl, c)) {
|
||
|
|
char next = *++in; // if in is one short of cend(), then next == 0
|
||
|
|
if (next < 'A' || next > 'Z')
|
||
|
|
return {};
|
||
|
|
if (c == ctrl[0])
|
||
|
|
c = next - 64; // $A to $Z map to control codes SH to SB
|
||
|
|
else if (c == ctrl[1])
|
||
|
|
c = PERCENTAGE_MAPPING[next - 'A'];
|
||
|
|
else if (c == ctrl[2])
|
||
|
|
c = next - 32; // /A to /O map to ! to , and /Z maps to :
|
||
|
|
else
|
||
|
|
c = next + 32; // +A to +Z map to a to z
|
||
|
|
}
|
||
|
|
*out++ = c;
|
||
|
|
}
|
||
|
|
encoded.erase(out, encoded.end());
|
||
|
|
return encoded;
|
||
|
|
}
|
||
|
|
|
||
|
|
Barcode Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr<RowReader::DecodingState>&) const
|
||
|
|
{
|
||
|
|
// minimal number of characters that must be present (including start, stop and checksum characters)
|
||
|
|
int minCharCount = _opts.validateCode39CheckSum() ? 4 : 3;
|
||
|
|
auto isStartOrStopSymbol = [](char c) { return c == '*'; };
|
||
|
|
|
||
|
|
// provide the indices with the narrow bars/spaces which have to be equally wide
|
||
|
|
constexpr auto START_PATTERN = FixedSparcePattern<CHAR_LEN, 6>{0, 2, 3, 5, 7, 8};
|
||
|
|
// quiet zone is half the width of a character symbol
|
||
|
|
constexpr float QUIET_ZONE_SCALE = 0.5f;
|
||
|
|
|
||
|
|
next = FindLeftGuard(next, minCharCount * CHAR_LEN, START_PATTERN, QUIET_ZONE_SCALE * 12);
|
||
|
|
if (!next.isValid())
|
||
|
|
return {};
|
||
|
|
|
||
|
|
if (!isStartOrStopSymbol(DecodeNarrowWidePattern(next, CHARACTER_ENCODINGS, ALPHABET))) // read off the start pattern
|
||
|
|
return {};
|
||
|
|
|
||
|
|
int xStart = next.pixelsInFront();
|
||
|
|
int maxInterCharacterSpace = next.sum() / 2; // spec actually says 1 narrow space, width/2 is about 4
|
||
|
|
|
||
|
|
std::string txt;
|
||
|
|
txt.reserve(20);
|
||
|
|
|
||
|
|
do {
|
||
|
|
// check remaining input width and inter-character space
|
||
|
|
if (!next.skipSymbol() || !next.skipSingle(maxInterCharacterSpace))
|
||
|
|
return {};
|
||
|
|
|
||
|
|
txt += DecodeNarrowWidePattern(next, CHARACTER_ENCODINGS, ALPHABET);
|
||
|
|
if (txt.back() == 0)
|
||
|
|
return {};
|
||
|
|
} while (!isStartOrStopSymbol(txt.back()));
|
||
|
|
|
||
|
|
txt.pop_back(); // remove asterisk
|
||
|
|
|
||
|
|
// check txt length and whitespace after the last char. See also FindStartPattern.
|
||
|
|
if (Size(txt) < minCharCount - 2 || !next.hasQuietZoneAfter(QUIET_ZONE_SCALE))
|
||
|
|
return {};
|
||
|
|
|
||
|
|
auto lastChar = txt.back();
|
||
|
|
txt.pop_back();
|
||
|
|
int checksum = TransformReduce(txt, 0, [](char c) { return IndexOf(ALPHABET, c); });
|
||
|
|
bool hasValidCheckSum = lastChar == ALPHABET[checksum % 43];
|
||
|
|
if (!hasValidCheckSum)
|
||
|
|
txt.push_back(lastChar);
|
||
|
|
|
||
|
|
const char shiftChars[] = "$%/+";
|
||
|
|
auto fullASCII = _opts.tryCode39ExtendedMode() ? DecodeCode39AndCode93FullASCII(txt, shiftChars) : "";
|
||
|
|
bool hasFullASCII = !fullASCII.empty() && std::find_first_of(txt.begin(), txt.end(), shiftChars, shiftChars + 4) != txt.end();
|
||
|
|
if (hasFullASCII)
|
||
|
|
txt = fullASCII;
|
||
|
|
|
||
|
|
if (hasValidCheckSum)
|
||
|
|
txt.push_back(lastChar);
|
||
|
|
|
||
|
|
Error error = _opts.validateCode39CheckSum() && !hasValidCheckSum ? ChecksumError() : Error();
|
||
|
|
|
||
|
|
// Symbology identifier modifiers ISO/IEC 16388:2007 Annex C Table C.1
|
||
|
|
constexpr const char symbologyModifiers[4] = { '0', '1' /*checksum*/, '4' /*full ASCII*/, '5' /*checksum + full ASCII*/ };
|
||
|
|
SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)hasValidCheckSum + 2 * (int)hasFullASCII]};
|
||
|
|
|
||
|
|
int xStop = next.pixelsTillEnd();
|
||
|
|
return {std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier, error};
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace ZXing::OneD
|