#include "CreateBarcode.h"
#include "BarcodeData.h"
#include "BitMatrix.h"
#include "ByteArray.h"
#include "DecoderResult.h"
#include "DetectorResult.h"
#include "JSON.h"
#if !defined(ZXING_READERS) && !defined(ZXING_WRITERS)
#include "Version.h"
#endif
#ifdef ZXING_READERS
#include "ReadBarcode.h"
#endif
#ifdef ZXING_USE_ZINT
#include "ECI.h"
#include "TextEncoder.h"
#include <zint.h>
#else
#include "MultiFormatWriter.h"
#endif
#include <charconv>
#include <optional>
using namespace std::literals;
namespace ZXing {
struct CreatorOptions::Data
{
BarcodeFormat format;
std::string options;
mutable unique_zint_symbol zint;
};
#define ZX_PROPERTY(TYPE, 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(std::string, options)
#undef ZX_PROPERTY
#define ZX_RO_PROPERTY(TYPE, NAME) \
std::optional<TYPE> CreatorOptions::NAME() const noexcept { return JsonGet<TYPE>(d->options, #NAME); }
ZX_RO_PROPERTY(std::string, ecLevel);
ZX_RO_PROPERTY(std::string, eci);
ZX_RO_PROPERTY(bool, gs1);
ZX_RO_PROPERTY(bool, readerInit);
ZX_RO_PROPERTY(bool, forceSquare);
ZX_RO_PROPERTY(int, columns);
ZX_RO_PROPERTY(int, rows);
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<Data>(format, std::move(options))) {}
CreatorOptions::~CreatorOptions() = default;
CreatorOptions::CreatorOptions(CreatorOptions&&) noexcept = default;
CreatorOptions& CreatorOptions::operator=(CreatorOptions&&) noexcept = default;
inline bool IsAscii(ByteView bv)
{
return std::all_of(bv.begin(), bv.end(), [](uint8_t c) { return c <= 127; });
}
#ifdef ZXING_WRITERS
#ifdef ZXING_USE_ZINT
struct String2Int
{
const char* str;
int val;
};
static int ParseECLevel(int symbology, std::string_view s)
{
constexpr std::string_view EC_LABELS_QR[4] = {"L", "M", "Q", "H"};
int res = 0;
if (Contains({BARCODE_QRCODE, BARCODE_MICROQR, BARCODE_RMQR}, symbology))
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(StrCat("Invalid ecLevel: '", s, "'"));
auto findClosestECLevel = [](const std::vector<int>& 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) {
mIdx = i;
mAbs = abs;
}
return mIdx + 1;
};
if (s.back() == '%') {
switch (symbology) {
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:
default:
return -1;
}
}
return res;
};
static constexpr struct { BarcodeFormat format; SymbologyIdentifier si; } barcodeFormat2SymbologyIdentifier[] = {
{BarcodeFormat::Aztec, {'z', '0', 3}}, {BarcodeFormat::Codabar, {'F', '0'}}, {BarcodeFormat::Code128, {'C', '0'}}, {BarcodeFormat::Code39Ext, {'A', '4'}}, {BarcodeFormat::DataBar, {'e', '0', 0, AIFlag::GS1}},
{BarcodeFormat::DataBarOmni, {'e', '0', 0, AIFlag::GS1}},
{BarcodeFormat::DataBarStk, {'e', '0', 0, AIFlag::GS1}},
{BarcodeFormat::DataBarStkOmni, {'e', '0', 0, AIFlag::GS1}},
{BarcodeFormat::DataBarExp, {'e', '0', 0, AIFlag::GS1}},
{BarcodeFormat::DataBarExpStk, {'e', '0', 0, AIFlag::GS1}},
{BarcodeFormat::DataBarLtd, {'e', '0', 0, AIFlag::GS1}},
{BarcodeFormat::DataMatrix, {'d', '1', 3}}, {BarcodeFormat::DXFilmEdge, {'X', 'F'}},
{BarcodeFormat::EAN8, {'E', '4'}},
{BarcodeFormat::MaxiCode, {'U', '0', 2}}, {BarcodeFormat::MicroPDF417, {'L', '2', -1}},
{BarcodeFormat::MicroQRCode, {'Q', '1', 1}},
{BarcodeFormat::PDF417, {'L', '2', -1}},
{BarcodeFormat::QRCode, {'Q', '1', 1}}, {BarcodeFormat::RMQRCode, {'Q', '1', 1}}, };
static SymbologyIdentifier SymbologyIdentifierZint2ZXing(const CreatorOptions& opts, const ByteArray& ba)
{
using enum BarcodeFormat;
const BarcodeFormat format = opts.format();
auto i = FindIf(barcodeFormat2SymbologyIdentifier, [format](auto& v) { return v.format == format; });
SymbologyIdentifier ret = i != std::end(barcodeFormat2SymbologyIdentifier) ? i->si : SymbologyIdentifier{SymbologyKey(format), '0'};
if (format & (EAN13 | UPCA | UPCE)) {
if (ba.size() > 13) ret.modifier = '3'; } else if (opts.gs1() && format & AllGS1) {
if (format & (Aztec | Code128))
ret.modifier = '1';
else if (format == DataMatrix)
ret.modifier = '2';
else if (format & (QRCode | RMQRCode))
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:
if (option_1 >= 2 && option_1 <= 6)
return std::to_string(option_1);
break;
case BARCODE_PDF417:
case BARCODE_PDF417COMP:
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_QRCODE:
case BARCODE_MICROQR:
case BARCODE_RMQR:
if (option_1 >= 1 && option_1 <= 4)
return {EC_LABELS_QR[option_1 - 1]};
break;
default:
break;
}
return {};
}
#ifndef ZXING_READERS
static std::string BinaryToUtf8(ByteView ba)
{
std::string utf8;
utf8.reserve(ba.size() * 2);
for (auto c : ba)
if (c < 0x80) {
utf8.push_back(c);
} else if (c < 0xC0) {
utf8.push_back(0xC2);
utf8.push_back(c);
} else {
utf8.push_back(0xC3);
utf8.push_back(c - 0x40);
}
return utf8;
}
#endif
zint_symbol* CreatorOptions::zint() const
{
using enum BarcodeFormat;
auto& zint = d->zint;
if (!zint) {
#ifdef PRINT_DEBUG
#endif
zint.reset(ZBarcode_Create());
switch (format()) {
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \
case BarcodeFormat(ZX_BCF_ID(SYM, VAR)): zint->symbology = ZINT; break;
ZX_BCF_LIST(X)
#undef X
};
if (zint->symbology == 0)
throw std::invalid_argument(StrCat("Unsupported barcode format for creation: ", ToString(format())));
if (format() == Code128 && gs1())
zint->symbology = BARCODE_GS1_128;
zint->scale = 0.5f;
if (auto val = ecLevel(); val)
zint->option_1 = ParseECLevel(zint->symbology, *val);
if (auto val = version(); val && !(format() & AllLinear))
zint->option_2 = *val;
if (auto val = columns(); val && format() & (DataBarExpStk | PDF417 | MicroPDF417 | CompactPDF417))
zint->option_2 = *val;
if (auto val = rows(); val && format() & (DataBarExpStk | PDF417))
zint->option_3 = *val;
if (auto val = dataMask(); val && format() & (QRCode | MicroQRCode))
zint->option_3 = (zint->option_3 & 0xFF) | (*val + 1) << 8;
if (format() == DataMatrix)
zint->option_3 = (forceSquare() ? DM_SQUARE : DM_DMRE) | DM_ISO_144;
}
return zint.get();
}
#define CHECK_WARN(ZINT_CALL, WARN) \
if (WARN = (ZINT_CALL); WARN >= ZINT_ERROR) \
throw std::invalid_argument(StrCat(zint->errtxt, " (retval: ", std::to_string(WARN), ")"));
Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& opts)
{
auto zint = opts.zint();
zint->input_mode = mode == UNICODE_MODE && opts.gs1() && (opts.format() & BarcodeFormat::AllGS1) ? GS1_MODE : mode;
if (mode == UNICODE_MODE && static_cast<const char*>(data)[0] != '[')
zint->input_mode |= GS1PARENS_MODE;
zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_NO_QUIET_ZONES | BARCODE_CONTENT_SEGS;
if (opts.readerInit())
zint->output_options |= READER_INIT;
if (ZBarcode_Cap(zint->symbology, ZINT_CAP_ECI)) {
if (auto eci = opts.eci(); eci) {
if (auto cs = CharacterSetFromString(*eci); cs != CharacterSet::Unknown) {
zint->eci = static_cast<int>(ToECI(cs));
} else if (std::all_of(eci->begin(), eci->end(), [](char c) { return std::isdigit(c); })) {
zint->eci = std::stoi(*eci);
}
} else if (mode == DATA_MODE) {
zint->eci = static_cast<int>(ECI::Binary);
}
}
int warning;
CHECK_WARN(ZBarcode_Encode_and_Buffer(zint, (uint8_t*)data, size, 0), warning);
#ifdef PRINT_DEBUG
printf("create symbol with size: %dx%d\n", zint->width, zint->rows);
#endif
#if 0#else
assert(zint->content_seg_count == 1);
const auto& content_seg = zint->content_segs[0];
const size_t content_seg_len =
static_cast<size_t>(content_seg.length - (opts.format() == BarcodeFormat::Code93 && content_seg.length >= 2 ? 2 : 0));
Content content;
if (zint->eci || warning == ZINT_WARN_USES_ECI)
content.switchEncoding(ECI(content_seg.eci));
else
content.switchEncoding(ToCharacterSet(ECI(content_seg.eci)));
if ((zint->input_mode & 0x07) == UNICODE_MODE) {
std::string utf8(reinterpret_cast<const char *>(content_seg.source), content_seg_len);
content.append(TextEncoder::FromUnicode(utf8, ToCharacterSet(ECI(content_seg.eci))));
#ifndef ZXING_READERS
content.utf8Cache.push_back(std::move(utf8));
#endif
} else {
content.append({content_seg.source, content_seg_len});
#ifndef ZXING_READERS
content.utf8Cache.push_back(BinaryToUtf8(content.bytes));
#endif
}
content.symbology = SymbologyIdentifierZint2ZXing(opts, content.bytes);
DecoderResult decRes(std::move(content));
decRes.setEcLevel(ECLevelZint2ZXing(zint));
decRes.setReaderInit(zint->output_options & READER_INIT);
auto bits = BitMatrix(zint->bitmap_width, zint->bitmap_height);
std::transform(zint->bitmap, zint->bitmap + zint->bitmap_width * zint->bitmap_height, bits.row(0).begin(),
[](unsigned char v) { return (v == '1') * BitMatrix::SET_V; });
int left, top, width, height;
bits.findBoundingBox(left, top, width, height);
auto res = MatrixBarcode(std::move(decRes), {std::move(bits), Rectangle<PointI>(left, top, width, height)}, opts.format());
#endif
res.zint = std::move(opts.d->zint);
return res;
}
Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& options)
{
return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, options);
}
Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& options)
{
return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, options);
}
Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& options)
{
return CreateBarcode(data, size, DATA_MODE, options);
}
#else
zint_symbol* CreatorOptions::zint() const { return nullptr; }
static Barcode CreateBarcode(BitMatrix&& bits, std::string_view contents, const CreatorOptions& opts)
{
Content content;
content.append(contents);
DecoderResult decRes(std::move(content));
DetectorResult detRes(std::move(bits), Rectangle<PointI>(0, 0, bits.width(), bits.height()));
return MatrixBarcode(std::move(decRes), std::move(detRes), opts.format());
}
Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& opts)
{
auto writer = MultiFormatWriter(opts.format()).setMargin(0);
if (auto ecLevel = opts.ecLevel(); ecLevel && ecLevel->size() == 1 && strchr("012345678", (*ecLevel)[0]))
writer.setEccLevel(std::stoi(*ecLevel));
if (!IsAscii({(const uint8_t*)contents.data(), contents.size()}))
writer.setEncoding(CharacterSet::UTF8);
return CreateBarcode(writer.encode(std::string(contents), 0, opts.format() & BarcodeFormat::AllLinear ? 50 : 0), contents, opts);
}
Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& opts)
{
return CreateBarcodeFromText({reinterpret_cast<const char*>(contents.data()), contents.size()}, opts);
}
Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& opts)
{
std::wstring bytes;
for (uint8_t c : ByteView(data, size))
bytes.push_back(c);
auto writer = MultiFormatWriter(opts.format()).setMargin(0);
if (auto ecLevel = opts.ecLevel(); ecLevel && ecLevel->size() == 1 && strchr("012345678", (*ecLevel)[0]))
writer.setEccLevel(std::stoi(*ecLevel));
writer.setEncoding(CharacterSet::BINARY);
return CreateBarcode(writer.encode(bytes, 0, opts.format() & BarcodeFormat::AllLinear ? 50 : 0), {(const char*)data, (size_t)size}, opts);
}
#endif
#else
zint_symbol* CreatorOptions::zint() const { return nullptr; }
Barcode CreateBarcodeFromText(std::string_view, const CreatorOptions&)
{
throw std::runtime_error("This build of zxing-cpp does not support creating barcodes.");
}
Barcode CreateBarcodeFromText(std::u8string_view, const CreatorOptions&)
{
throw std::runtime_error("This build of zxing-cpp does not support creating barcodes.");
}
Barcode CreateBarcodeFromBytes(const void*, int, const CreatorOptions&)
{
throw std::runtime_error("This build of zxing-cpp does not support creating barcodes.");
}
#endif
}