diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4691012156..4986ba1f08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,7 @@ jobs: path: install build-ubuntu-sanitize: - runs-on: ubuntu-20.04 # see https://github.com/quantumlib/Stim/issues/717#issuecomment-2002623560 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -229,6 +229,11 @@ jobs: working-directory: wrappers/python run: python -m unittest -v + - uses: actions/upload-artifact@v4 + with: + name: ${{matrix.os}}-python-artifacts + path: wrappers/python/zxingcpp.* + build-rust: runs-on: ${{ matrix.os }} strategy: @@ -254,9 +259,11 @@ jobs: - name: Test run: cargo test --release --all-features - - name: Package - # --allow-dirty is required on the windows build (but not the ubuntu build?!) - run: cargo package --verbose --allow-dirty --all-features + # disable for now, as this started to cause random failures like "error: attempt to get status of nonexistent file 'zint/backend/rss.c'" + # this is apparently related with the used symlinks + # - name: Package + # # --allow-dirty is required on the windows build (but not the ubuntu build?!) + # run: cargo package --verbose --allow-dirty --all-features build-wasm: runs-on: ubuntu-latest diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index afb3be26c4..24ab171b60 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -35,3 +35,6 @@ jobs: ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} run: ./gradlew publishReleasePublicationToSonatypeRepository + + +# To actually publish the the blob, login to https://s01.oss.sonatype.org/#stagingRepositories, then press "Close", then "Release". diff --git a/.gitignore b/.gitignore index 53a44bff88..f24ac8e73d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,10 @@ CMakeLists.txt.user # JetBrain (AndroidStudio, clion) .idea + +# Visual Studio +/.vs +/CMakeUserPresets.json + +# Visual Studio Code +/.vscode diff --git a/README.md b/README.md index 112324a1da..9dc75d7f5b 100644 --- a/README.md +++ b/README.md @@ -13,7 +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://eurekam.fr/) +* [synedra](https://synedra.com/) Thanks a lot for your contribution! @@ -47,7 +47,7 @@ Thanks a lot for your contribution! [Note:] * DataBar used to be called RSS. - * DataBar, DX Film Edge, MaxiCode, Micro QR Code and rMQR Code are not supported for writing (unless the library is configured `ZXING_WRITERS=NEW` and `ZING_EXPERIMENTAL_API=ON`). + * DataBar, DX Film Edge, MaxiCode, Micro QR Code and rMQR Code are not supported for writing (unless the library is configured `ZXING_WRITERS=NEW` and `ZXING_EXPERIMENTAL_API=ON`). * Building with only C++17 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behavior of the library: it then lacks support for DataBarLimited and multi-symbol and position independent detection for DataMatrix. ## Getting Started diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index fb19617bb8..8ded55ef5e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -103,6 +103,8 @@ set (COMMON_FILES src/CharacterSet.cpp src/Content.h src/Content.cpp + src/DecoderResult.h + src/DetectorResult.h src/ECI.h src/ECI.cpp src/Error.h @@ -111,6 +113,8 @@ set (COMMON_FILES src/GTIN.h src/GTIN.cpp src/ImageView.h + src/JSON.h + src/JSON.cpp src/Matrix.h src/Point.h src/Quadrilateral.h @@ -156,8 +160,6 @@ if (ZXING_READERS) src/ConcentricFinder.cpp src/DecodeHints.h $<$:src/DecodeHints.cpp> # [[deprecated]] - src/DecoderResult.h - src/DetectorResult.h src/GlobalHistogramBinarizer.h src/GlobalHistogramBinarizer.cpp src/GridSampler.h @@ -220,6 +222,7 @@ set (PUBLIC_HEADERS src/ImageView.h src/Point.h src/Quadrilateral.h + src/Range.h # re-evaluate for 3.0 src/ReadBarcode.h src/ReaderOptions.h src/StructuredAppend.h @@ -240,7 +243,6 @@ if (ZXING_WRITERS_OLD) set (PUBLIC_HEADERS ${PUBLIC_HEADERS} src/BitMatrix.h src/BitMatrixIO.h - src/Range.h src/Matrix.h src/MultiFormatWriter.h ) @@ -402,9 +404,9 @@ if (ZXING_READERS) src/pdf417/PDFCodeword.h src/pdf417/PDFCodewordDecoder.h src/pdf417/PDFCodewordDecoder.cpp + src/pdf417/PDFCustomData.h src/pdf417/PDFDecoder.h src/pdf417/PDFDecoder.cpp - src/pdf417/PDFDecoderResultExtra.h src/pdf417/PDFDetectionResult.h src/pdf417/PDFDetectionResult.cpp src/pdf417/PDFDetectionResultColumn.h @@ -419,7 +421,6 @@ if (ZXING_READERS) src/pdf417/PDFReader.cpp src/pdf417/PDFScanningDecoder.h src/pdf417/PDFScanningDecoder.cpp - src/pdf417/CustomData.h src/pdf417/ZXNullable.h ) endif() @@ -639,6 +640,9 @@ install ( ) IF (NOT WIN32 OR MINGW) + if (${ZXING_EXPERIMENTAL_API}) + set(COMPILE_FLAGS "-DZXING_EXPERIMENTAL_API") + endif() configure_file(zxing.pc.in zxing.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/zxing.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) ENDIF() diff --git a/core/src/Barcode.cpp b/core/src/Barcode.cpp index 41b2137d36..5454c85588 100644 --- a/core/src/Barcode.cpp +++ b/core/src/Barcode.cpp @@ -8,6 +8,7 @@ #include "DecoderResult.h" #include "DetectorResult.h" +#include "JSON.h" #include "ZXAlgorithms.h" #ifdef ZXING_EXPERIMENTAL_API @@ -52,6 +53,7 @@ Result::Result(DecoderResult&& decodeResult, DetectorResult&& detectorResult, Ba _readerInit(decodeResult.readerInit()) #ifdef ZXING_EXPERIMENTAL_API , _symbol(std::make_shared(std::move(detectorResult).bits())) + , _json(std::move(decodeResult).json()) #endif { if (decodeResult.versionNumber()) @@ -153,30 +155,49 @@ void Result::symbol(BitMatrix&& bits) ImageView Result::symbol() const { - return {_symbol->row(0).begin(), _symbol->width(), _symbol->height(), ImageFormat::Lum}; + return _symbol && !_symbol->empty() ? ImageView{_symbol->row(0).begin(), _symbol->width(), _symbol->height(), ImageFormat::Lum} + : ImageView{}; } void Result::zint(unique_zint_symbol&& z) { _zint = std::shared_ptr(std::move(z)); } + +std::string Result::extra(std::string_view key) const +{ + if (key == "ALL") { + if (format() == BarcodeFormat::None) + return {}; + auto res = + StrCat("{", JsonProp("Text", text(TextMode::Plain)), JsonProp("HRI", text(TextMode::HRI)), + JsonProp("TextECI", text(TextMode::ECI)), JsonProp("Bytes", text(TextMode::Hex)), + JsonProp("Identifier", symbologyIdentifier()), JsonProp("Format", ToString(format())), + JsonProp("ContentType", isValid() ? ToString(contentType()) : ""), JsonProp("Position", ToString(position())), + JsonProp("HasECI", hasECI()), JsonProp("IsMirrored", isMirrored()), JsonProp("IsInverted", isInverted()), _json, + JsonProp("Error", ToString(error()))); + res.back() = '}'; + return res; + } + return _json.empty() ? "" : key.empty() ? StrCat("{", _json.substr(0, _json.size() - 1), "}") : std::string(JsonGetStr(_json, key)); +} #endif bool Result::operator==(const Result& o) const { - // handle case where both are MatrixCodes first - if (!BarcodeFormats(BarcodeFormat::LinearCodes).testFlags(format() | o.format())) { - if (format() != o.format() || (bytes() != o.bytes() && isValid() && o.isValid())) + if (format() != o.format()) + return false; + + // handle MatrixCodes first + if (!IsLinearBarcode(format())) { + if (bytes() != o.bytes() && isValid() && o.isValid()) return false; // check for equal position if both are valid with equal bytes or at least one is in error return IsInside(Center(o.position()), position()); } - if (format() != o.format() || bytes() != o.bytes() || error() != o.error()) - return false; - - if (orientation() != o.orientation()) + if (bytes() != o.bytes() || error() != o.error() || orientation() != o.orientation()) return false; if (lineCount() > 1 && o.lineCount() > 1) diff --git a/core/src/Barcode.h b/core/src/Barcode.h index 051205ae41..cc13023130 100644 --- a/core/src/Barcode.h +++ b/core/src/Barcode.h @@ -20,7 +20,17 @@ #include namespace ZXing { class BitMatrix; -} + +namespace BarcodeExtra { + #define ZX_EXTRA(NAME) static constexpr auto NAME = #NAME + ZX_EXTRA(DataMask); // QRCodes + ZX_EXTRA(Version); + ZX_EXTRA(EanAddOn); // EAN/UPC + ZX_EXTRA(UPCE); + #undef ZX_EXTRA +} // namespace BarcodeExtra + +} // namespace ZXing extern "C" struct zint_symbol; struct zint_symbol_deleter @@ -78,7 +88,7 @@ class Result /** * @brief bytes is the raw / standard content without any modifications like character set conversions */ - const ByteArray& bytes() const; + const ByteArray& bytes() const; // TODO 3.0: replace ByteArray with std::vector /** * @brief bytesECI is the raw / standard content following the ECI protocol @@ -179,6 +189,10 @@ class Result ImageView symbol() const; void zint(unique_zint_symbol&& z); zint_symbol* zint() const { return _zint.get(); } + Result&& addExtra(std::string&& json) { _json += std::move(json); return std::move(*this); } + // template + // Result&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); } + std::string extra(std::string_view key = "") const; #endif bool operator==(const Result& o) const; @@ -199,6 +213,7 @@ class Result #ifdef ZXING_EXPERIMENTAL_API std::shared_ptr _symbol; std::shared_ptr _zint; + std::string _json; #endif }; diff --git a/core/src/BarcodeFormat.h b/core/src/BarcodeFormat.h index 1b29272410..f8b7bc0e9c 100644 --- a/core/src/BarcodeFormat.h +++ b/core/src/BarcodeFormat.h @@ -53,6 +53,11 @@ enum class BarcodeFormat ZX_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) +inline constexpr bool IsLinearBarcode(BarcodeFormat format) +{ + return BarcodeFormats(BarcodeFormat::LinearCodes).testFlag(format); +} + std::string ToString(BarcodeFormat format); std::string ToString(BarcodeFormats formats); diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index 8b4914e9bf..9cbb592ee7 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -63,7 +63,7 @@ void BinaryBitmap::invert() auto matrix = const_cast(_cache->matrix.get()); matrix->flipAll(); } - _inverted = true; + _inverted = !_inverted; } template diff --git a/core/src/ByteArray.h b/core/src/ByteArray.h index 6cb603e7b4..f652b1ba2d 100644 --- a/core/src/ByteArray.h +++ b/core/src/ByteArray.h @@ -6,6 +6,8 @@ #pragma once +#include "Range.h" + #include #include #include @@ -15,7 +17,7 @@ namespace ZXing { /** - ByteArray is an extension of std::vector. + ByteArray is an extension of std::vector. */ class ByteArray : public std::vector { @@ -25,15 +27,21 @@ class ByteArray : public std::vector explicit ByteArray(int len) : std::vector(len, 0) {} explicit ByteArray(const std::string& str) : std::vector(str.begin(), str.end()) {} - void append(const ByteArray& other) { insert(end(), other.begin(), other.end()); } + void append(ByteView other) { insert(end(), other.begin(), other.end()); } + void append(std::string_view other) { insert(end(), other.begin(), other.end()); } std::string_view asString(size_t pos = 0, size_t len = std::string_view::npos) const { return std::string_view(reinterpret_cast(data()), size()).substr(pos, len); } + + ByteView asView(size_t pos = 0, size_t len = size_t(-1)) const + { + return ByteView(*this).subview(pos, len); + } }; -inline std::string ToHex(const ByteArray& bytes) +inline std::string ToHex(ByteView bytes) { std::string res(bytes.size() * 3, ' '); diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index e21f65e664..e43dd38b79 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -156,8 +156,14 @@ static std::vector CollectRingPoints(const BitMatrix& image, PointF cent static std::optional FitQadrilateralToPoints(PointF center, std::vector& points) { auto dist2Center = [c = center](auto a, auto b) { return distance(a, c) < distance(b, c); }; + auto [minDistElem, maxDistElem] = std::minmax_element(points.begin(), points.end(), dist2Center); + + // check if points are on a circle: for a square the min/max ratio is 0.7, for a circle it is 1 + if (distance(center, *minDistElem) / distance(center, *maxDistElem) > 0.85) + return {}; + // rotate points such that the first one is the furthest away from the center (hence, a corner) - std::rotate(points.begin(), std::max_element(points.begin(), points.end(), dist2Center), points.end()); + std::rotate(points.begin(), maxDistElem, points.end()); std::array corners; corners[0] = &points[0]; @@ -207,7 +213,7 @@ static bool QuadrilateralIsPlausibleSquare(const QuadrilateralF q, int lineIndex return m >= lineIndex * 2 && m > M / 3; } -static std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup) +std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup) { auto points = CollectRingPoints(image, center, range, lineIndex, backup); if (points.empty()) diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index 68f29b6d45..f8490e0c21 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -101,6 +101,8 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize); +std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup); + std::optional FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int ringIndex); struct ConcentricPattern : public PointF diff --git a/core/src/Content.cpp b/core/src/Content.cpp index d0f2e80a16..6f18ad8581 100644 --- a/core/src/Content.cpp +++ b/core/src/Content.cpp @@ -81,15 +81,15 @@ void Content::erase(int pos, int n) bytes.erase(bytes.begin() + pos, bytes.begin() + pos + n); for (auto& e : encodings) if (e.pos > pos) - pos -= n; + e.pos -= n; } -void Content::insert(int pos, const std::string& str) +void Content::insert(int pos, std::string_view str) { bytes.insert(bytes.begin() + pos, str.begin(), str.end()); for (auto& e : encodings) if (e.pos > pos) - pos += Size(str); + e.pos += Size(str); } bool Content::canProcess() const @@ -104,39 +104,36 @@ std::string Content::render(bool withECI) const #ifdef ZXING_READERS std::string res; + res.reserve(bytes.size() * 2); if (withECI) - res = symbology.toString(true); + res += symbology.toString(true); ECI lastECI = ECI::Unknown; auto fallbackCS = defaultCharset; if (!hasECI && fallbackCS == CharacterSet::Unknown) fallbackCS = guessEncoding(); ForEachECIBlock([&](ECI eci, int begin, int end) { - // first determine how to decode the content (choose character set) - // * eci == ECI::Unknown implies !hasECI and we guess - // * if !IsText(eci) the ToCharcterSet(eci) will return Unknown and we decode as binary - CharacterSet cs = eci == ECI::Unknown ? fallbackCS : ToCharacterSet(eci); - + // basic idea: if IsText(eci), we transcode it to UTF8, otherwise we treat it as binary but + // transcoded it to valid UTF8 bytes seqences representing the code points 0-255. The eci we report + // back to the caller by inserting their "\XXXXXX" ECI designator is UTF8 for text and + // the original ECI for everything else. + // first determine how to decode the content (use fallback if unknown) + auto inEci = IsText(eci) ? eci : eci == ECI::Unknown ? ToECI(fallbackCS) : ECI::Binary; if (withECI) { // then find the eci to report back in the ECI designator - if (IsText(ToECI(cs))) // everything decoded as text is reported as utf8 - eci = ECI::UTF8; - else if (eci == ECI::Unknown) // implies !hasECI and fallbackCS is Unknown or Binary - eci = ECI::Binary; - - if (lastECI != eci) - res += ToString(eci); - lastECI = eci; - - std::string tmp; - TextDecoder::Append(tmp, bytes.data() + begin, end - begin, cs); - for (auto c : tmp) { + auto outEci = IsText(inEci) ? ECI::UTF8 : eci; + + if (lastECI != outEci) + res += ToString(outEci); + lastECI = outEci; + + for (auto c : BytesToUtf8(bytes.asView(begin, end - begin), inEci)) { res += c; - if (c == '\\') // in the ECI protocol a '\' has to be doubled + if (c == '\\') // in the ECI protocol a '\' (0x5c) has to be doubled, works only because 0x5c can only mean `\` res += c; } } else { - TextDecoder::Append(res, bytes.data() + begin, end - begin, cs); + res += BytesToUtf8(bytes.asView(begin, end - begin), inEci); } }); @@ -182,21 +179,28 @@ ByteArray Content::bytesECI() const if (empty()) return {}; - std::string res = symbology.toString(true); + ByteArray res; + res.reserve(3 + bytes.size() + hasECI * encodings.size() * 7); - ForEachECIBlock([&](ECI eci, int begin, int end) { - if (hasECI) - res += ToString(eci); + // report ECI protocol only if actually found ECI data in the barode bit stream + // see also https://github.com/zxing-cpp/zxing-cpp/issues/936 + res.append(symbology.toString(hasECI)); - for (int i = begin; i != end; ++i) { - char c = static_cast(bytes[i]); - res += c; - if (c == '\\') // in the ECI protocol a '\' has to be doubled - res += c; - } - }); + if (hasECI) + ForEachECIBlock([&](ECI eci, int begin, int end) { + if (hasECI) + res.append(ToString(eci)); + + for (auto b : bytes.asView(begin, end - begin)) { + res.push_back(b); + if (b == '\\') // in the ECI protocol a '\' has to be doubled + res.push_back(b); + } + }); + else + res.append(bytes); - return ByteArray(res); + return res; } CharacterSet Content::guessEncoding() const @@ -206,21 +210,21 @@ CharacterSet Content::guessEncoding() const ByteArray input; ForEachECIBlock([&](ECI eci, int begin, int end) { if (eci == ECI::Unknown) - input.insert(input.end(), bytes.begin() + begin, bytes.begin() + end); + input.append(bytes.asView(begin, end - begin)); }); if (input.empty()) return CharacterSet::Unknown; - return TextDecoder::GuessEncoding(input.data(), input.size(), CharacterSet::ISO8859_1); + return GuessTextEncoding(input); #else - return CharacterSet::Unknown; + return CharacterSet::ISO8859_1; #endif } ContentType Content::type() const { -#ifdef ZXING_READERS +#if 1 //def ZXING_READERS if (empty()) return ContentType::Text; diff --git a/core/src/Content.h b/core/src/Content.h index 99e5a01e71..17fe1413df 100644 --- a/core/src/Content.h +++ b/core/src/Content.h @@ -8,8 +8,10 @@ #include "ByteArray.h" #include "CharacterSet.h" #include "ReaderOptions.h" +#include "ZXAlgorithms.h" #include +#include #include namespace ZXing { @@ -28,7 +30,8 @@ struct SymbologyIdentifier std::string toString(bool hasECI = false) const { - return code ? ']' + std::string(1, code) + static_cast(modifier + eciModifierOffset * hasECI) : std::string(); + int modVal = (modifier >= 'A' ? modifier - 'A' + 10 : modifier - '0') + eciModifierOffset * hasECI; + return code ? StrCat(']', code, static_cast((modVal >= 10 ? 'A' - 10 : '0') + modVal)) : std::string(); } }; @@ -62,15 +65,13 @@ class Content void reserve(int count) { bytes.reserve(bytes.size() + count); } void push_back(uint8_t val) { bytes.push_back(val); } - void append(const std::string& str) { bytes.insert(bytes.end(), str.begin(), str.end()); } - void append(const ByteArray& ba) { bytes.insert(bytes.end(), ba.begin(), ba.end()); } + void push_back(int val) { bytes.push_back(narrow_cast(val)); } + void append(std::string_view str) { bytes.insert(bytes.end(), str.begin(), str.end()); } + void append(ByteView bv) { bytes.insert(bytes.end(), bv.begin(), bv.end()); } void append(const Content& other); - void operator+=(char val) { push_back(val); } - void operator+=(const std::string& str) { append(str); } - void erase(int pos, int n); - void insert(int pos, const std::string& str); + void insert(int pos, std::string_view str); bool empty() const { return bytes.empty(); } bool canProcess() const; diff --git a/core/src/DecoderResult.h b/core/src/DecoderResult.h index de068c4263..66a6731158 100644 --- a/core/src/DecoderResult.h +++ b/core/src/DecoderResult.h @@ -8,6 +8,7 @@ #include "Content.h" #include "Error.h" +#include "JSON.h" #include "StructuredAppend.h" #include @@ -16,7 +17,10 @@ namespace ZXing { -class CustomData; +struct CustomData +{ + virtual ~CustomData() = default; +}; class DecoderResult { @@ -24,12 +28,12 @@ class DecoderResult std::string _ecLevel; int _lineCount = 0; int _versionNumber = 0; - int _dataMask = 0; StructuredAppendInfo _structuredAppend; bool _isMirrored = false; bool _readerInit = false; Error _error; - std::shared_ptr _extra; + std::string _json; + std::shared_ptr _customData; DecoderResult(const DecoderResult &) = delete; DecoderResult& operator=(const DecoderResult &) = delete; @@ -72,14 +76,17 @@ class DecoderResult ZX_PROPERTY(std::string, ecLevel, setEcLevel) ZX_PROPERTY(int, lineCount, setLineCount) ZX_PROPERTY(int, versionNumber, setVersionNumber) - ZX_PROPERTY(int, dataMask, setDataMask) ZX_PROPERTY(StructuredAppendInfo, structuredAppend, setStructuredAppend) ZX_PROPERTY(Error, error, setError) ZX_PROPERTY(bool, isMirrored, setIsMirrored) ZX_PROPERTY(bool, readerInit, setReaderInit) - ZX_PROPERTY(std::shared_ptr, extra, setExtra) - + ZX_PROPERTY(std::string, json, setJson) + ZX_PROPERTY(std::shared_ptr, customData, setCustomData) #undef ZX_PROPERTY + + template + DecoderResult&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); } + }; } // ZXing diff --git a/core/src/GTIN.cpp b/core/src/GTIN.cpp index 5ee06a9de7..1c51bda2da 100644 --- a/core/src/GTIN.cpp +++ b/core/src/GTIN.cpp @@ -201,11 +201,9 @@ std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat std::string EanAddOn(const Barcode& barcode) { - if (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE | BarcodeFormat::EAN8).testFlag(barcode.format())) + if (barcode.symbologyIdentifier() != "]E3") return {}; - auto txt = barcode.bytes().asString(); - auto pos = txt.find(' '); - return pos != std::string::npos ? std::string(txt.substr(pos + 1)) : std::string(); + return std::string(barcode.bytes().asString().substr(barcode.format() == BarcodeFormat::EAN8 ? 8 : 13)); } std::string IssueNr(const std::string& ean2AddOn) diff --git a/core/src/GTIN.h b/core/src/GTIN.h index 9148844ff3..74df5e8645 100644 --- a/core/src/GTIN.h +++ b/core/src/GTIN.h @@ -33,6 +33,7 @@ bool IsCheckDigitValid(const std::basic_string& s) return ComputeCheckDigit(s, true) == s.back(); } +//TODO: use std::string_view in 3.0 /** * Evaluate the prefix of the GTIN to estimate the country of origin. See * diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index fb0b3ec26e..2777bb2283 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -8,6 +8,7 @@ #include "BitMatrix.h" #include "Pattern.h" +#include "ZXConfig.h" #include #include @@ -115,7 +116,7 @@ bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& // during the histogram calculation and during the sharpen+threshold operation. Additionally, if we // perform the ThresholdSharpened function on pixStride==1 data, the auto-vectorizer makes that part // 8x faster on an AVX2 cpu which easily recovers the extra cost that we pay for the copying. - thread_local std::vector line; + ZX_THREAD_LOCAL std::vector line; if (std::abs(buffer.pixStride()) > 4) { line.resize(lineView.size()); std::copy(lineView.begin(), lineView.end(), line.begin()); @@ -127,7 +128,7 @@ bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& if (threshold <= 0) return false; - thread_local std::vector binarized; + ZX_THREAD_LOCAL std::vector binarized; // the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 8x faster on AVX2 hardware if (lineView.begin().stride == 1) ThresholdSharpened(lineView, threshold, binarized); diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 33562817a0..8f8a6f90cc 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -143,7 +143,7 @@ static std::shared_ptr CalculateMatrix(const uint8_t* __restrict lumi { auto matrix = std::make_shared(width, height); -#ifndef NDEBUG +#ifdef PRINT_DEBUG Matrix out(width, height); Matrix out2(width, height); #endif @@ -163,7 +163,7 @@ static std::shared_ptr CalculateMatrix(const uint8_t* __restrict lumi int average = sum / 25; ThresholdBlock(luminances, xoffset, yoffset, average, rowStride, *matrix); -#ifndef NDEBUG +#ifdef PRINT_DEBUG for (int yy = 0; yy < 8; ++yy) for (int xx = 0; xx < 8; ++xx) { out.set(xoffset + xx, yoffset + yy, blackPoints(x, y)); @@ -173,7 +173,7 @@ static std::shared_ptr CalculateMatrix(const uint8_t* __restrict lumi } } -#ifndef NDEBUG +#ifdef PRINT_DEBUG std::ofstream file("thresholds.pnm"); file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; file.write(reinterpret_cast(out.data()), out.size()); @@ -260,7 +260,7 @@ static std::shared_ptr ThresholdImage(const ImageView iv, const Matri { auto matrix = std::make_shared(iv.width(), iv.height()); -#ifndef NDEBUG +#ifdef PRINT_DEBUG Matrix out(iv.width(), iv.height()); #endif @@ -270,7 +270,7 @@ static std::shared_ptr ThresholdImage(const ImageView iv, const Matri int xoffset = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE); ThresholdBlock(iv.data(), xoffset, yoffset, thresholds(x, y), iv.rowStride(), *matrix); -#ifndef NDEBUG +#ifdef PRINT_DEBUG for (int yy = 0; yy < 8; ++yy) for (int xx = 0; xx < 8; ++xx) out.set(xoffset + xx, yoffset + yy, thresholds(x, y)); @@ -278,7 +278,7 @@ static std::shared_ptr ThresholdImage(const ImageView iv, const Matri } } -#ifndef NDEBUG +#ifdef PRINT_DEBUG std::ofstream file("thresholds_new.pnm"); file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; file.write(reinterpret_cast(out.data()), out.size()); diff --git a/core/src/JSON.cpp b/core/src/JSON.cpp new file mode 100644 index 0000000000..1c65bbdf9f --- /dev/null +++ b/core/src/JSON.cpp @@ -0,0 +1,159 @@ +/* +* Copyright 2025 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "JSON.h" + +#include +#include + +// This code is trying to find the value of a key-value pair in a string of those. +// The input could be valid JSON, like '{"key": "val"}' or a stipped down version like +// 'key:val'. This is also compatible with the string serialization of a python dictionary. +// This could easily be done with the following regex (see below). +// But using std::regex adds 140k+ to the binary size. ctre is _very_ nice to use and has +// the same binary footprint as the hand-roled code below! But I don't want to add +// 5k lines of c++ code for that. + +// #define ZXING_USE_CTRE +#ifdef ZXING_USE_CTRE +#pragma GCC diagnostic ignored "-Wundef" // see https://github.com/hanickadot/compile-time-regular-expressions/issues/340 +#include "ctre.hpp" +#pragma GCC diagnostic error "-Wundef" + +static constexpr auto PATTERN = + ctll::fixed_string{R"(["']?([[:alpha:]][[:alnum:]]*)["']?\s*(?:[:]\s*["']?([[:alnum:]]+)["']?)?(?:,|\}|$))"}; +#else +#include "ZXAlgorithms.h" +#endif + +namespace ZXing { + +// Trim whitespace and quotes/braces from both ends +inline std::string_view Trim(std::string_view sv) +{ + constexpr auto ws = " \t\n\r\"'{}"; + while (sv.size() && Contains(ws, sv.back())) + sv.remove_suffix(1); + while (sv.size() && Contains(ws, sv.front())) + sv.remove_prefix(1); + return sv.empty() ? std::string_view() : sv; +} + +inline bool IsEqualCaseInsensitive(std::string_view sv1, std::string_view sv2) +{ + return sv1.size() == sv2.size() + && std::equal(sv1.begin(), sv1.end(), sv2.begin(), [](uint8_t a, uint8_t b) { return std::tolower(a) == std::tolower(b); }); +} + +std::string_view JsonGetStr(std::string_view json, std::string_view key) +{ +#ifdef ZXING_USE_CTRE + for (auto [ma, mk, mv] : ctre::search_all(json)) + if (IsEqualCaseInsensitive(key, mk)) + return mv.size() ? mv.to_view() : std::string_view(mk.data(), 0); + + return {}; +#else + json = Trim(json); + + while (!json.empty()) { + auto posComma = json.find(','); + auto pair = Trim(json.substr(0, posComma)); + + if (IsEqualCaseInsensitive(pair, key)) + return {pair.data(), 0}; // return non-null data to signal that we found the key + + auto posColon = pair.find(':'); + + if (posColon != std::string_view::npos && IsEqualCaseInsensitive(Trim(pair.substr(0, posColon)), key)) + return Trim(pair.substr(posColon + 1)); + + json = (posComma == std::string_view::npos) ? std::string_view{} : json.substr(posComma + 1); + } + + return {}; +#endif +} + +std::string JsonEscapeStr(std::string_view str) +{ + std::string res; + res.reserve(str.size() + 10); + + for (unsigned char c : str) { + switch (c) { + case '\"': res += "\\\""; break; + case '\\': res += "\\\\"; break; + case '\b': res += "\\b"; break; + case '\f': res += "\\f"; break; + case '\n': res += "\\n"; break; + case '\r': res += "\\r"; break; + case '\t': res += "\\t"; break; + default: + if (c <= 0x1F) { + char buf[7]; + std::snprintf(buf, sizeof(buf), "\\u%04X", c); + res += buf; + } else { + res += c; + } + break; + } + } + + return res; +} + +std::string JsonUnEscapeStr(std::string_view str) +{ + std::string res; + res.reserve(str.size()); + + for (size_t i = 0; i < str.size(); ++i) { + char c = str[i]; + if (c == '\\') { + if (++i >= str.size()) + throw std::runtime_error("Invalid escape sequence"); + + char esc = str[i]; + switch (esc) { + case '"': res += '\"'; break; + case '\\': res += '\\'; break; + case '/': res += '/'; break; + case 'b': res += '\b'; break; + case 'f': res += '\f'; break; + case 'n': res += '\n'; break; + case 'r': res += '\r'; break; + case 't': res += '\t'; break; + case 'u': { + if (i + 4 >= str.size()) + throw std::runtime_error("Incomplete \\u escape"); + + uint32_t code = 0; + auto first = str.data() + i + 1; + auto last = first + 4; + + auto [ptr, ec] = std::from_chars(first, last, code, 16); + if (ec != std::errc() || ptr != last) + throw std::runtime_error("Failed to parse hex code"); + + if (code > 0x1F) + throw std::runtime_error("Unexpected code point in \\u escape"); + + res += static_cast(code); + i += 4; + break; + } + default: throw std::runtime_error("Unknown escape sequence"); + } + } else { + res += c; + } + } + + return res; +} + +} // ZXing diff --git a/core/src/JSON.h b/core/src/JSON.h new file mode 100644 index 0000000000..0124db7251 --- /dev/null +++ b/core/src/JSON.h @@ -0,0 +1,57 @@ +/* +* Copyright 2025 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ZXAlgorithms.h" + +#include +#include +#include +#include + +namespace ZXing { + +std::string JsonEscapeStr(std::string_view str); +std::string JsonUnEscapeStr(std::string_view str); + +template +inline std::string JsonProp(std::string_view key, T val, T ignore = {}) +{ + if (val == ignore) + return {}; + + #define ZX_JSON_KEY_VAL(...) StrCat("\"", key, "\":", __VA_ARGS__, ',') + if constexpr (std::is_same_v) + return val ? ZX_JSON_KEY_VAL("true") : ""; + else if constexpr (std::is_arithmetic_v) + return ZX_JSON_KEY_VAL(std::to_string(val)); + else if constexpr (std::is_convertible_v) + return ZX_JSON_KEY_VAL("\"" , JsonEscapeStr(val), "\""); + else + static_assert("unsupported JSON value type"); + #undef ZX_JSON_KEY_VAL +} + +std::string_view JsonGetStr(std::string_view json, std::string_view key); + +template +inline std::optional JsonGet(std::string_view json, std::string_view key) +{ + auto str = JsonGetStr(json, key); + if (!str.data()) + return std::nullopt; + + if constexpr (std::is_same_v) + return str.empty() || Contains("1tT", str.front()) ? std::optional(true) : std::nullopt; + else if constexpr (std::is_arithmetic_v) + return str.empty() ? std::nullopt : std::optional(FromString(str)); + else if constexpr (std::is_same_v) + return JsonUnEscapeStr(str); + else + static_assert("unsupported JSON value type"); +} + +} // ZXing diff --git a/core/src/MultiFormatWriter.cpp b/core/src/MultiFormatWriter.cpp index e8d48471cc..14124a3e8c 100644 --- a/core/src/MultiFormatWriter.cpp +++ b/core/src/MultiFormatWriter.cpp @@ -69,7 +69,7 @@ MultiFormatWriter::encode(const std::wstring& contents, int width, int height) c case BarcodeFormat::ITF: return exec0(OneD::ITFWriter()); case BarcodeFormat::UPCA: return exec0(OneD::UPCAWriter()); case BarcodeFormat::UPCE: return exec0(OneD::UPCEWriter()); - default: throw std::invalid_argument(std::string("Unsupported format: ") + ToString(_format)); + default: throw std::invalid_argument("Unsupported format: " + ToString(_format)); } } diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 891060b45e..cf6963c25f 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -184,7 +184,7 @@ double IsPattern(const PatternView& view, const FixedPattern& p if (minQuietZone && spaceInPixel < minQuietZone * modSize.space) return 0; - const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] / (2 + (LEN < 6)) + .5}; + const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] * .5 + .5}; for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * modSize[x]) > thr[x]) diff --git a/core/src/PerspectiveTransform.h b/core/src/PerspectiveTransform.h index cc85fcb20c..3ac6699828 100644 --- a/core/src/PerspectiveTransform.h +++ b/core/src/PerspectiveTransform.h @@ -13,9 +13,11 @@ namespace ZXing { /** -*

