or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated

docs

index.md
tile.json

tessl/github-zxing-cpp--zxing-cpp

tessl install tessl/github-zxing-cpp--zxing-cpp@2.3.0

Open-source, multi-format linear/matrix barcode image processing library implemented in C++

structured-append.mddocs/reference/

Structured Append

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.

Supported Formats

Structured append is supported by these formats:

  • QR Code - Up to 16 symbols per sequence
  • PDF417 - Multiple symbols
  • Data Matrix - Up to 16 symbols per sequence
  • Aztec - Multiple symbols

StructuredAppendInfo

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
};

Checking for Structured Append

Individual Barcode

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";
    }
}

Multiple Barcodes

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";
    }
}

Sequence Properties

Sequence Index

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";
}

Sequence Size

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";
}

Sequence ID

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.

Last Symbol Check

if (barcode.isLastInSequence()) {
    std::cout << "All symbols should now be available\n";
    // Trigger merge operation
}

Merging Sequences

Automatic Merge (All Sequences)

// 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";
        }
    }
}

Manual Merge (Specific Sequence)

// 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";
}

Multi-Frame Scanning

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;
    }
};

Sequence Validation

Checking Completeness

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;
}

Detecting Duplicates

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;
}

Finding Missing Symbols

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;
}

Merge Behavior

Content Concatenation

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

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]

Position Information

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 boundaries

Metadata

Merged 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());

Error Handling

Incomplete Sequences

auto merged = ZXing::MergeStructuredAppendSequence(incomplete);

if (!merged.isValid()) {
    std::cerr << "Merge failed: " << merged.error().msg() << "\n";
    // Typically "incomplete sequence" error
}

Mismatched Symbols

// Mixing symbols from different sequences
ZXing::Barcodes mixed;
mixed.push_back(symbolFromSequenceA);
mixed.push_back(symbolFromSequenceB);

auto merged = ZXing::MergeStructuredAppendSequence(mixed);
// Fails - different sequence IDs

Empty Input

ZXing::Barcodes empty;
auto merged = ZXing::MergeStructuredAppendSequence(empty);

// Returns invalid barcode
assert(!merged.isValid());

Format-Specific Features

QR Code

QR Code structured append:

  • Up to 16 symbols per sequence
  • Parity data for validation
  • Widely supported by readers
if (barcode.format() == ZXing::BarcodeFormat::QRCode &&
    barcode.isPartOfSequence()) {
    // QR structured append
    std::cout << "QR sequence: " << (barcode.sequenceIndex() + 1)
              << "/" << barcode.sequenceSize() << "\n";
}

PDF417

PDF417 structured append:

  • Multiple symbols
  • Commonly used for driver's licenses with photo data

Data Matrix

Data Matrix structured append:

  • Up to 16 symbols
  • Common in industrial applications

Aztec

Aztec structured append:

  • Multiple symbols
  • Less commonly used than QR

Best Practices

Timeout for Collection

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;
            }
        }
    }
};

Progress Indication

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";
}

Duplicate Handling

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);
}

Creating Structured Append Sequences

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

Related

  • Barcode Results - Accessing sequence information
  • Barcode Reading - Reading multiple symbols
  • Reader Options - Configuring multi-symbol detection
  • Errors - Handling merge errors