/* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors * Copyright 2020 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #include "ODDataBarReader.h" #include "BarcodeFormat.h" #include "DecoderResult.h" #include "DetectorResult.h" #include "GTIN.h" #include "ODDataBarCommon.h" #include "Barcode.h" #include #include namespace ZXing::OneD { using namespace DataBar; static bool IsCharacterPair(PatternView v, int modsLeft, int modsRight) { float modSizeRef = ModSizeFinder(v); return IsCharacter(LeftChar(v), modsLeft, modSizeRef) && IsCharacter(RightChar(v), modsRight, modSizeRef); } static bool IsLeftPair(const PatternView& v) { return IsFinder(v[8], v[9], v[10], v[11], v[12]) && IsGuard(v[-1], v[11]) && IsCharacterPair(v, 16, 15); } static bool IsRightPair(const PatternView& v) { return IsFinder(v[12], v[11], v[10], v[9], v[8]) && IsGuard(v[9], v[21]) && IsCharacterPair(v, 15, 16); } static Character ReadDataCharacter(const PatternView& view, bool outsideChar, bool rightPair) { constexpr int OUTSIDE_EVEN_TOTAL_SUBSET[] = {1, 10, 34, 70, 126}; constexpr int INSIDE_ODD_TOTAL_SUBSET[] = {4, 20, 48, 81}; constexpr int OUTSIDE_GSUM[] = {0, 161, 961, 2015, 2715}; constexpr int INSIDE_GSUM[] = {0, 336, 1036, 1516}; constexpr int OUTSIDE_ODD_WIDEST[] = {8, 6, 4, 3, 1}; constexpr int INSIDE_ODD_WIDEST[] = {2, 4, 6, 8}; Array4I oddPattern = {}, evnPattern = {}; if (!ReadDataCharacterRaw(view, outsideChar ? 16 : 15, outsideChar == rightPair, oddPattern, evnPattern)) return {}; auto calcChecksumPortion = [](const Array4I& counts) { int res = 0; for (auto it = counts.rbegin(); it != counts.rend(); ++it) res = 9 * res + *it; return res; }; int checksumPortion = calcChecksumPortion(oddPattern) + 3 * calcChecksumPortion(evnPattern); if (outsideChar) { int oddSum = Reduce(oddPattern); assert((oddSum & 1) == 0 && oddSum <= 12 && oddSum >= 4); // checked in ReadDataCharacterRaw int group = (12 - oddSum) / 2; int oddWidest = OUTSIDE_ODD_WIDEST[group]; int evnWidest = 9 - oddWidest; int vOdd = GetValue(oddPattern, oddWidest, false); int vEvn = GetValue(evnPattern, evnWidest, true); int tEvn = OUTSIDE_EVEN_TOTAL_SUBSET[group]; int gSum = OUTSIDE_GSUM[group]; return {vOdd * tEvn + vEvn + gSum, checksumPortion}; } else { int evnSum = Reduce(evnPattern); assert((evnSum & 1) == 0 && evnSum <= 12 && evnSum >= 4); // checked in ReadDataCharacterRaw int group = (10 - evnSum) / 2; int oddWidest = INSIDE_ODD_WIDEST[group]; int evnWidest = 9 - oddWidest; int vOdd = GetValue(oddPattern, oddWidest, true); int vEvn = GetValue(evnPattern, evnWidest, false); int tOdd = INSIDE_ODD_TOTAL_SUBSET[group]; int gSum = INSIDE_GSUM[group]; return {vEvn * tOdd + vOdd + gSum, checksumPortion}; } } int ParseFinderPattern(const PatternView& view, bool reversed) { static constexpr std::array, 10> FINDER_PATTERNS = {{ {3, 8, 2, 1, 1}, {3, 5, 5, 1, 1}, {3, 3, 7, 1, 1}, {3, 1, 9, 1, 1}, {2, 7, 4, 1, 1}, {2, 5, 6, 1, 1}, {2, 3, 8, 1, 1}, {1, 5, 7, 1, 1}, {1, 3, 9, 1, 1}, }}; // TODO: c++20 constexpr inversion from FIND_PATTERN? static constexpr std::array, 10> REVERSED_FINDER_PATTERNS = {{ {1, 1, 2, 8, 3}, {1, 1, 5, 5, 3}, {1, 1, 7, 3, 3}, {1, 1, 9, 1, 3}, {1, 1, 4, 7, 2}, {1, 1, 6, 5, 2}, {1, 1, 8, 3, 2}, {1, 1, 7, 5, 1}, {1, 1, 9, 3, 1}, }}; return ParseFinderPattern(view, reversed, FINDER_PATTERNS, REVERSED_FINDER_PATTERNS); } static Pair ReadPair(const PatternView& view, bool rightPair) { if (int pattern = ParseFinderPattern(Finder(view), rightPair)) if (auto outside = ReadDataCharacter(rightPair ? RightChar(view) : LeftChar(view), true, rightPair)) if (auto inside = ReadDataCharacter(rightPair ? LeftChar(view) : RightChar(view), false, rightPair)) { // include left and right guards int xStart = view.pixelsInFront() - view[-1]; int xStop = view.pixelsTillEnd() + 2 * view[FULL_PAIR_SIZE]; return {outside, inside, pattern, xStart, xStop}; } return {}; } static bool ChecksumIsValid(Pair leftPair, Pair rightPair) { auto checksum = [](Pair p) { return p.left.checksum + 4 * p.right.checksum; }; int a = (checksum(leftPair) + 16 * checksum(rightPair)) % 79; int b = 9 * (std::abs(leftPair.finder) - 1) + (std::abs(rightPair.finder) - 1); if (b > 72) b--; if (b > 8) b--; return a == b; } static std::string ConstructText(Pair leftPair, Pair rightPair) { auto value = [](Pair p) { return 1597 * p.left.value + p.right.value; }; auto res = 4537077LL * value(leftPair) + value(rightPair); if (res >= 10000000000000LL) { // Strip 2D linkage flag (GS1 Composite) if any (ISO/IEC 24724:2011 Section 5.2.3) res -= 10000000000000LL; assert(res <= 9999999999999LL); // 13 digits } auto txt = ToString(res, 13); return txt + GTIN::ComputeCheckDigit(txt); } struct State : public RowReader::DecodingState { std::unordered_set leftPairs; std::unordered_set rightPairs; }; Barcode DataBarReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const { #if 0 // non-stacked version next = next.subView(-1, FULL_PAIR_SIZE + 1); // +1 reflects the guard pattern on the right, see IsRightPair()); // yes: the first view we test is at index 1 (black bar at 0 would be the guard pattern) while (next.shift(2)) { if (IsLeftPair(next)) { if (auto leftPair = ReadPair(next, false); leftPair && next.shift(FULL_PAIR_SIZE) && IsRightPair(next)) { if (auto rightPair = ReadPair(next, true); rightPair && ChecksumIsValid(leftPair, rightPair)) { return {ConstructText(leftPair, rightPair), rowNumber, leftPair.xStart, rightPair.xStop, BarcodeFormat::DataBar}; } } } } #else if (!state) state.reset(new State); auto* prevState = static_cast(state.get()); next = next.subView(0, FULL_PAIR_SIZE + 1); // +1 reflects the guard pattern on the right, see IsRightPair() // yes: the first view we test is at index 1 (black bar at 0 would be the guard pattern) while (next.shift(1)) { if (IsLeftPair(next)) { if (auto leftPair = ReadPair(next, false)) { leftPair.y = rowNumber; prevState->leftPairs.insert(leftPair); next.shift(FULL_PAIR_SIZE - 1); } } if (next.shift(1) && IsRightPair(next)) { if (auto rightPair = ReadPair(next, true)) { rightPair.y = rowNumber; prevState->rightPairs.insert(rightPair); next.shift(FULL_PAIR_SIZE + 2); } } } for (const auto& leftPair : prevState->leftPairs) for (const auto& rightPair : prevState->rightPairs) if (ChecksumIsValid(leftPair, rightPair)) { // Symbology identifier ISO/IEC 24724:2011 Section 9 and GS1 General Specifications 5.1.3 Figure 5.1.3-2 Barcode res{DecoderResult(Content(ByteArray(ConstructText(leftPair, rightPair)), {'e', '0'})) .setLineCount(EstimateLineCount(leftPair, rightPair)), {{}, EstimatePosition(leftPair, rightPair)}, BarcodeFormat::DataBar}; prevState->leftPairs.erase(leftPair); prevState->rightPairs.erase(rightPair); return res; } #endif // guarantee progress (see loop in ODReader.cpp) next = {}; return {}; } } // namespace ZXing::OneD