Skip to content

Commit f825e96

Browse files
committed
WriteBarcode: use new BARCODE_RAW_TEXT feature from libzint
This is a significant step forward towards a complete libzint based writer backend. It removes the use of the reader API to generate the Barcode object after creating the symbol with libzint. This adds a string based `options` member to `CreatorOptions` to pass symbology specific flags and options. This is an experiment and is motivated by the discussion in zxing-cpp#862. This changeset draws heavily from the work done by @gitlost in his diagnostics2 branch.
1 parent d74d5ba commit f825e96

File tree

9 files changed

+530
-22
lines changed

9 files changed

+530
-22
lines changed

core/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ set (COMMON_FILES
103103
src/CharacterSet.cpp
104104
src/Content.h
105105
src/Content.cpp
106+
src/DecoderResult.h
107+
src/DetectorResult.h
106108
src/ECI.h
107109
src/ECI.cpp
108110
src/Error.h
@@ -156,8 +158,6 @@ if (ZXING_READERS)
156158
src/ConcentricFinder.cpp
157159
src/DecodeHints.h
158160
$<$<BOOL:${BUILD_SHARED_LIBS}>:src/DecodeHints.cpp> # [[deprecated]]
159-
src/DecoderResult.h
160-
src/DetectorResult.h
161161
src/GlobalHistogramBinarizer.h
162162
src/GlobalHistogramBinarizer.cpp
163163
src/GridSampler.h

core/src/Content.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,13 @@ CharacterSet Content::guessEncoding() const
214214

215215
return TextDecoder::GuessEncoding(input.data(), input.size(), CharacterSet::ISO8859_1);
216216
#else
217-
return CharacterSet::Unknown;
217+
return CharacterSet::ISO8859_1;
218218
#endif
219219
}
220220

