tessl install tessl/github-zxing-cpp--zxing-cpp@2.3.0Open-source, multi-format linear/matrix barcode image processing library implemented in C++
Structured Append enables splitting large data across multiple barcode symbols. This is useful when data exceeds a single barcode's capacity or when creating distributed barcode systems.
Structured append is supported by these formats:
struct StructuredAppendInfo {
int index; // Index in sequence (-1 if not part of sequence)
int count; // Total count in sequence (-1 if not part of sequence)
std::string id; // Sequence identifier
};auto barcode = ZXing::ReadBarcode(image);
if (barcode.isPartOfSequence()) {
std::cout << "This barcode is part of a sequence\n";
int index = barcode.sequenceIndex(); // 0-based position
int size = barcode.sequenceSize(); // Total symbols in sequence
std::string id = barcode.sequenceId(); // Sequence identifier
std::cout << "Symbol " << (index + 1) << " of " << size
<< " (ID: " << id << ")\n";
if (barcode.isLastInSequence()) {
std::cout << "This is the last symbol in the sequence\n";
}
}auto barcodes = ZXing::ReadBarcodes(image);
for (const auto& bc : barcodes) {
if (bc.isPartOfSequence()) {
std::cout << ZXing::ToString(bc.format())
<< " sequence: " << (bc.sequenceIndex() + 1)
<< "/" << bc.sequenceSize()
<< " ID: " << bc.sequenceId() << "\n";
}
}Zero-based position in the sequence:
int index = barcode.sequenceIndex();
if (index == -1) {
std::cout << "Not part of a sequence\n";
} else {
std::cout << "Position: " << index << " (0-based)\n";
std::cout << "Symbol number: " << (index + 1) << " (1-based)\n";
}Total number of symbols in the complete sequence:
int size = barcode.sequenceSize();
if (size == -1) {
std::cout << "Not part of a sequence\n";
} else {
std::cout << "Total symbols: " << size << "\n";
}Unique identifier for the sequence:
std::string id = barcode.sequenceId();
if (!id.empty()) {
std::cout << "Sequence ID: " << id << "\n";
}The sequence ID helps identify which symbols belong together when multiple sequences are present in the same scan.
if (barcode.isLastInSequence()) {
std::cout << "All symbols should now be available\n";
// Trigger merge operation
}// Read all barcodes in image
auto barcodes = ZXing::ReadBarcodes(image);
// Automatically merge all structured append sequences
auto merged = ZXing::MergeStructuredAppendSequences(barcodes);
// Result contains:
// - Merged barcodes (one per complete sequence)
// - Individual non-sequence barcodes
// - Incomplete sequences as individual symbols
for (const auto& bc : merged) {
if (bc.isValid()) {
std::cout << "Content: " << bc.text() << "\n";
if (!bc.isPartOfSequence()) {
std::cout << " (merged from structured append)\n";
}
}
}// Collect symbols from a specific sequence
ZXing::Barcodes sequence;
for (const auto& bc : barcodes) {
if (bc.sequenceId() == "ABC123") {
sequence.push_back(bc);
}
}
// Merge the specific sequence
ZXing::Barcode merged = ZXing::MergeStructuredAppendSequence(sequence);
if (merged.isValid()) {
std::cout << "Complete data: " << merged.text() << "\n";
} else {
std::cerr << "Merge failed: " << merged.error().msg() << "\n";
}For capturing sequences across multiple images:
class SequenceCollector {
std::map<std::string, ZXing::Barcodes> sequences;
public:
void processFrame(const ZXing::ImageView& image) {
auto barcodes = ZXing::ReadBarcodes(image);
for (const auto& bc : barcodes) {
if (bc.isPartOfSequence()) {
std::string id = bc.sequenceId();
sequences[id].push_back(bc);
// Check if sequence is complete
if (isSequenceComplete(sequences[id])) {
auto merged = ZXing::MergeStructuredAppendSequence(
sequences[id]);
if (merged.isValid()) {
onSequenceComplete(merged);
sequences.erase(id);
}
}
} else {
// Handle non-sequence barcode
onBarcode(bc);
}
}
}
private:
bool isSequenceComplete(const ZXing::Barcodes& seq) {
if (seq.empty()) return false;
int expectedSize = seq[0].sequenceSize();
if (seq.size() != expectedSize) return false;
// Verify all indices are present
std::set<int> indices;
for (const auto& bc : seq) {
indices.insert(bc.sequenceIndex());
}
return indices.size() == expectedSize;
}
};bool isComplete(const ZXing::Barcodes& sequence) {
if (sequence.empty()) return false;
// All must be part of same sequence
std::string id = sequence[0].sequenceId();
int size = sequence[0].sequenceSize();
if (sequence.size() != size) return false;
// Check all indices present and matching
std::set<int> indices;
for (const auto& bc : sequence) {
if (bc.sequenceId() != id) return false;
if (bc.sequenceSize() != size) return false;
indices.insert(bc.sequenceIndex());
}
// All indices from 0 to size-1 must be present
for (int i = 0; i < size; ++i) {
if (indices.find(i) == indices.end()) {
return false;
}
}
return true;
}bool hasDuplicates(const ZXing::Barcodes& sequence) {
std::set<int> indices;
for (const auto& bc : sequence) {
if (!bc.isPartOfSequence()) continue;
int index = bc.sequenceIndex();
if (indices.find(index) != indices.end()) {
return true; // Duplicate index
}
indices.insert(index);
}
return false;
}std::vector<int> findMissing(const ZXing::Barcodes& sequence) {
std::vector<int> missing;
if (sequence.empty()) return missing;
int size = sequence[0].sequenceSize();
std::set<int> present;
for (const auto& bc : sequence) {
present.insert(bc.sequenceIndex());
}
for (int i = 0; i < size; ++i) {
if (present.find(i) == present.end()) {
missing.push_back(i);
}
}
return missing;
}Symbols are concatenated in index order:
// Symbol 0: "Hello "
// Symbol 1: "World"
// Merged: "Hello World"
auto merged = ZXing::MergeStructuredAppendSequence(sequence);
std::cout << merged.text() << "\n"; // "Hello World"Binary content is also concatenated:
// Symbol 0: bytes [0x01, 0x02]
// Symbol 1: bytes [0x03, 0x04]
// Merged: bytes [0x01, 0x02, 0x03, 0x04]
auto merged = ZXing::MergeStructuredAppendSequence(sequence);
const auto& bytes = merged.bytes(); // [0x01, 0x02, 0x03, 0x04]Merged barcode position is blend of all symbols:
auto merged = ZXing::MergeStructuredAppendSequence(sequence);
auto pos = merged.position();
// Position is approximate blend of all symbol positions
// Useful for general location, not exact boundariesMerged barcode retains common metadata:
auto merged = ZXing::MergeStructuredAppendSequence(sequence);
// Format is preserved
ZXing::BarcodeFormat format = merged.format();
// Error correction level (if consistent)
std::string ecLevel = merged.ecLevel();
// Symbology identifier
std::string symId = merged.symbologyIdentifier();
// Sequence information is cleared after merge
assert(!merged.isPartOfSequence());auto merged = ZXing::MergeStructuredAppendSequence(incomplete);
if (!merged.isValid()) {
std::cerr << "Merge failed: " << merged.error().msg() << "\n";
// Typically "incomplete sequence" error
}// Mixing symbols from different sequences
ZXing::Barcodes mixed;
mixed.push_back(symbolFromSequenceA);
mixed.push_back(symbolFromSequenceB);
auto merged = ZXing::MergeStructuredAppendSequence(mixed);
// Fails - different sequence IDsZXing::Barcodes empty;
auto merged = ZXing::MergeStructuredAppendSequence(empty);
// Returns invalid barcode
assert(!merged.isValid());QR Code structured append:
if (barcode.format() == ZXing::BarcodeFormat::QRCode &&
barcode.isPartOfSequence()) {
// QR structured append
std::cout << "QR sequence: " << (barcode.sequenceIndex() + 1)
<< "/" << barcode.sequenceSize() << "\n";
}PDF417 structured append:
Data Matrix structured append:
Aztec structured append:
class SequenceCollector {
struct SequenceState {
ZXing::Barcodes symbols;
std::chrono::steady_clock::time_point lastUpdate;
};
std::map<std::string, SequenceState> sequences;
void cleanup() {
auto now = std::chrono::steady_clock::now();
auto timeout = std::chrono::seconds(30);
for (auto it = sequences.begin(); it != sequences.end(); ) {
if (now - it->second.lastUpdate > timeout) {
onTimeout(it->first);
it = sequences.erase(it);
} else {
++it;
}
}
}
};void showProgress(const ZXing::Barcodes& sequence) {
if (sequence.empty()) return;
int total = sequence[0].sequenceSize();
int collected = sequence.size();
std::cout << "Progress: " << collected << "/" << total << " symbols\n";
// Show which symbols are collected
std::set<int> indices;
for (const auto& bc : sequence) {
indices.insert(bc.sequenceIndex());
}
std::cout << "Collected: ";
for (int i = 0; i < total; ++i) {
std::cout << (indices.find(i) != indices.end() ? "█" : "□");
}
std::cout << "\n";
}void addSymbol(ZXing::Barcodes& sequence, const ZXing::Barcode& symbol) {
// Check for duplicate
for (const auto& existing : sequence) {
if (existing.sequenceIndex() == symbol.sequenceIndex()) {
// Duplicate - compare quality
if (symbol.text().size() > existing.text().size() ||
symbol.error().type() < existing.error().type()) {
// Replace with better quality
existing = symbol;
}
return;
}
}
// New symbol
sequence.push_back(symbol);
}When using the experimental writing API, some libraries support creating structured append:
#ifdef ZXING_EXPERIMENTAL_API
// Note: Structured append creation API varies by format
// Check library support for specific format
// Typically:
// 1. Split data into chunks
// 2. Create each symbol with sequence metadata
// 3. Each symbol knows its position (index, total, ID)
#endif