Skip to content

Commit

Permalink
Add reader support for DataBarLimited
Browse files Browse the repository at this point in the history
This fixes zxing-cpp#534. Writing is also supported if the new zint backend is
enabled. This work has been fully sponsored by EUREKAM(@glebaccon-eurekam).

Note: the new code uses a c++20 feature, so it is only enabled when
compiled with a c++20 compiler.
  • Loading branch information
axxel committed Oct 11, 2024
1 parent 81407a0 commit 5fadfbc
Show file tree
Hide file tree
Showing 32 changed files with 303 additions and 30 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/ax
Named Sponsors:
* [KURZ Digital Solutions GmbH & Co. KG](https://github.com/kurzdigital)
* [Useful Sensors Inc](https://github.com/usefulsensors)
* [EUREKAM](https://eurecam.fr/)

Thanks a lot for your contribution!

Expand All @@ -34,20 +35,20 @@ Thanks a lot for your contribution!

## Supported Formats

| Linear product | Linear industrial | Matrix |
|----------------|-------------------|--------------------|
| UPC-A | Code 39 | QR Code |
| UPC-E | Code 93 | Micro QR Code |
| EAN-8 | Code 128 | rMQR Code |
| EAN-13 | Codabar | Aztec |
| DataBar | DataBar Expanded | DataMatrix |
| | DX Film Edge | PDF417 |
| | ITF | MaxiCode (partial) |
| Linear product | Linear industrial | Matrix |
|-----------------|-------------------|--------------------|
| UPC-A | Code 39 | QR Code |
| UPC-E | Code 93 | Micro QR Code |
| EAN-8 | Code 128 | rMQR Code |
| EAN-13 | Codabar | Aztec |
| DataBar | DataBar Expanded | DataMatrix |
| DataBar Limited | DX Film Edge | PDF417 |
| | ITF | MaxiCode (partial) |

[Note:]
* DataBar used to be called RSS.
* DataBar, DX Film Edge, MaxiCode, Micro QR Code and rMQR Code are not supported for writing.
* Building with only C++17 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behaviour of the library: it then lacks supports multi-symbol and position independent detection for DataMatrix.
* Building with only C++17 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behaviour of the library: it then lacks support for DataBarLimited and multi-symbol and position independent detection for DataMatrix.

## Getting Started

Expand Down
2 changes: 2 additions & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ if (ZXING_READERS)
src/oned/ODDataBarExpandedBitDecoder.cpp
src/oned/ODDataBarExpandedReader.h
src/oned/ODDataBarExpandedReader.cpp
src/oned/ODDataBarLimitedReader.h
src/oned/ODDataBarLimitedReader.cpp
src/oned/ODDXFilmEdgeReader.h
src/oned/ODDXFilmEdgeReader.cpp
src/oned/ODITFReader.h
Expand Down
1 change: 1 addition & 0 deletions core/src/BarcodeFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ static BarcodeFormatName NAMES[] = {
{BarcodeFormat::Code128, "Code128"},
{BarcodeFormat::DataBar, "DataBar"},
{BarcodeFormat::DataBarExpanded, "DataBarExpanded"},
{BarcodeFormat::DataBarLimited, "DataBarLimited"},
{BarcodeFormat::DataMatrix, "DataMatrix"},
{BarcodeFormat::DXFilmEdge, "DXFilmEdge"},
{BarcodeFormat::EAN8, "EAN-8"},
Expand Down
6 changes: 4 additions & 2 deletions core/src/BarcodeFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ enum class BarcodeFormat
MicroQRCode = (1 << 16), ///< Micro QR Code
RMQRCode = (1 << 17), ///< Rectangular Micro QR Code
DXFilmEdge = (1 << 18), ///< DX Film Edge Barcode
DataBarLimited = (1 << 19), ///< GS1 DataBar Limited

LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DXFilmEdge | UPCA | UPCE,
LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DataBarLimited
| DXFilmEdge | UPCA | UPCE,
MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode,
Any = LinearCodes | MatrixCodes,

_max = DXFilmEdge, ///> implementation detail, don't use
_max = DataBarLimited, ///> implementation detail, don't use
};

ZX_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat)
Expand Down
1 change: 1 addition & 0 deletions core/src/WriteBarcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ static constexpr BarcodeFormatZXing2Zint barcodeFormatZXing2Zint[] = {
{BarcodeFormat::Code128, BARCODE_CODE128},
{BarcodeFormat::DataBar, BARCODE_DBAR_OMN},
{BarcodeFormat::DataBarExpanded, BARCODE_DBAR_EXP},
{BarcodeFormat::DataBarLimited, BARCODE_DBAR_LTD},
{BarcodeFormat::DataMatrix, BARCODE_DATAMATRIX},
{BarcodeFormat::DXFilmEdge, -1},
{BarcodeFormat::EAN8, BARCODE_EANX},
Expand Down
4 changes: 3 additions & 1 deletion core/src/ZXingC.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,13 @@ typedef enum
ZXing_BarcodeFormat_MicroQRCode = (1 << 16),
ZXing_BarcodeFormat_RMQRCode = (1 << 17),
ZXing_BarcodeFormat_DXFilmEdge = (1 << 18),
ZXing_BarcodeFormat_DataBarLimited = (1 << 19),

ZXing_BarcodeFormat_LinearCodes = ZXing_BarcodeFormat_Codabar | ZXing_BarcodeFormat_Code39 | ZXing_BarcodeFormat_Code93
| ZXing_BarcodeFormat_Code128 | ZXing_BarcodeFormat_EAN8 | ZXing_BarcodeFormat_EAN13
| ZXing_BarcodeFormat_ITF | ZXing_BarcodeFormat_DataBar | ZXing_BarcodeFormat_DataBarExpanded
| ZXing_BarcodeFormat_DXFilmEdge | ZXing_BarcodeFormat_UPCA | ZXing_BarcodeFormat_UPCE,
| ZXing_BarcodeFormat_DataBarLimited | ZXing_BarcodeFormat_DXFilmEdge | ZXing_BarcodeFormat_UPCA
| ZXing_BarcodeFormat_UPCE,
ZXing_BarcodeFormat_MatrixCodes = ZXing_BarcodeFormat_Aztec | ZXing_BarcodeFormat_DataMatrix | ZXing_BarcodeFormat_MaxiCode
| ZXing_BarcodeFormat_PDF417 | ZXing_BarcodeFormat_QRCode | ZXing_BarcodeFormat_MicroQRCode
| ZXing_BarcodeFormat_RMQRCode,
Expand Down
11 changes: 4 additions & 7 deletions core/src/oned/ODDataBarCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ static int combins(int n, int r)
return val;
}

#ifdef __cpp_lib_span
int GetValue(const std::span<int> widths, int maxWidth, bool noNarrow)
#else
int GetValue(const Array4I& widths, int maxWidth, bool noNarrow)
#endif
{
int elements = Size(widths);
int n = Reduce(widths);
Expand Down Expand Up @@ -65,13 +69,6 @@ int GetValue(const Array4I& widths, int maxWidth, bool noNarrow)
return val;
}

template <typename T>
struct OddEven
{
T odd = {}, evn = {};
T& operator[](int i) { return i & 1 ? evn : odd; }
};

using Array4F = std::array<float, 4>;

bool ReadDataCharacterRaw(const PatternView& view, int numModules, bool reversed, Array4I& oddPattern,
Expand Down
15 changes: 15 additions & 0 deletions core/src/oned/ODDataBarCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
#include <array>
#include <cmath>

#if __has_include(<span>) // c++20
#include <span>
#endif

namespace ZXing::OneD::DataBar {

inline bool IsFinder(int a, int b, int c, int d, int e)
Expand Down Expand Up @@ -121,12 +125,23 @@ int ParseFinderPattern(const PatternView& view, bool reversed, T l2rPattern, T r
return reversed ? -i : i;
}

template <typename T>
struct OddEven
{
T odd = {}, evn = {};
T& operator[](int i) { return i & 1 ? evn : odd; }
};

using Array4I = std::array<int, 4>;

bool ReadDataCharacterRaw(const PatternView& view, int numModules, bool reversed, Array4I& oddPattern,
Array4I& evnPattern);

#ifdef __cpp_lib_span
int GetValue(const std::span<int> widths, int maxWidth, bool noNarrow);
#else
int GetValue(const Array4I& widths, int maxWidth, bool noNarrow);
#endif

Position EstimatePosition(const Pair& first, const Pair& last);
int EstimateLineCount(const Pair& first, const Pair& last);
Expand Down
199 changes: 199 additions & 0 deletions core/src/oned/ODDataBarLimitedReader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Copyright 2024 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0

#include "ODDataBarLimitedReader.h"

#include "BarcodeFormat.h"
#include "GTIN.h"
#include "ODDataBarCommon.h"
#include "Barcode.h"

//#define PRINT_DEBUG
#ifndef PRINT_DEBUG
#define printf(...){}
#define printv(...){}
#else
#define printv(fmt, ...) \
for (auto v : __VA_ARGS__) \
printf(fmt, v);
#endif

namespace ZXing::OneD {

using namespace DataBar;

constexpr int CHAR_LEN = 14;
constexpr int SYMBOL_LEN = 1 + 3 * CHAR_LEN + 2;

// elements() determines the element widths of an (n,k) character with
// at least one even-numbered element that's just one module wide.
// (Note: even-numbered elements - 2nd, 4th, 6th, etc., have odd indexes)
// for DataBarLimited: SUM=26/18, LEN=14
template <int LEN, int SUM>
std::array<int, LEN> NormalizedPatternFromE2E(const PatternView& view)
{
auto e2e = NormalizedE2EPattern<LEN, SUM>(view);
std::array<int, LEN> widths;

// derive element widths from normalized edge-to-similar-edge measurements
int barSum = widths[0] = 1; // first assume 1st bar is 1
for (int i = 0; i < Size(e2e); i++) {
widths[i + 1] = e2e[i] - widths[i];
barSum += widths[i + 1];
}
widths.back() = SUM - barSum; // last even element makes SUM modules

int minEven = widths[1];
for (int i = 3; i < Size(widths); i += 2)
minEven = std::min(minEven, widths[i]);

if (minEven > 1) {
// minimum even width is too big, readjust so minimum even is 1
for (int i = 0; i < Size(widths); i += 2) {
widths[i] += minEven - 1;
widths[i + 1] -= minEven - 1;
}
}

return widths;
}

static Character ReadDataCharacter(const PatternView& view)
{
constexpr int G_SUM[] = {0, 183064, 820064, 1000776, 1491021, 1979845, 1996939};
constexpr int T_EVEN[] = {28, 728, 6454, 203, 2408, 1, 16632};
constexpr int ODD_SUM[] = {17, 13, 9, 15, 11, 19, 7};
constexpr int ODD_WIDEST[] = {6, 5, 3, 5, 4, 8, 1};

auto pattern = NormalizedPatternFromE2E<14, 26>(view);

int checkSum = 0;
for (auto it = pattern.rbegin(); it != pattern.rend(); ++it)
checkSum = 3 * checkSum + *it;

using Array7I = std::array<int, 7>;
Array7I oddPattern = {}, evnPattern = {};
OddEven<Array7I&> res = {oddPattern, evnPattern};

for (int i = 0; i < Size(pattern); ++i)
res[i % 2][i / 2] = pattern[i];

printf(" o: ");
printv("%d ", oddPattern);
printf(" e: ");
printv("%d ", evnPattern);

int group = IndexOf(ODD_SUM, Reduce(oddPattern));
if (group == -1)
return {};

int oddWidest = ODD_WIDEST[group];
int evnWidest = 9 - oddWidest;
#ifndef __cpp_lib_span
#pragma message("DataBarLimited not supported without std::span<> (c++20 feature)")
int vOdd = 0;
int vEvn = 0;
#else
int vOdd = GetValue(oddPattern, oddWidest, false);
int vEvn = GetValue(evnPattern, evnWidest, true);
#endif
int tEvn = T_EVEN[group];
int gSum = G_SUM[group];

return {vOdd * tEvn + vEvn + gSum, checkSum};
}

static std::string ConstructText(Character left, Character right)
{
auto symVal = 2'013'571LL * left.value + right.value;

// Strip 2D linkage flag (GS1 Composite) if any (ISO/IEC 24724:2011 Section 6.2.3)
if (symVal >= 2'015'133'531'096LL) {
symVal -= 2'015'133'531'096LL;
assert(symVal <= 1'999'999'999'999LL); // 13 digits
}
auto txt = ToString(symVal, 13);
return "01" + txt + GTIN::ComputeCheckDigit(txt);
}

static inline bool Has26to18Ratio(int v26, int v18)
{
return v26 + 1.5 * v26 / 26 > v18 / 18. * 26. && v26 - 1.5 * v26 / 26 < v18 / 18. * 26.;
}

std::array<int, 89> CheckChars = {
0b10'10101010'11100010, 0b10'10101010'01110010, 0b10'10101010'00111010, 0b10'10101001'01110010, 0b10'10101001'00111010,
0b10'10101000'10111010, 0b10'10100101'01110010, 0b10'10100101'00111010, 0b10'10100100'10111010, 0b10'10100010'10111010,
0b10'10010101'01110010, 0b10'10010101'00111010, 0b10'10010100'10111010, 0b10'10010010'10111010, 0b10'10001010'10111010,
0b10'01010101'01110010, 0b10'01010101'00111010, 0b10'01010100'10111010, 0b10'01010010'10111010, 0b10'01001010'10111010,
0b10'00101010'10111010, 0b10'10101011'01100010, 0b10'10101011'00110010, 0b10'10101011'00011010, 0b10'10101001'10110010,
0b10'10101001'10011010, 0b10'10101000'11011010, 0b10'10100101'10110010, 0b10'10100101'10011010, 0b10'10100100'11011010,
0b10'10100010'11011010, 0b10'10010101'10110010, 0b10'10010101'10011010, 0b10'10010100'11011010, 0b10'10010010'11011010,
0b10'10001010'11011010, 0b10'01010101'10110010, 0b10'01010101'10011010, 0b10'01010100'11011010, 0b10'01010010'11011010,
0b10'01001010'11011010, 0b10'00101010'11011010, 0b10'10101011'10100010, 0b10'10101011'10010010, 0b10'10101001'11010010,
0b10'10010101'11010010, 0b10'01010101'11010010, 0b10'10101101'01100010, 0b10'10101101'00110010, 0b10'10101101'00011010,
0b10'10101100'10110010, 0b10'10010110'10110010, 0b10'10010110'10011010, 0b10'10010110'01011010, 0b10'10010011'01011010,
0b10'10001011'01011010, 0b10'01010110'10110010, 0b10'01010110'10011010, 0b10'01001011'01011010, 0b10'10110101'01100010,
0b10'10110101'00110010, 0b10'10110101'00011010, 0b10'10110100'10110010, 0b10'10110100'10011010, 0b10'10110010'10110010,
0b10'01011010'10110010, 0b10'01011010'10011010, 0b10'01011010'01011010, 0b10'01011001'01011010, 0b10'01001101'01011010,
0b10'00101101'01011010, 0b10'11010101'01100010, 0b10'11010101'00110010, 0b10'11010101'00011010, 0b10'11010100'10110010,
0b10'11010100'10011010, 0b10'11010100'01011010, 0b10'11010010'10110010, 0b10'11010010'10011010, 0b10'11001010'10110010,
0b11'01010101'00110010, 0b11'01010101'00011010, 0b11'01010100'10110010, 0b11'01010100'10011010, 0b11'01010100'01011010,
0b11'01010010'10011010, 0b11'01010010'01011010, 0b11'01001010'10011010, 0b11'01010101'10010010,
};

Barcode DataBarLimitedReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr<RowReader::DecodingState>&) const
{
next = next.subView(-2, SYMBOL_LEN);
while (next.shift(2)) {
if (!IsGuard(next[27], next[43]))
continue;
auto spaceSize = (next[27] + next[43]) / 2;
if ((!next.isAtFirstBar() && next[-1] < spaceSize) || (!next.isAtLastBar() && next[SYMBOL_LEN] < 4 * spaceSize))
continue;
auto [mBar, MBar] = std::minmax({next[0], next[28], next[44]});
if (MBar > mBar * 4 / 3 + 1)
continue;

auto leftView = next.subView(1 + 0 * CHAR_LEN, CHAR_LEN);
auto checkView = next.subView(1 + 1 * CHAR_LEN, CHAR_LEN);
auto rightView = next.subView(1 + 2 * CHAR_LEN, CHAR_LEN);
auto leftWidth = leftView.sum();
auto checkWith = checkView.sum();
auto rightWidth = rightView.sum();
if (!Has26to18Ratio(leftWidth, checkWith) || !Has26to18Ratio(rightWidth, checkWith))
continue;

auto modSize = double(leftWidth + checkWith + rightWidth) / (26 + 18 + 26);
if ((!next.isAtFirstBar() && next[-1] < modSize) || (!next.isAtLastBar() && next[SYMBOL_LEN] < 5 * modSize))
continue;

auto checkCharPattern = ToInt(NormalizedPatternFromE2E<CHAR_LEN, 18>(checkView));
int checkSum = IndexOf(CheckChars, checkCharPattern);
if (checkSum == -1)
continue;

printf("%f - ", modSize);
printv("%d ", NormalizedPatternFromE2E<CHAR_LEN, 18>(checkView));

auto left = ReadDataCharacter(leftView);
auto right = ReadDataCharacter(rightView);

printf("- %d, %d, %d\n", checkSum, left.value, right.value);

if ((left.checksum + 20 * right.checksum) % 89 != checkSum)
continue;

return {ConstructText(left, right), rowNumber, next.pixelsInFront(), next.pixelsTillEnd(),
BarcodeFormat::DataBarLimited, {'e', '0'}};
}

// guarantee progress (see loop in ODReader.cpp)
next = {};

return {};
}

} // namespace ZXing::OneD
23 changes: 23 additions & 0 deletions core/src/oned/ODDataBarLimitedReader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2024 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "ODRowReader.h"

namespace ZXing::OneD {

/**
* Decodes DataBarLimited sybmols. See ISO/IEC 24724:2011.
*/
class DataBarLimitedReader : public RowReader
{
public:
using RowReader::RowReader;

Barcode decodePattern(int rowNumber, PatternView& next, std::unique_ptr<DecodingState>& state) const override;
};

} // namespace ZXing::OneD
3 changes: 3 additions & 0 deletions core/src/oned/ODReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "ODCode39Reader.h"
#include "ODCode93Reader.h"
#include "ODDataBarExpandedReader.h"
#include "ODDataBarLimitedReader.h"
#include "ODDataBarReader.h"
#include "ODDXFilmEdgeReader.h"
#include "ODITFReader.h"
Expand Down Expand Up @@ -62,6 +63,8 @@ Reader::Reader(const ReaderOptions& opts) : ZXing::Reader(opts)
_readers.emplace_back(new DataBarReader(opts));
if (formats.testFlags(BarcodeFormat::DataBarExpanded))
_readers.emplace_back(new DataBarExpandedReader(opts));
if (formats.testFlags(BarcodeFormat::DataBarLimited))
_readers.emplace_back(new DataBarLimitedReader(opts));
if (formats.testFlag(BarcodeFormat::DXFilmEdge))
_readers.emplace_back(new DXFilmEdgeReader(opts));
}
Expand Down
Loading

0 comments on commit 5fadfbc

Please sign in to comment.