221221
ContentType Content::type() const
222222
{
223-
#ifdef ZXING_READERS
223+
#if 1 //def ZXING_READERS
224224
if (empty())
225225
return ContentType::Text;
226226

core/src/WriteBarcode.cpp

Lines changed: 194 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright 2024 Axel Waggershauser
3+
* Copyright 2025 gitlost
34
*/
45
// SPDX-License-Identifier: Apache-2.0
56

@@ -16,6 +17,9 @@
1617

1718
#ifdef ZXING_USE_ZINT
1819

20+
#include "oned/ODUPCEANCommon.h"
21+
#include "DecoderResult.h"
22+
#include "DetectorResult.h"
1923
#include <zint.h>
2024

2125
#else
@@ -29,6 +33,7 @@ namespace ZXing {
2933
struct CreatorOptions::Data
3034
{
3135
BarcodeFormat format;
36+
std::string options;
3237
bool readerInit = false;
3338
bool forceSquareDataMatrix = false;
3439
std::string ecLevel;
@@ -39,27 +44,35 @@ struct CreatorOptions::Data
3944
mutable unique_zint_symbol zint;
4045

4146
#ifndef __cpp_aggregate_paren_init
42-
Data(BarcodeFormat f) : format(f) {}
47+
Data(BarcodeFormat f, std::string o) : format(f), options(std::move(o)) {}
4348
#endif
4449
};
4550

4651
#define ZX_PROPERTY(TYPE, NAME) \
47-
TYPE CreatorOptions::NAME() const noexcept { return d->NAME; } \
52+
const TYPE& CreatorOptions::NAME() const noexcept { return d->NAME; } \
4853
CreatorOptions& CreatorOptions::NAME(TYPE v)& { return d->NAME = std::move(v), *this; } \
4954
CreatorOptions&& CreatorOptions::NAME(TYPE v)&& { return d->NAME = std::move(v), std::move(*this); }
5055

5156
ZX_PROPERTY(BarcodeFormat, format)
5257
ZX_PROPERTY(bool, readerInit)
5358
ZX_PROPERTY(bool, forceSquareDataMatrix)
5459
ZX_PROPERTY(std::string, ecLevel)
60+
ZX_PROPERTY(std::string, options)
5561

5662
#undef ZX_PROPERTY
5763

58-
CreatorOptions::CreatorOptions(BarcodeFormat format) : d(std::make_unique<Data>(format)) {}
59-
CreatorOptions::~CreatorOptions() = default;
60-
CreatorOptions::CreatorOptions(CreatorOptions&&) = default;
61-
CreatorOptions& CreatorOptions::operator=(CreatorOptions&&) = default;
64+
#define ZX_RO_PROPERTY(TYPE, NAME) \
65+
TYPE CreatorOptions::NAME() const noexcept { return Contains(std::string_view(d->options), std::string_view(#NAME)); }
6266

67+
ZX_RO_PROPERTY(bool, gs1);
68+
ZX_RO_PROPERTY(bool, stacked);
69+
70+
#undef ZX_PROPERTY
71+
72+
CreatorOptions::CreatorOptions(BarcodeFormat format, std::string options) : d(std::make_unique<Data>(format, std::move(options))) {}
73+
CreatorOptions::~CreatorOptions() = default;
74+
CreatorOptions::CreatorOptions(CreatorOptions&&) = default;
75+
CreatorOptions& CreatorOptions::operator=(CreatorOptions&&) = default;
6376

6477
struct WriterOptions::Data
6578
{
@@ -88,9 +101,10 @@ WriterOptions::~WriterOptions() = default;
88101
WriterOptions::WriterOptions(WriterOptions&&) = default;
89102
WriterOptions& WriterOptions::operator=(WriterOptions&&) = default;
90103

91-
static bool IsLinearCode(BarcodeFormat format)
104+
static bool SupportsGS1(BarcodeFormat format)
92105
{
93-
return BarcodeFormats(BarcodeFormat::LinearCodes).testFlag(format);
106+
using enum BarcodeFormat;
107+
return (Aztec | Code128 | DataMatrix | QRCode | RMQRCode).testFlag(format);
94108
}
95109

96110
static std::string ToSVG(ImageView iv)
@@ -136,7 +150,10 @@ static Image ToImage(BitMatrix bits, bool isLinearCode, const WriterOptions& opt
136150

137151
#ifdef ZXING_USE_ZINT
138152
#include "ECI.h"
153+
154+
#ifdef ZXING_READERS
139155
#include "ReadBarcode.h"
156+
#endif
140157

141158
#include <charconv>
142159
#include <zint.h>
@@ -218,6 +235,123 @@ static int ParseECLevel(int symbology, std::string_view s)
218235
return res;
219236
};
220237

238+
static constexpr struct { BarcodeFormat format; SymbologyIdentifier si; } barcodeFormat2SymbologyIdentifier[] = {
239+
{BarcodeFormat::Aztec, {'z', '0', 3}}, // '1' GS1, '2' AIM
240+
{BarcodeFormat::Codabar, {'F', '0'}}, // if checksum processing were implemented and checksum present and stripped then modifier would be 4
241+
// {BarcodeFormat::CodablockF, {'O', '4'}}, // '5' GS1
242+
{BarcodeFormat::Code128, {'C', '0'}}, // '1' GS1, '2' AIM
243+
// {BarcodeFormat::Code16K, {'K', '0'}}, // '1' GS1, '2' AIM, '4' D1 PAD
244+
{BarcodeFormat::Code39, {'A', '0'}}, // '3' checksum, '4' extended, '7' checksum,extended
245+
{BarcodeFormat::Code93, {'G', '0'}}, // no modifiers
246+
{BarcodeFormat::DataBar, {'e', '0', 0, AIFlag::GS1}},
247+
{BarcodeFormat::DataBarExpanded, {'e', '0', 0, AIFlag::GS1}},
248+
{BarcodeFormat::DataBarLimited, {'e', '0', 0, AIFlag::GS1}},
249+
{BarcodeFormat::DataMatrix, {'d', '1', 3}}, // '2' GS1, '3' AIM
250+
// {BarcodeFormat::DotCode, {'J', '0', 3}}, // '1' GS1, '2' AIM
251+
{BarcodeFormat::DXFilmEdge, {}},
252+
{BarcodeFormat::EAN8, {'E', '4'}},
253+
{BarcodeFormat::EAN13, {'E', '0'}},
254+
// {BarcodeFormat::HanXin, {'h', '0', 1}}, // '2' GS1
255+
{BarcodeFormat::ITF, {'I', '0'}}, // '1' check digit
256+
{BarcodeFormat::MaxiCode, {'U', '0', 2}}, // '1' mode 2 or 3
257+
// {BarcodeFormat::MicroPDF417, {'L', '2', char(-1)}},
258+
{BarcodeFormat::MicroQRCode, {'Q', '1', 1}},
259+
{BarcodeFormat::PDF417, {'L', '2', char(-1)}},
260+
{BarcodeFormat::QRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM
261+
{BarcodeFormat::RMQRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM
262+
{BarcodeFormat::UPCA, {'E', '0'}},
263+
{BarcodeFormat::UPCE, {'E', '0'}},
264+
};
265+
266+
static SymbologyIdentifier SymbologyIdentifierZint2ZXing(const CreatorOptions& opts, const ByteArray& ba)
267+
{
268+
const BarcodeFormat format = opts.format();
269+
270+
auto i = FindIf(barcodeFormat2SymbologyIdentifier, [format](auto& v) { return v.format == format; });
271+
assert(i != std::end(barcodeFormat2SymbologyIdentifier));
272+
SymbologyIdentifier ret = i->si;
273+
274+
if ((BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE).testFlag(format)) {
275+
if (Contains(ba.asString().data(), ' ')) // Have EAN-2/5 add-on?
276+
ret.modifier = '3'; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on
277+
} else if (format == BarcodeFormat::Code39) {
278+
if (FindIf(ba, iscntrl) != ba.end()) // Extended Code 39?
279+
ret.modifier = static_cast<char>(ret.modifier + 4);
280+
} else if (opts.gs1() && SupportsGS1(format)) {
281+
if ((BarcodeFormat::Aztec | BarcodeFormat::Code128).testFlag(format))
282+
ret.modifier = '1';
283+
else if (format == BarcodeFormat::DataMatrix)
284+
ret.modifier = '2';
285+
else if ((BarcodeFormat::QRCode | BarcodeFormat::RMQRCode).testFlag(format))
286+
ret.modifier = '3';
287+
ret.aiFlag = AIFlag::GS1;
288+
}
289+
290+
return ret;
291+
}
292+
293+
static std::string ECLevelZint2ZXing(const zint_symbol* zint)
294+
{
295+
constexpr char EC_LABELS_QR[4] = {'L', 'M', 'Q', 'H'};
296+
297+
const int symbology = zint->symbology;
298+
const int option_1 = zint->option_1;
299+
300+
switch (symbology) {
301+
case BARCODE_AZTEC:
302+
if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99)
303+
return std::to_string(option_1 >> 8) + "%";
304+
break;
305+
case BARCODE_MAXICODE:
306+
// Mode
307+
if (option_1 >= 2 && option_1 <= 6)
308+
return std::to_string(option_1);
309+
break;
310+
case BARCODE_PDF417:
311+
case BARCODE_PDF417COMP:
312+
// Convert to percentage
313+
if (option_1 >= 0 && option_1 <= 8) {
314+
int overhead = symbology == BARCODE_PDF417COMP ? 35 : 69;
315+
int cols = (zint->width - overhead) / 17;
316+
int tot_cws = zint->rows * cols;
317+
assert(tot_cws);
318+
return std::to_string((2 << option_1) * 100 / tot_cws) + "%";
319+
}
320+
break;
321+
// case BARCODE_MICROPDF417:
322+
// if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99)
323+
// return std::to_string(option_1 >> 8) + "%";
324+
// break;
325+
case BARCODE_QRCODE:
326+
case BARCODE_MICROQR:
327+
case BARCODE_RMQR:
328+
// Convert to L/M/Q/H
329+
if (option_1 >= 1 && option_1 <= 4)
330+
return {EC_LABELS_QR[option_1 - 1]};
331+
break;
332+
// case BARCODE_HANXIN:
333+
// if (option_1 >= 1 && option_1 <= 4)
334+
// return "L" + std::to_string(option_1);
335+
// break;
336+
default:
337+
break;
338+
}
339+
340+
return {};
341+
}
342+
343+
static std::string NormalizedOptionsString(std::string_view sv)
344+
{
345+
std::string str(sv);
346+
std::transform(str.begin(), str.end(), str.begin(), [](char c) { return (char)std::tolower(c); });
347+
#ifdef __cpp_lib_erase_if
348+
std::erase_if(str, [](char c) { return Contains("\n \"", c); });
349+
#else
350+
str.erase(std::remove_if(str.begin(), str.end(), [](char c) { return Contains("\n \"", c); }), str.end());
351+
#endif
352+
return str;
353+
}
354+
221355
zint_symbol* CreatorOptions::zint() const
222356
{
223357
auto& zint = d->zint;
@@ -228,10 +362,23 @@ zint_symbol* CreatorOptions::zint() const
228362
#endif
229363
zint.reset(ZBarcode_Create());
230364

365+
d->options = NormalizedOptionsString(options());
366+
#ifdef PRINT_DEBUG
367+
printf("options: %s\n", options().c_str());
368+
#endif
369+
231370
auto i = FindIf(barcodeFormatZXing2Zint, [zxing = format()](auto& v) { return v.zxing == zxing; });
232371
if (i == std::end(barcodeFormatZXing2Zint))
233372
throw std::invalid_argument("unsupported barcode format: " + ToString(format()));
234-
zint->symbology = i->zint;
373+
374+
if (format() == BarcodeFormat::Code128 && gs1())
375+
zint->symbology = BARCODE_GS1_128;
376+
else if (format() == BarcodeFormat::DataBar && stacked())
377+
zint->symbology = BARCODE_DBAR_OMNSTK;
378+
else if (format() == BarcodeFormat::DataBarExpanded && stacked())
379+
zint->symbology = BARCODE_DBAR_EXPSTK;
380+
else
381+
zint->symbology = i->zint;
235382

236383
zint->scale = 0.5f;
237384

@@ -250,8 +397,8 @@ Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions
250397
{
251398
auto zint = opts.zint();
252399

253-
zint->input_mode = mode;
254-
zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_QUIET_ZONES;
400+
zint->input_mode = mode == UNICODE_MODE && opts.gs1() && SupportsGS1(opts.format()) ? GS1_MODE | GS1PARENS_MODE : mode;
401+
zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_QUIET_ZONES | BARCODE_RAW_TEXT;
255402

256403
if (mode == DATA_MODE && ZBarcode_Cap(zint->symbology, ZINT_CAP_ECI))
257404
zint->eci = static_cast<int>(ECI::Binary);
@@ -262,16 +409,49 @@ Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions
262409
printf("create symbol with size: %dx%d\n", zint->width, zint->rows);
263410
#endif
264411

265-
#ifdef ZXING_READERS
412+
#if 0 // use ReadBarcode to create Barcode object
266413
auto buffer = std::vector<uint8_t>(zint->bitmap_width * zint->bitmap_height);
267414
std::transform(zint->bitmap, zint->bitmap + zint->bitmap_width * zint->bitmap_height, buffer.data(),
268415
[](unsigned char v) { return (v == '0') * 0xff; });
269416

270417
auto res = ReadBarcode({buffer.data(), zint->bitmap_width, zint->bitmap_height, ImageFormat::Lum},
271418
ReaderOptions().setFormats(opts.format()).setIsPure(true).setBinarizer(Binarizer::BoolCast));
272419
#else
273-
//TODO: replace by proper construction from encoded data from within zint
274-
auto res = Barcode(std::string((const char*)data, size), 0, 0, 0, opts.format(), {});
420+
Content content;
421+
422+
#ifdef ZXING_READERS
423+
for (int i = 0; i < zint->raw_seg_count; ++i) {
424+
const auto& raw_seg = zint->raw_segs[i];
425+
#ifdef PRINT_DEBUG
426+
printf(" seg %d of %d with eci %d: %s\n", i, zint->raw_seg_count, raw_seg.eci, (char*)raw_seg.source);
427+
#endif
428+
if (ECI(raw_seg.eci) != ECI::ISO8859_1)
429+
content.switchEncoding(ECI(raw_seg.eci));
430+
else
431+
content.switchEncoding(CharacterSet::ISO8859_1); // set this as default to prevent guessing without setting "hasECI"
432+
content.append({raw_seg.source, static_cast<size_t>(raw_seg.length - (opts.format() == BarcodeFormat::Code93 ? 2 : 0))});
433+
}
434+
if (opts.format() == BarcodeFormat::UPCE)
435+
content.bytes = ByteArray("0" + OneD::UPCEANCommon::ConvertUPCEtoUPCA(std::string(content.bytes.asString())));
436+
else if (opts.format() == BarcodeFormat::UPCA)
437+
content.bytes = ByteArray("0" + std::string(content.bytes.asString()));
438+
#else
439+
if (zint->text_length) {
440+
content.switchEncoding(ECI::UTF8);
441+
content.append({zint->text, static_cast<size_t>(zint->text_length)});
442+
} else {
443+
content.switchEncoding(mode == DATA_MODE ? ECI::Binary : ECI::UTF8);
444+
content.append({static_cast<const uint8_t*>(data), static_cast<size_t>(size)});
445+
}
446+
#endif
447+
448+
content.symbology = SymbologyIdentifierZint2ZXing(opts, content.bytes);
449+
450+
DecoderResult decRes(std::move(content));
451+
decRes.setEcLevel(ECLevelZint2ZXing(zint));
452+
DetectorResult detRes;
453+
454+
auto res = Barcode(std::move(decRes), std::move(detRes), opts.format());
275455
#endif
276456

277457
auto bits = BitMatrix(zint->bitmap_width, zint->bitmap_height);

core/src/WriteBarcode.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class CreatorOptions
2626
friend Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& options);
2727

2828
public:
29-
CreatorOptions(BarcodeFormat format);
29+
CreatorOptions(BarcodeFormat format, std::string options = {});
3030

3131
~CreatorOptions();
3232
CreatorOptions(CreatorOptions&&);
@@ -35,16 +35,24 @@ class CreatorOptions
3535
zint_symbol* zint() const;
3636

3737
#define ZX_PROPERTY(TYPE, NAME) \
38-
TYPE NAME() const noexcept; \
38+
const TYPE& NAME() const noexcept; \
3939
CreatorOptions& NAME(TYPE v)&; \
4040
CreatorOptions&& NAME(TYPE v)&&;
4141

4242
ZX_PROPERTY(BarcodeFormat, format)
4343
ZX_PROPERTY(bool, readerInit)
4444
ZX_PROPERTY(bool, forceSquareDataMatrix)
4545
ZX_PROPERTY(std::string, ecLevel)
46+
ZX_PROPERTY(std::string, options)
4647

4748
#undef ZX_PROPERTY
49+
50+
#define ZX_RO_PROPERTY(TYPE, NAME) \
51+
TYPE NAME() const noexcept;
52+
53+
ZX_RO_PROPERTY(bool, gs1);
54+
ZX_RO_PROPERTY(bool, stacked);
55+
#undef ZX_RO_PROPERTY
4856
};
4957

5058
/**

core/src/ZXAlgorithms.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ inline bool Contains(const char* str, char c) {
4646
return strchr(str, c) != nullptr;
4747
}
4848

49+
inline bool Contains(std::string_view str, std::string_view substr) {
50+
return str.find(substr) != std::string_view::npos;
51+
}
52+
4953
template <template <typename...> typename C, typename... Ts>
5054
auto FirstOrDefault(C<Ts...>&& container)
5155
{

0 commit comments

Comments
 (0)