This class implements a perspective transform in two dimensions. Given four source and four +* This class implements a perspective transform in two dimensions. Given four source and four * destination points, it will compute the transformation implied between them. The code is based -* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.

+* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56. +* +* See also e.g. https://math.stackexchange.com/a/339033 */ class PerspectiveTransform { diff --git a/core/src/Point.h b/core/src/Point.h index e1146f8056..731a8c207c 100644 --- a/core/src/Point.h +++ b/core/src/Point.h @@ -7,6 +7,7 @@ #include #include +#include namespace ZXing { @@ -153,6 +154,11 @@ PointT mainDirection(PointT d) return std::abs(d.x) > std::abs(d.y) ? PointT(d.x, 0) : PointT(0, d.y); } +template +std::string ToString(const PointT& p, bool swap = false, char delim = 'x') +{ + return std::to_string(swap ? p.y : p.x) + delim + std::to_string(swap ? p.x : p.y); +} } // ZXing diff --git a/core/src/Range.h b/core/src/Range.h index 5040157807..e3fbbc3064 100644 --- a/core/src/Range.h +++ b/core/src/Range.h @@ -7,6 +7,7 @@ #include "ZXAlgorithms.h" +#include #include namespace ZXing { @@ -56,4 +57,56 @@ struct Range template Range(const C&) -> Range; +/** + * ArrayView is a lightweight, non-owning, non-mutable view over a contiguous sequence of elements. + * Similar to std::span. See also Range template for general iterator use case. + */ +template +class ArrayView +{ + const T* _data = nullptr; + std::size_t _size = 0; + +public: + using value_type = T; + using pointer = const value_type*; + using const_pointer = const value_type*; + using reference = const value_type&; + using const_reference = const value_type&; + using size_type = std::size_t; + + constexpr ArrayView() noexcept = default; + + constexpr ArrayView(pointer data, size_type size) noexcept : _data(data), _size(size) {} + + template >>>>> + constexpr ArrayView(P data, size_type size) noexcept : _data(reinterpret_cast(data)), _size(size) + {} + + template ())), const_pointer>>> + constexpr ArrayView(const Container& c) noexcept : _data(std::data(c)), _size(std::size(c)) + {} + + constexpr pointer data() const noexcept { return _data; } + constexpr size_type size() const noexcept { return _size; } + constexpr bool empty() const noexcept { return _size == 0; } + + constexpr const_reference operator[](size_type index) const noexcept { return _data[index]; } + + constexpr pointer begin() const noexcept { return _data; } + constexpr pointer end() const noexcept { return _data + _size; } + + constexpr ArrayView subview(size_type pos, size_type len = size_type(-1)) const noexcept + { + if (pos > _size) + return {}; + return {_data + pos, std::min(len, _size - pos)}; + } +}; + +using ByteView = ArrayView; + } // namespace ZXing diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 88c0ac2e20..8a38791628 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -175,8 +175,12 @@ Barcodes ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) for (auto&& iv : pyramid.layers) { auto bitmap = CreateBitmap(opts.binarizer(), iv); for (int close = 0; close <= (closedReader ? 1 : 0); ++close) { - if (close) + if (close) { + // if we already inverted the image in the first round, we need to undo that first + if (bitmap->inverted()) + bitmap->invert(); bitmap->close(); + } // TODO: check if closing after invert would be beneficial for (int invert = 0; invert <= static_cast(opts.tryInvert() && !close); ++invert) { diff --git a/core/src/ReedSolomonDecoder.cpp b/core/src/ReedSolomonDecoder.cpp index e521f4cb7e..f2a49df10d 100644 --- a/core/src/ReedSolomonDecoder.cpp +++ b/core/src/ReedSolomonDecoder.cpp @@ -71,7 +71,7 @@ RunEuclideanAlgorithm(const GenericGF& field, std::vector&& rCoefs, Generic static std::vector FindErrorLocations(const GenericGF& field, const GenericGFPoly& errorLocator) { - // This is a direct application of Chien's search + // This is a brute force search for roots of errorLocator (not Chien's search) int numErrors = errorLocator.degree(); std::vector res; res.reserve(numErrors); @@ -80,9 +80,6 @@ FindErrorLocations(const GenericGF& field, const GenericGFPoly& errorLocator) if (errorLocator.evaluateAt(i) == 0) res.push_back(field.inverse(i)); - if (Size(res) != numErrors) - return {}; // Error locator degree does not match number of roots - return res; } @@ -124,8 +121,8 @@ ReedSolomonDecode(const GenericGF& field, std::vector& message, int numECCo return false; auto errorLocations = FindErrorLocations(field, sigma); - if (errorLocations.empty()) - return false; + if (Size(errorLocations) != sigma.degree()) + return false; // Error locator degree does not match number of roots, most likely there are more errors than can be recovered auto errorMagnitudes = FindErrorMagnitudes(field, omega, errorLocations); @@ -137,6 +134,16 @@ ReedSolomonDecode(const GenericGF& field, std::vector& message, int numECCo message[position] ^= errorMagnitudes[i]; } + +#if 1 + // re-evaluate the syndromes of the recovered message to make sure it is a valid (see #940) + poly = GenericGFPoly(field, message); + + for (int i = 0; i < numECCodeWords; i++) + if (poly.evaluateAt(field.exp(i + field.generatorBase())) != 0) + return false; +#endif + return true; } diff --git a/core/src/TextDecoder.cpp b/core/src/TextDecoder.cpp index 216c6c9994..faa5fd5330 100644 --- a/core/src/TextDecoder.cpp +++ b/core/src/TextDecoder.cpp @@ -1,14 +1,12 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2022 gitlost +* Copyright 2025 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #include "TextDecoder.h" -#include "CharacterSet.h" -#include "ECI.h" -#include "Utf.h" #include "ZXAlgorithms.h" #include "libzueci/zueci.h" @@ -17,38 +15,29 @@ namespace ZXing { -void TextDecoder::Append(std::string& str, const uint8_t* bytes, size_t length, CharacterSet charset, bool sjisASCII) +std::string BytesToUtf8(ByteView bytes, ECI eci) { - int eci = ToInt(ToECI(charset)); - const size_t str_len = str.length(); - const int bytes_len = narrow_cast(length); constexpr unsigned int replacement = 0xFFFD; - const unsigned int flags = ZUECI_FLAG_SB_STRAIGHT_THRU | (sjisASCII ? ZUECI_FLAG_SJIS_STRAIGHT_THRU : 0); + constexpr unsigned int flags = ZUECI_FLAG_SB_STRAIGHT_THRU | ZUECI_FLAG_SJIS_STRAIGHT_THRU; int utf8_len; - if (eci == -1) - eci = 899; // Binary + if (eci == ECI::Unknown) + eci = ECI::Binary; - int error_number = zueci_dest_len_utf8(eci, bytes, bytes_len, replacement, flags, &utf8_len); + int error_number = zueci_dest_len_utf8(ToInt(eci), bytes.data(), bytes.size(), replacement, flags, &utf8_len); if (error_number >= ZUECI_ERROR) throw std::runtime_error("zueci_dest_len_utf8 failed"); - str.resize(str_len + utf8_len); // Precise length - unsigned char *utf8_buf = reinterpret_cast(str.data()) + str_len; + std::string utf8(utf8_len, 0); - error_number = zueci_eci_to_utf8(eci, bytes, bytes_len, replacement, flags, utf8_buf, &utf8_len); - if (error_number >= ZUECI_ERROR) { - str.resize(str_len); + error_number = zueci_eci_to_utf8(ToInt(eci), bytes.data(), bytes.size(), replacement, flags, + reinterpret_cast(utf8.data()), &utf8_len); + if (error_number >= ZUECI_ERROR) throw std::runtime_error("zueci_eci_to_utf8 failed"); - } - assert(str.length() == str_len + utf8_len); -} -void TextDecoder::Append(std::wstring& str, const uint8_t* bytes, size_t length, CharacterSet charset) -{ - std::string u8str; - Append(u8str, bytes, length, charset); - str.append(FromUtf8(u8str)); + assert(Size(utf8) == utf8_len); + + return utf8; } /** @@ -57,8 +46,7 @@ void TextDecoder::Append(std::wstring& str, const uint8_t* bytes, size_t length, * {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform * default encoding if none of these can possibly be correct */ -CharacterSet -TextDecoder::GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fallback) +CharacterSet GuessTextEncoding(ByteView bytes, CharacterSet fallback) { // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, // which should be by far the most common encodings. @@ -82,11 +70,12 @@ TextDecoder::GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fal //int isoHighChars = 0; int isoHighOther = 0; - bool utf8bom = length > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; + bool utf8bom = bytes.size() > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; - for (size_t i = 0; i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8); ++i) + for (int value : bytes) { - int value = bytes[i]; + if(!(canBeISO88591 || canBeShiftJIS || canBeUTF8)) + break; // UTF-8 stuff if (canBeUTF8) { @@ -208,7 +197,7 @@ TextDecoder::GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fal // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1, // - then we conclude Shift_JIS, else ISO-8859-1 if (canBeISO88591 && canBeShiftJIS) { - return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= (int)length + return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= Size(bytes) ? CharacterSet::Shift_JIS : CharacterSet::ISO8859_1; } @@ -226,10 +215,4 @@ TextDecoder::GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fal return fallback; } -CharacterSet -TextDecoder::DefaultEncoding() -{ - return CharacterSet::ISO8859_1; -} - } // ZXing diff --git a/core/src/TextDecoder.h b/core/src/TextDecoder.h index 531614a779..7ada47ec0a 100644 --- a/core/src/TextDecoder.h +++ b/core/src/TextDecoder.h @@ -1,39 +1,25 @@ /* -* Copyright 2016 Nu-book Inc. +* Copyright 2025 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #pragma once #include "CharacterSet.h" +#include "ECI.h" +#include "Range.h" -#include -#include #include namespace ZXing { -class TextDecoder +std::string BytesToUtf8(ByteView bytes, ECI eci); + +inline std::string BytesToUtf8(ByteView bytes, CharacterSet cs) { -public: - static CharacterSet DefaultEncoding(); - static CharacterSet GuessEncoding(const uint8_t* bytes, size_t length, CharacterSet fallback = DefaultEncoding()); - - // If `sjisASCII` set then for Shift_JIS maps ASCII directly (straight-thru), i.e. does not map ASCII backslash & tilde - // to Yen sign & overline resp. (JIS X 0201 Roman) - static void Append(std::string& str, const uint8_t* bytes, size_t length, CharacterSet charset, bool sjisASCII = true); - - static void Append(std::wstring& str, const uint8_t* bytes, size_t length, CharacterSet charset); - - static void AppendLatin1(std::wstring& str, const std::string& latin1) { - auto ptr = (const uint8_t*)latin1.data(); - str.append(ptr, ptr + latin1.length()); - } - - static std::wstring FromLatin1(const std::string& latin1) { - auto ptr = (const uint8_t*)latin1.data(); - return std::wstring(ptr, ptr + latin1.length()); - } -}; + return BytesToUtf8(bytes, ToECI(cs)); +} + +CharacterSet GuessTextEncoding(ByteView bytes, CharacterSet fallback = CharacterSet::ISO8859_1); } // ZXing diff --git a/core/src/Utf.cpp b/core/src/Utf.cpp index d15be33765..59194b6862 100644 --- a/core/src/Utf.cpp +++ b/core/src/Utf.cpp @@ -14,13 +14,16 @@ #include #include -namespace ZXing { - -// TODO: c++20 has char8_t #if __cplusplus <= 201703L +#include "Range.h" using char8_t = uint8_t; +using utf8_t = ZXing::ArrayView; +#else +#include +using utf8_t = std::u8string_view; #endif -using utf8_t = std::basic_string_view; + +namespace ZXing { using state_t = uint8_t; constexpr state_t kAccepted = 0; diff --git a/core/src/WriteBarcode.cpp b/core/src/WriteBarcode.cpp index 8b26843959..c54a28563d 100644 --- a/core/src/WriteBarcode.cpp +++ b/core/src/WriteBarcode.cpp @@ -1,5 +1,6 @@ /* * Copyright 2024 Axel Waggershauser +* Copyright 2025 gitlost */ // SPDX-License-Identifier: Apache-2.0 @@ -7,6 +8,7 @@ #include "WriteBarcode.h" #include "BitMatrix.h" +#include "JSON.h" #if !defined(ZXING_READERS) && !defined(ZXING_WRITERS) #include "Version.h" @@ -16,6 +18,8 @@ #ifdef ZXING_USE_ZINT +#include "DecoderResult.h" +#include "DetectorResult.h" #include #else @@ -29,6 +33,7 @@ namespace ZXing { struct CreatorOptions::Data { BarcodeFormat format; + std::string options; bool readerInit = false; bool forceSquareDataMatrix = false; std::string ecLevel; @@ -39,28 +44,38 @@ struct CreatorOptions::Data mutable unique_zint_symbol zint; #ifndef __cpp_aggregate_paren_init - Data(BarcodeFormat f) : format(f) {} + Data(BarcodeFormat f, std::string o) : format(f), options(std::move(o)) {} #endif }; #define ZX_PROPERTY(TYPE, NAME) \ - TYPE CreatorOptions::NAME() const noexcept { return d->NAME; } \ + const TYPE& CreatorOptions::NAME() const noexcept { return d->NAME; } \ CreatorOptions& CreatorOptions::NAME(TYPE v)& { return d->NAME = std::move(v), *this; } \ CreatorOptions&& CreatorOptions::NAME(TYPE v)&& { return d->NAME = std::move(v), std::move(*this); } - ZX_PROPERTY(BarcodeFormat, format) - ZX_PROPERTY(bool, readerInit) - ZX_PROPERTY(bool, forceSquareDataMatrix) - ZX_PROPERTY(std::string, ecLevel) +ZX_PROPERTY(BarcodeFormat, format) +ZX_PROPERTY(bool, readerInit) +ZX_PROPERTY(bool, forceSquareDataMatrix) +ZX_PROPERTY(std::string, ecLevel) +ZX_PROPERTY(std::string, options) #undef ZX_PROPERTY -CreatorOptions::CreatorOptions(BarcodeFormat format) : d(std::make_unique(format)) {} +#define ZX_RO_PROPERTY(TYPE, NAME) \ + std::optional CreatorOptions::NAME() const noexcept { return JsonGet(d->options, #NAME); } + +ZX_RO_PROPERTY(bool, gs1); +ZX_RO_PROPERTY(bool, stacked); +ZX_RO_PROPERTY(int, version); +ZX_RO_PROPERTY(int, dataMask); + +#undef ZX_RO_PROPERTY + +CreatorOptions::CreatorOptions(BarcodeFormat format, std::string options) : d(std::make_unique(format, std::move(options))) {} CreatorOptions::~CreatorOptions() = default; CreatorOptions::CreatorOptions(CreatorOptions&&) = default; CreatorOptions& CreatorOptions::operator=(CreatorOptions&&) = default; - struct WriterOptions::Data { int scale = 0; @@ -88,9 +103,11 @@ WriterOptions::~WriterOptions() = default; WriterOptions::WriterOptions(WriterOptions&&) = default; WriterOptions& WriterOptions::operator=(WriterOptions&&) = default; -static bool IsLinearCode(BarcodeFormat format) +static bool SupportsGS1(BarcodeFormat format) { - return BarcodeFormats(BarcodeFormat::LinearCodes).testFlag(format); + return (BarcodeFormat::Aztec | BarcodeFormat::Code128 | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode + | BarcodeFormat::RMQRCode | BarcodeFormat::DataBarExpanded) + .testFlag(format); } static std::string ToSVG(ImageView iv) @@ -136,7 +153,10 @@ static Image ToImage(BitMatrix bits, bool isLinearCode, const WriterOptions& opt #ifdef ZXING_USE_ZINT #include "ECI.h" + +#ifdef ZXING_READERS #include "ReadBarcode.h" +#endif #include #include @@ -160,8 +180,8 @@ static constexpr BarcodeFormatZXing2Zint barcodeFormatZXing2Zint[] = { {BarcodeFormat::DataBarLimited, BARCODE_DBAR_LTD}, {BarcodeFormat::DataMatrix, BARCODE_DATAMATRIX}, {BarcodeFormat::DXFilmEdge, BARCODE_DXFILMEDGE}, - {BarcodeFormat::EAN8, BARCODE_EANX}, - {BarcodeFormat::EAN13, BARCODE_EANX}, + {BarcodeFormat::EAN8, BARCODE_EAN8}, + {BarcodeFormat::EAN13, BARCODE_EAN13}, {BarcodeFormat::ITF, BARCODE_C25INTER}, {BarcodeFormat::MaxiCode, BARCODE_MAXICODE}, {BarcodeFormat::MicroQRCode, BARCODE_MICROQR}, @@ -181,33 +201,33 @@ struct String2Int static int ParseECLevel(int symbology, std::string_view s) { constexpr std::string_view EC_LABELS_QR[4] = {"L", "M", "Q", "H"}; - int res = 0; + + // Convert L/M/Q/H to Zint 1-4 if (Contains({BARCODE_QRCODE, BARCODE_MICROQR, BARCODE_RMQR}, symbology)) - if ((res = IndexOf(EC_LABELS_QR, s) != -1)) + if ((res = IndexOf(EC_LABELS_QR, s)) != -1) return res + 1; if (std::from_chars(s.data(), s.data() + s.size() - (s.back() == '%'), res).ec != std::errc{}) - throw std::invalid_argument("Invalid ecLevel: '" + std::string(s) + "'"); + throw std::invalid_argument(StrCat("Invalid ecLevel: '", s, "'")); auto findClosestECLevel = [](const std::vector& list, int val) { int mIdx = -2, mAbs = 100; for (int i = 0; i < Size(list); ++i) - if (int abs = std::abs(val - list[i]); abs < mAbs) { + if (int abs = std::abs(val - list[i]); abs < mAbs) { mIdx = i; mAbs = abs; - } + } return mIdx + 1; }; - if (s.back()=='%'){ + // Convert percentage to Zint + if (s.back() == '%') { switch (symbology) { - case BARCODE_QRCODE: - case BARCODE_MICROQR: - case BARCODE_RMQR: - return findClosestECLevel({20, 37, 55, 65}, res); - case BARCODE_AZTEC: - return findClosestECLevel({10, 23, 26, 50}, res); + case BARCODE_QRCODE: return findClosestECLevel({20, 37, 55, 65}, res); + case BARCODE_MICROQR: return findClosestECLevel({20, 37, 55}, res); + case BARCODE_RMQR: return res <= 46 ? 2 : 4; + case BARCODE_AZTEC: return findClosestECLevel({10, 23, 36, 50}, res); case BARCODE_PDF417: // TODO: do something sensible with PDF417? default: @@ -218,25 +238,144 @@ static int ParseECLevel(int symbology, std::string_view s) return res; }; +static constexpr struct { BarcodeFormat format; SymbologyIdentifier si; } barcodeFormat2SymbologyIdentifier[] = { + {BarcodeFormat::Aztec, {'z', '0', 3}}, // '1' GS1, '2' AIM + {BarcodeFormat::Codabar, {'F', '0'}}, // if checksum processing were implemented and checksum present and stripped then modifier would be 4 + // {BarcodeFormat::CodablockF, {'O', '4'}}, // '5' GS1 + {BarcodeFormat::Code128, {'C', '0'}}, // '1' GS1, '2' AIM + // {BarcodeFormat::Code16K, {'K', '0'}}, // '1' GS1, '2' AIM, '4' D1 PAD + {BarcodeFormat::Code39, {'A', '0'}}, // '3' checksum, '4' extended, '7' checksum,extended + {BarcodeFormat::Code93, {'G', '0'}}, // no modifiers + {BarcodeFormat::DataBar, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarExpanded, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataBarLimited, {'e', '0', 0, AIFlag::GS1}}, + {BarcodeFormat::DataMatrix, {'d', '1', 3}}, // '2' GS1, '3' AIM + // {BarcodeFormat::DotCode, {'J', '0', 3}}, // '1' GS1, '2' AIM + {BarcodeFormat::DXFilmEdge, {}}, + {BarcodeFormat::EAN8, {'E', '4'}}, + {BarcodeFormat::EAN13, {'E', '0'}}, + // {BarcodeFormat::HanXin, {'h', '0', 1}}, // '2' GS1 + {BarcodeFormat::ITF, {'I', '0'}}, // '1' check digit + {BarcodeFormat::MaxiCode, {'U', '0', 2}}, // '1' mode 2 or 3 + // {BarcodeFormat::MicroPDF417, {'L', '2', char(-1)}}, + {BarcodeFormat::MicroQRCode, {'Q', '1', 1}}, + {BarcodeFormat::PDF417, {'L', '2', char(-1)}}, + {BarcodeFormat::QRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM + {BarcodeFormat::RMQRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM + {BarcodeFormat::UPCA, {'E', '0'}}, + {BarcodeFormat::UPCE, {'E', '0'}}, +}; + +static SymbologyIdentifier SymbologyIdentifierZint2ZXing(const CreatorOptions& opts, const ByteArray& ba) +{ + const BarcodeFormat format = opts.format(); + + auto i = FindIf(barcodeFormat2SymbologyIdentifier, [format](auto& v) { return v.format == format; }); + assert(i != std::end(barcodeFormat2SymbologyIdentifier)); + SymbologyIdentifier ret = i->si; + + if ((BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE).testFlag(format)) { + if (ba.size() > 13) // Have EAN-2/5 add-on? + ret.modifier = '3'; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on + } else if (format == BarcodeFormat::Code39) { + if (FindIf(ba, iscntrl) != ba.end()) // Extended Code 39? + ret.modifier = static_cast(ret.modifier + 4); + } else if (opts.gs1() && SupportsGS1(format)) { + if ((BarcodeFormat::Aztec | BarcodeFormat::Code128).testFlag(format)) + ret.modifier = '1'; + else if (format == BarcodeFormat::DataMatrix) + ret.modifier = '2'; + else if ((BarcodeFormat::QRCode | BarcodeFormat::RMQRCode).testFlag(format)) + ret.modifier = '3'; + ret.aiFlag = AIFlag::GS1; + } + + return ret; +} + +static std::string ECLevelZint2ZXing(const zint_symbol* zint) +{ + constexpr char EC_LABELS_QR[4] = {'L', 'M', 'Q', 'H'}; + + const int symbology = zint->symbology; + const int option_1 = zint->option_1; + + switch (symbology) { + case BARCODE_AZTEC: + if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99) + return std::to_string(option_1 >> 8) + "%"; + break; + case BARCODE_MAXICODE: + // Mode + if (option_1 >= 2 && option_1 <= 6) + return std::to_string(option_1); + break; + case BARCODE_PDF417: + case BARCODE_PDF417COMP: + // Convert to percentage + if (option_1 >= 0 && option_1 <= 8) { + int overhead = symbology == BARCODE_PDF417COMP ? 35 : 69; + int cols = (zint->width - overhead) / 17; + int tot_cws = zint->rows * cols; + assert(tot_cws); + return std::to_string((2 << option_1) * 100 / tot_cws) + "%"; + } + break; + // case BARCODE_MICROPDF417: + // if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99) + // return std::to_string(option_1 >> 8) + "%"; + // break; + case BARCODE_QRCODE: + case BARCODE_MICROQR: + case BARCODE_RMQR: + // Convert to L/M/Q/H + if (option_1 >= 1 && option_1 <= 4) + return {EC_LABELS_QR[option_1 - 1]}; + break; + // case BARCODE_HANXIN: + // if (option_1 >= 1 && option_1 <= 4) + // return "L" + std::to_string(option_1); + // break; + default: + break; + } + + return {}; +} + zint_symbol* CreatorOptions::zint() const { auto& zint = d->zint; if (!zint) { #ifdef PRINT_DEBUG - printf("zint version: %d, sizeof(zint_symbol): %ld\n", ZBarcode_Version(), sizeof(zint_symbol)); +// printf("zint version: %d, sizeof(zint_symbol): %ld, options: %s\n", ZBarcode_Version(), sizeof(zint_symbol), options().c_str()); #endif zint.reset(ZBarcode_Create()); auto i = FindIf(barcodeFormatZXing2Zint, [zxing = format()](auto& v) { return v.zxing == zxing; }); if (i == std::end(barcodeFormatZXing2Zint)) throw std::invalid_argument("unsupported barcode format: " + ToString(format())); - zint->symbology = i->zint; + + if (format() == BarcodeFormat::Code128 && gs1()) + zint->symbology = BARCODE_GS1_128; + else if (format() == BarcodeFormat::DataBar && stacked()) + zint->symbology = BARCODE_DBAR_OMNSTK; + else if (format() == BarcodeFormat::DataBarExpanded && stacked()) + zint->symbology = BARCODE_DBAR_EXPSTK; + else + zint->symbology = i->zint; zint->scale = 0.5f; if (!ecLevel().empty()) zint->option_1 = ParseECLevel(zint->symbology, ecLevel()); + + if (auto val = version(); val && !IsLinearBarcode(format())) + zint->option_2 = *val; + + if (auto val = dataMask(); val && (BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode).testFlag(format())) + zint->option_3 = (zint->option_3 & 0xFF) | (*val + 1) << 8; } return zint.get(); @@ -244,14 +383,16 @@ zint_symbol* CreatorOptions::zint() const #define CHECK(ZINT_CALL) \ if (int err = (ZINT_CALL); err >= ZINT_ERROR) \ - throw std::invalid_argument(zint->errtxt); + throw std::invalid_argument(StrCat(zint->errtxt, " (retval: ", std::to_string(err), ")")); Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& opts) { auto zint = opts.zint(); - zint->input_mode = mode; - zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_QUIET_ZONES; + zint->input_mode = mode == UNICODE_MODE && opts.gs1() && SupportsGS1(opts.format()) ? GS1_MODE : mode; + if (mode == UNICODE_MODE && static_cast(data)[0] != '[') + zint->input_mode |= GS1PARENS_MODE; + zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_QUIET_ZONES | BARCODE_RAW_TEXT; if (mode == DATA_MODE && ZBarcode_Cap(zint->symbology, ZINT_CAP_ECI)) zint->eci = static_cast(ECI::Binary); @@ -262,7 +403,7 @@ Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions printf("create symbol with size: %dx%d\n", zint->width, zint->rows); #endif -#ifdef ZXING_READERS +#if 0 // use ReadBarcode to create Barcode object auto buffer = std::vector(zint->bitmap_width * zint->bitmap_height); std::transform(zint->bitmap, zint->bitmap + zint->bitmap_width * zint->bitmap_height, buffer.data(), [](unsigned char v) { return (v == '0') * 0xff; }); @@ -270,8 +411,37 @@ Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions auto res = ReadBarcode({buffer.data(), zint->bitmap_width, zint->bitmap_height, ImageFormat::Lum}, ReaderOptions().setFormats(opts.format()).setIsPure(true).setBinarizer(Binarizer::BoolCast)); #else - //TODO: replace by proper construction from encoded data from within zint - auto res = Barcode(std::string((const char*)data, size), 0, 0, 0, opts.format(), {}); + Content content; + +#ifdef ZXING_READERS + for (int i = 0; i < zint->raw_seg_count; ++i) { + const auto& raw_seg = zint->raw_segs[i]; +#ifdef PRINT_DEBUG + printf(" seg %d of %d with eci %d: %.*s\n", i, zint->raw_seg_count, raw_seg.eci, raw_seg.length, (char*)raw_seg.source); +#endif + if (ECI(raw_seg.eci) != ECI::ISO8859_1) + content.switchEncoding(ECI(raw_seg.eci)); + else + content.switchEncoding(CharacterSet::ISO8859_1); // set this as default to prevent guessing without setting "hasECI" + content.append({raw_seg.source, static_cast(raw_seg.length - (opts.format() == BarcodeFormat::Code93 ? 2 : 0))}); + } +#else + if (zint->text_length) { + content.switchEncoding(ECI::UTF8); + content.append({zint->text, static_cast(zint->text_length)}); + } else { + content.switchEncoding(mode == DATA_MODE ? ECI::Binary : ECI::UTF8); + content.append({static_cast(data), static_cast(size)}); + } +#endif + + content.symbology = SymbologyIdentifierZint2ZXing(opts, content.bytes); + + DecoderResult decRes(std::move(content)); + decRes.setEcLevel(ECLevelZint2ZXing(zint)); + DetectorResult detRes; + + auto res = Barcode(std::move(decRes), std::move(detRes), opts.format()); #endif auto bits = BitMatrix(zint->bitmap_width, zint->bitmap_height); @@ -353,7 +523,7 @@ Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& o writer.setEccLevel(std::stoi(opts.ecLevel())); writer.setEncoding(CharacterSet::UTF8); // write UTF8 (ECI value 26) for maximum compatibility - return CreateBarcode(writer.encode(std::string(contents), 0, IsLinearCode(opts.format()) ? 50 : 0), opts); + return CreateBarcode(writer.encode(std::string(contents), 0, IsLinearBarcode(opts.format()) ? 50 : 0), opts); } #if __cplusplus > 201703L @@ -366,7 +536,7 @@ Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& opts) { std::wstring bytes; - for (uint8_t c : std::basic_string_view((uint8_t*)data, size)) + for (uint8_t c : ByteView(data, size)) bytes.push_back(c); auto writer = MultiFormatWriter(opts.format()).setMargin(0); @@ -374,7 +544,7 @@ Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& writer.setEccLevel(std::stoi(opts.ecLevel())); writer.setEncoding(CharacterSet::BINARY); - return CreateBarcode(writer.encode(bytes, 0, IsLinearCode(opts.format()) ? 50 : 0), opts); + return CreateBarcode(writer.encode(bytes, 0, IsLinearBarcode(opts.format()) ? 50 : 0), opts); } } // namespace ZXing @@ -436,7 +606,7 @@ Image WriteBarcodeToImage(const Barcode& barcode, [[maybe_unused]] const WriterO auto zint = barcode.zint(); if (!zint) - return ToImage(barcode._symbol->copy(), IsLinearCode(barcode.format()), opts); + return ToImage(barcode._symbol->copy(), IsLinearBarcode(barcode.format()), opts); #if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT) auto resetOnExit = SetCommonWriterOptions(zint, opts); @@ -470,6 +640,11 @@ std::string WriteBarcodeToUtf8(const Barcode& barcode, [[maybe_unused]] const Wr bool inverted = false; // TODO: take from WriterOptions for (int y = 0; y < iv.height(); y += 2) { + // for linear barcodes, only print line pairs that are distinct from the previous one + if (IsLinearBarcode(barcode.format()) && y > 1 && y < iv.height() - 1 + && memcmp(iv.data(0, y), iv.data(0, y - 2), 2 * iv.rowStride()) == 0) + continue; + for (int x = 0; x < iv.width(); ++x) { int tp = bool(*iv.data(x, y)) ^ inverted; int bt = (iv.height() == 1 && tp) || (y + 1 < iv.height() && (bool(*iv.data(x, y + 1)) ^ inverted)); diff --git a/core/src/WriteBarcode.h b/core/src/WriteBarcode.h index 6f149b2523..aa3cf5bec2 100644 --- a/core/src/WriteBarcode.h +++ b/core/src/WriteBarcode.h @@ -11,6 +11,7 @@ #include "ImageView.h" #include +#include #include extern "C" struct zint_symbol; @@ -26,7 +27,7 @@ class CreatorOptions friend Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& options); public: - CreatorOptions(BarcodeFormat format); + CreatorOptions(BarcodeFormat format, std::string options = {}); ~CreatorOptions(); CreatorOptions(CreatorOptions&&); @@ -35,7 +36,7 @@ class CreatorOptions zint_symbol* zint() const; #define ZX_PROPERTY(TYPE, NAME) \ - TYPE NAME() const noexcept; \ + const TYPE& NAME() const noexcept; \ CreatorOptions& NAME(TYPE v)&; \ CreatorOptions&& NAME(TYPE v)&&; @@ -43,8 +44,18 @@ class CreatorOptions ZX_PROPERTY(bool, readerInit) ZX_PROPERTY(bool, forceSquareDataMatrix) ZX_PROPERTY(std::string, ecLevel) + ZX_PROPERTY(std::string, options) #undef ZX_PROPERTY + +#define ZX_RO_PROPERTY(TYPE, NAME) \ + std::optional NAME() const noexcept; + + ZX_RO_PROPERTY(bool, gs1); + ZX_RO_PROPERTY(bool, stacked); + ZX_RO_PROPERTY(int, version); + ZX_RO_PROPERTY(int, dataMask); +#undef ZX_RO_PROPERTY }; /** diff --git a/core/src/ZXAlgorithms.h b/core/src/ZXAlgorithms.h index 66898363eb..487b1cafcd 100644 --- a/core/src/ZXAlgorithms.h +++ b/core/src/ZXAlgorithms.h @@ -8,11 +8,13 @@ #include "Error.h" #include +#include #include #include #include #include #include +#include #include namespace ZXing { @@ -46,6 +48,10 @@ inline bool Contains(const char* str, char c) { return strchr(str, c) != nullptr; } +inline bool Contains(std::string_view str, std::string_view substr) { + return str.find(substr) != std::string_view::npos; +} + template