#include "ODMultiUPCEANReader.h"
#include "BarcodeFormat.h"
#include "BitArray.h"
#include "ReaderOptions.h"
#include "GTIN.h"
#include "ODUPCEANCommon.h"
#include "Barcode.h"
#include "JSON.h"
#include "SymbologyIdentifier.h"
#include <cmath>
namespace ZXing::OneD {
constexpr int CHAR_LEN = 4;
constexpr auto END_PATTERN = FixedPattern<3, 3>{1, 1, 1};
constexpr auto MID_PATTERN = FixedPattern<5, 5>{1, 1, 1, 1, 1};
constexpr auto UPCE_END_PATTERN = FixedPattern<6, 6>{1, 1, 1, 1, 1, 1};
constexpr auto EXT_START_PATTERN = FixedPattern<3, 4>{1, 1, 2};
constexpr auto EXT_SEPARATOR_PATTERN = FixedPattern<2, 2>{1, 1};
static const int FIRST_DIGIT_ENCODINGS[] = {0x00, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A};
constexpr float QUIET_ZONE_LEFT = 6;
constexpr float QUIET_ZONE_RIGHT_EAN = 3; constexpr float QUIET_ZONE_RIGHT_UPC = 6;
constexpr float QUIET_ZONE_ADDON = 3;
static bool DecodeDigit(const PatternView& view, std::string& txt, int* lgPattern = nullptr)
{
#if 1
static constexpr float MAX_AVG_VARIANCE = 0.48f;
static constexpr float MAX_INDIVIDUAL_VARIANCE = 0.7f;
int bestMatch =
lgPattern ? RowReader::DecodeDigit(view, UPCEANCommon::L_AND_G_PATTERNS, MAX_AVG_VARIANCE, MAX_INDIVIDUAL_VARIANCE, false)
: RowReader::DecodeDigit(view, UPCEANCommon::L_PATTERNS, MAX_AVG_VARIANCE, MAX_INDIVIDUAL_VARIANCE, false);
if (bestMatch == -1)
return false;
txt += ToDigit(bestMatch % 10);
if (lgPattern)
AppendBit(*lgPattern, bestMatch >= 10);
return true;
#else#endif
}
static bool DecodeDigits(int digitCount, PatternView& next, std::string& txt, int* lgPattern = nullptr)
{
for (int j = 0; j < digitCount; ++j, next.skipSymbol())
if (!DecodeDigit(next, txt, lgPattern))
return false;
return true;
}
struct PartialResult
{
std::string txt;
PatternView end;
BarcodeFormat format = BarcodeFormat::None;
PartialResult() { txt.reserve(14); }
bool isValid() const { return format != BarcodeFormat::None; }
};
bool _ret_false_debug_helper()
{
return false;
}
#define CHECK(A) if(!(A)) return _ret_false_debug_helper();
static bool EAN13(PartialResult& res, PatternView begin)
{
auto mid = begin.subView(27, MID_PATTERN.size());
auto end = begin.subView(56, END_PATTERN.size());
CHECK(IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT_EAN) && IsPattern(mid, MID_PATTERN));
auto next = begin.subView(END_PATTERN.size(), CHAR_LEN);
res.txt = " "; int lgPattern = 0;
CHECK(DecodeDigits(6, next, res.txt, &lgPattern));
next = next.subView(MID_PATTERN.size(), CHAR_LEN);
CHECK(DecodeDigits(6, next, res.txt));
int i = IndexOf(FIRST_DIGIT_ENCODINGS, lgPattern);
CHECK(i != -1);
res.txt[0] = ToDigit(i);
res.end = end;
res.format = BarcodeFormat::EAN13;
return true;
}
static bool PlausibleDigitModuleSize(PatternView begin, int start, int i, float moduleSizeRef)
{
float moduleSizeData = begin.subView(start + i * 4, 4).sum() / 7.f;
return std::abs(moduleSizeData / moduleSizeRef - 1) < 0.2f;
}
static bool EAN8(PartialResult& res, PatternView begin)
{
auto mid = begin.subView(19, MID_PATTERN.size());
auto end = begin.subView(40, END_PATTERN.size());
CHECK(IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT_EAN) && IsPattern(mid, MID_PATTERN));
float moduleSizeGuard = (begin.sum() + mid.sum() + end.sum()) / 11.f;
for (auto start : {3, 24})
for (int i = 0; i < 4; ++i)
CHECK(PlausibleDigitModuleSize(begin, start, i, moduleSizeGuard));
auto next = begin.subView(END_PATTERN.size(), CHAR_LEN);
res.txt.clear();
CHECK(DecodeDigits(4, next, res.txt));
next = next.subView(MID_PATTERN.size(), CHAR_LEN);
CHECK(DecodeDigits(4, next, res.txt));
res.end = end;
res.format = BarcodeFormat::EAN8;
return true;
}
static bool UPCE(PartialResult& res, PatternView begin)
{
auto end = begin.subView(27, UPCE_END_PATTERN.size());
CHECK(IsRightGuard(end, UPCE_END_PATTERN, QUIET_ZONE_RIGHT_UPC));
float moduleSizeGuard = (begin.sum() + end.sum()) / 9.f;
for (int i = 0; i < 6; ++i)
CHECK(PlausibleDigitModuleSize(begin, 3, i, moduleSizeGuard));
auto next = begin.subView(END_PATTERN.size(), CHAR_LEN);
int lgPattern = 0;
res.txt = " ";
CHECK(DecodeDigits(6, next, res.txt, &lgPattern));
int i = IndexOf(UPCEANCommon::NUMSYS_AND_CHECK_DIGIT_PATTERNS, lgPattern);
CHECK(i != -1);
res.txt[0] = ToDigit(i / 10);
res.txt += ToDigit(i % 10);
res.end = end;
res.format = BarcodeFormat::UPCE;
return true;
}
static int Ean5Checksum(const std::string& s)
{
int sum = 0, N = Size(s);
for (int i = N - 2; i >= 0; i -= 2)
sum += s[i] - '0';
sum *= 3;
for (int i = N - 1; i >= 0; i -= 2)
sum += s[i] - '0';
sum *= 3;
return sum % 10;
}
static bool AddOn(PartialResult& res, PatternView begin, int digitCount)
{
auto ext = begin.subView(0, 3 + digitCount * 4 + (digitCount - 1) * 2);
CHECK(ext.isValid());
auto moduleSize = IsPattern(ext, EXT_START_PATTERN);
CHECK(moduleSize);
CHECK(ext.isAtLastBar() || *ext.end() > QUIET_ZONE_ADDON * moduleSize - 1);
res.end = ext;
ext = ext.subView(EXT_START_PATTERN.size(), CHAR_LEN);
int lgPattern = 0;
res.txt.clear();
for (int i = 0; i < digitCount; ++i) {
CHECK(DecodeDigit(ext, res.txt, &lgPattern));
ext.skipSymbol();
if (i < digitCount - 1) {
CHECK(IsPattern(ext, EXT_SEPARATOR_PATTERN, 0, 0, moduleSize));
ext.skipPair();
}
}
if (digitCount == 2) {
CHECK(std::stoi(res.txt) % 4 == lgPattern);
} else {
constexpr int CHECK_DIGIT_ENCODINGS[] = {0x18, 0x14, 0x12, 0x11, 0x0C, 0x06, 0x03, 0x0A, 0x09, 0x05};
CHECK(Ean5Checksum(res.txt) == IndexOf(CHECK_DIGIT_ENCODINGS, lgPattern));
}
res.format = BarcodeFormat::EANUPC; return true;
}
BarcodeData MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr<RowReader::DecodingState>&) const
{
const int minSize = 3 + 6*4 + 6;
next = FindLeftGuard(next, minSize, END_PATTERN, QUIET_ZONE_LEFT);
if (!next.isValid())
return {};
PartialResult res;
auto begin = next;
if (!(((_opts.hasFormat(BarcodeFormat::EAN13 | BarcodeFormat::UPCA)) && EAN13(res, begin)) ||
(_opts.hasFormat(BarcodeFormat::EAN8) && EAN8(res, begin)) ||
(_opts.hasFormat(BarcodeFormat::UPCE) && UPCE(res, begin))))
return {};
std::string upceTxt;
if (res.format == BarcodeFormat::UPCE) {
upceTxt = res.txt;
res.txt = "0" + UPCEANCommon::ConvertUPCEtoUPCA(res.txt);
}
Error error = !GTIN::IsCheckDigitValid(res.txt) ? ChecksumError() : Error();
if (res.format == BarcodeFormat::EAN13 && !_opts.hasFormat(BarcodeFormat::EAN13)) {
if (res.txt.front() == '0')
res.format = BarcodeFormat::UPCA;
else
return {};
}
SymbologyIdentifier symbologyIdentifier = {'E', res.format == BarcodeFormat::EAN8 ? '4' : '0'};
next = res.end;
auto ext = res.end;
PartialResult addOnRes;
if (_opts.eanAddOnSymbol() != EanAddOnSymbol::Ignore && ext.skipSymbol()
&& ext.skipSingle(static_cast<int>(begin.sum() * 3.5)) && (AddOn(addOnRes, ext, 5) || AddOn(addOnRes, ext, 2))) {
res.txt += addOnRes.txt;
next = addOnRes.end;
symbologyIdentifier.modifier = '3'; }
if (_opts.eanAddOnSymbol() == EanAddOnSymbol::Require && !addOnRes.isValid())
return {};
return LinearBarcode(res.format, res.txt, rowNumber, begin.pixelsInFront(), next.pixelsTillEnd(), symbologyIdentifier, error,
JsonProp(BarcodeExtra::UPCE, upceTxt) + JsonProp(BarcodeExtra::EanAddOn, addOnRes.txt));
}
}