TypeScript port of ZXing multi-format 1D/2D barcode image processing library
PDF417 is a stacked linear barcode format that can encode large amounts of data including text, numbers, and binary data. The format uses Reed-Solomon error correction to recover from damage and supports structured append for splitting data across multiple symbols.
core/pdf417/)import {
// Reader
PDF417Reader,
// Metadata
PDF417ResultMetadata,
// Decoder components
PDF417DecodedBitStreamParser,
PDF417DecoderErrorCorrection,
// Supporting types
BarcodeFormat,
DecodeHintType,
Result,
ResultMetadataType,
BinaryBitmap
} from "@zxing/library";import {
BinaryBitmap,
HybridBinarizer,
PDF417Reader,
RGBLuminanceSource,
Result,
ResultMetadataType,
PDF417ResultMetadata
} from "@zxing/library";
// Decode a PDF417 barcode from image data
const luminanceSource = new RGBLuminanceSource(
imageData, // Uint8ClampedArray
width, // number
height // number
);
const binarizer = new HybridBinarizer(luminanceSource);
const bitmap = new BinaryBitmap(binarizer);
const reader = new PDF417Reader();
try {
const result: Result = reader.decode(bitmap);
console.log("Decoded text:", result.getText());
console.log("Format:", result.getBarcodeFormat()); // BarcodeFormat.PDF_417
// Access PDF417-specific metadata
const metadata = result.getResultMetadata()?.get(
ResultMetadataType.PDF417_EXTRA_METADATA
);
if (metadata instanceof PDF417ResultMetadata) {
console.log("Segment index:", metadata.getSegmentIndex());
console.log("Segment count:", metadata.getSegmentCount());
console.log("File ID:", metadata.getFileId());
console.log("Is last segment:", metadata.isLastSegment());
// File transfer metadata (if present)
const fileName = metadata.getFileName();
if (fileName) {
console.log("File name:", fileName);
console.log("File size:", metadata.getFileSize(), "bytes");
console.log("Checksum:", metadata.getChecksum());
const timestamp = metadata.getTimestamp();
if (timestamp !== -1) {
const date = new Date(timestamp * 1000);
console.log("Timestamp:", date.toISOString());
}
}
}
} catch (error) {
console.error("PDF417 decode failed:", error);
}The PDF417 module is structured around several key components:
PDF417Reader for decoding PDF417 barcodes with single and multiple barcode supportPDF417ResultMetadata for structured append and file transfer informationThe decoding process follows these stages:
Decodes PDF417 barcodes from images, supporting both single and multiple barcode detection.
/**
* PDF417 barcode reader
* Can detect and decode single or multiple PDF417 codes in images
* Implements both Reader and MultipleBarcodeReader interfaces
*/
class PDF417Reader implements Reader, MultipleBarcodeReader {
/**
* Locates and decodes a single PDF417 code in an image
* @param image - BinaryBitmap: binary bitmap of the image to decode
* @param hints - Map<DecodeHintType, any>: optional decoding hints
* - PURE_BARCODE: boolean for pure barcode (faster, no detection needed)
* - TRY_HARDER: boolean for more thorough scanning
* - CHARACTER_SET: string for text encoding
* @returns Result: decoded text and metadata
* @throws NotFoundException if no PDF417 code is found
* @throws FormatException if PDF417 code structure is invalid
* @throws ChecksumException if error correction fails (too many errors)
*/
decode(image: BinaryBitmap, hints?: Map<DecodeHintType, any>): Result;
/**
* Locates and decodes multiple PDF417 codes in an image
* Useful for documents containing multiple PDF417 barcodes
* @param image - BinaryBitmap: binary bitmap of the image to decode
* @param hints - Map<DecodeHintType, any>: optional decoding hints
* @returns Result[]: array of results, one for each detected barcode
* @throws NotFoundException if no PDF417 codes are found
*/
decodeMultiple(
image: BinaryBitmap,
hints?: Map<DecodeHintType, any>
): Result[];
/**
* Resets the reader state (no-op for PDF417)
*/
reset(): void;
}Usage Examples:
import {
PDF417Reader,
BinaryBitmap,
HybridBinarizer,
GlobalHistogramBinarizer,
RGBLuminanceSource,
DecodeHintType,
BarcodeFormat,
Result,
ResultPoint,
ResultMetadataType
} from "@zxing/library";
// Basic single barcode decoding
const reader = new PDF417Reader();
try {
const result: Result = reader.decode(bitmap);
console.log("Decoded:", result.getText());
console.log("Format:", result.getBarcodeFormat()); // BarcodeFormat.PDF_417
} catch (error) {
console.error("PDF417 decode failed:", error);
}
// Decode multiple PDF417 barcodes in one image
try {
const results: Result[] = reader.decodeMultiple(bitmap);
console.log(`Found ${results.length} PDF417 barcodes`);
results.forEach((result, index) => {
console.log(`Barcode ${index + 1}:`, result.getText());
console.log(` Length: ${result.getText().length} characters`);
});
} catch (error) {
console.error("Multiple barcode decode failed:", error);
}
// Decode with hints
const hints = new Map<DecodeHintType, any>();
hints.set(DecodeHintType.PURE_BARCODE, true); // Pure barcode (faster)
hints.set(DecodeHintType.TRY_HARDER, true); // More thorough
hints.set(DecodeHintType.CHARACTER_SET, "UTF-8");
try {
const result: Result = reader.decode(bitmap, hints);
console.log("Decoded with hints:", result.getText());
} catch (error) {
console.error("Decode with hints failed:", error);
}
// Access result points (corners of detected barcode)
const result: Result = reader.decode(bitmap);
const points: ResultPoint[] = result.getResultPoints();
console.log(`Found ${points.length} corner points`);
points.forEach((point, i) => {
console.log(`Point ${i}: (${point.getX()}, ${point.getY()})`);
});
// PDF417 typically returns 4-8 points:
// Points 0-3: Outer boundary corners (clockwise from top-left)
// Points 4-7: Inner boundary points (if available)
if (points.length >= 4) {
// Calculate barcode dimensions
const width = ResultPoint.distance(points[0], points[1]);
const height = ResultPoint.distance(points[0], points[3]);
console.log(`Barcode dimensions: ${width.toFixed(1)} × ${height.toFixed(1)} pixels`);
}
// Check error correction level
const ecLevel = result.getResultMetadata()?.get(
ResultMetadataType.ERROR_CORRECTION_LEVEL
);
if (ecLevel !== undefined) {
console.log("Error correction level:", ecLevel); // 0-8
}
// Use GlobalHistogramBinarizer for faster processing (less accurate)
const fastSource = new RGBLuminanceSource(imageData, width, height);
const fastBinarizer = new GlobalHistogramBinarizer(fastSource);
const fastBitmap = new BinaryBitmap(fastBinarizer);
try {
const fastResult: Result = reader.decode(fastBitmap);
console.log("Fast decode:", fastResult.getText());
} catch (error) {
console.error("Fast decode failed, falling back to HybridBinarizer");
// Fall back to more accurate binarizer
const accurateBitmap = new BinaryBitmap(new HybridBinarizer(fastSource));
const accurateResult: Result = reader.decode(accurateBitmap);
}Contains metadata specific to PDF417 barcodes, including structured append information for multi-symbol messages and optional file transfer data.
/**
* Metadata for PDF417 barcode results
* Contains information about structured append sequences and file transfer metadata
* Used for multi-symbol messages and file encoding
*/
class PDF417ResultMetadata {
/**
* Get the segment index within a structured append sequence
* @returns number: zero-based segment index
*/
getSegmentIndex(): number;
/**
* Set the segment index
* @param segmentIndex - number: zero-based segment index
*/
setSegmentIndex(segmentIndex: number): void;
/**
* Get the file ID that is the same for all related PDF417 symbols
* Used to group multi-symbol messages
* @returns string: file ID string
*/
getFileId(): string;
/**
* Set the file ID
* @param fileId - string: file ID that identifies related symbols
*/
setFileId(fileId: string): void;
/**
* Check if this is the last segment in a sequence
* @returns boolean: true if this is the last segment
*/
isLastSegment(): boolean;
/**
* Set whether this is the last segment
* @param lastSegment - boolean: true if last segment
*/
setLastSegment(lastSegment: boolean): void;
/**
* Get the total number of segments in the sequence
* @returns number: segment count, or -1 if not set
*/
getSegmentCount(): number;
/**
* Set the total segment count
* @param segmentCount - number: total number of segments in sequence
*/
setSegmentCount(segmentCount: number): void;
/**
* Get the sender information from file transfer metadata
* @returns string: sender identifier or null
*/
getSender(): string;
/**
* Set the sender information
* @param sender - string: sender identifier
*/
setSender(sender: string): void;
/**
* Get the addressee from file transfer metadata
* @returns string: addressee identifier or null
*/
getAddressee(): string;
/**
* Set the addressee
* @param addressee - string: addressee identifier
*/
setAddressee(addressee: string): void;
/**
* Get the filename from file transfer metadata
* @returns string: filename
*/
getFileName(): string;
/**
* Set the filename
* @param fileName - string: name of the encoded file
*/
setFileName(fileName: string): void;
/**
* Get the file size in bytes
* @returns number: file size in bytes, or -1 if not set
*/
getFileSize(): number;
/**
* Set the file size
* @param fileSize - number: size in bytes
*/
setFileSize(fileSize: number): void;
/**
* Get the Unix epoch timestamp (seconds since 1970-01-01)
* @returns number: timestamp in seconds, or -1 if not set
*/
getTimestamp(): number;
/**
* Set the Unix epoch timestamp
* @param timestamp - number: seconds since 1970-01-01
*/
setTimestamp(timestamp: number): void;
/**
* Get the 16-bit CRC checksum using CCITT-16 algorithm
* @returns number: checksum value (0-65535), or -1 if not set
*/
getChecksum(): number;
/**
* Set the CRC checksum
* @param checksum - number: CCITT-16 checksum value
*/
setChecksum(checksum: number): void;
/**
* Get optional data in legacy format
* @returns Int32Array: optional data array
* @deprecated Use dedicated parsed fields instead
*/
getOptionalData(): Int32Array;
/**
* Set optional data in legacy format
* @param optionalData - Int32Array: optional data as int array
* @deprecated Parse and use specific fields instead
*/
setOptionalData(optionalData: Int32Array): void;
}Usage Examples:
import {
PDF417Reader,
PDF417ResultMetadata,
ResultMetadataType,
BinaryBitmap,
Result
} from "@zxing/library";
// Decode and access metadata
const reader = new PDF417Reader();
const result: Result = reader.decode(bitmap);
const metadata = result.getResultMetadata()?.get(
ResultMetadataType.PDF417_EXTRA_METADATA
);
if (metadata instanceof PDF417ResultMetadata) {
// Check for structured append (multi-symbol message)
const segmentIndex: number = metadata.getSegmentIndex();
const segmentCount: number = metadata.getSegmentCount();
const isLast: boolean = metadata.isLastSegment();
if (segmentCount > 0) {
console.log(`Segment ${segmentIndex + 1} of ${segmentCount}`);
console.log("Last segment:", isLast);
console.log("File ID:", metadata.getFileId());
}
// Access file transfer metadata (if present)
const fileName: string = metadata.getFileName();
if (fileName) {
console.log("File name:", fileName);
const fileSize: number = metadata.getFileSize();
if (fileSize !== -1) {
console.log("File size:", fileSize, "bytes");
}
const checksum: number = metadata.getChecksum();
if (checksum !== -1) {
console.log("Checksum (CCITT-16):", checksum.toString(16));
}
const timestamp: number = metadata.getTimestamp();
if (timestamp !== -1) {
const date = new Date(timestamp * 1000);
console.log("Timestamp:", date.toISOString());
console.log("Date:", date.toLocaleString());
}
}
// Access sender/receiver info (if present)
const sender: string = metadata.getSender();
const addressee: string = metadata.getAddressee();
if (sender) {
console.log("Sender:", sender);
}
if (addressee) {
console.log("Addressee:", addressee);
}
}
// Reconstruct multi-segment message
interface SegmentInfo {
index: number;
text: string;
metadata: PDF417ResultMetadata;
}
const segments = new Map<string, SegmentInfo[]>(); // Keyed by file ID
// Decode multiple barcodes and collect segments
const results: Result[] = reader.decodeMultiple(bitmap);
for (const result of results) {
const meta = result.getResultMetadata()?.get(
ResultMetadataType.PDF417_EXTRA_METADATA
);
if (meta instanceof PDF417ResultMetadata) {
const fileId: string = meta.getFileId();
const index: number = meta.getSegmentIndex();
// Initialize segment array for this file ID
if (!segments.has(fileId)) {
segments.set(fileId, []);
}
segments.get(fileId)!.push({
index,
text: result.getText(),
metadata: meta
});
}
}
// Reconstruct complete message for each file
segments.forEach((segmentList, fileId) => {
// Sort by index
segmentList.sort((a, b) => a.index - b.index);
const segmentCount = segmentList[0].metadata.getSegmentCount();
// Check if all segments received
if (segmentList.length === segmentCount) {
// Concatenate data
let completeMessage = "";
for (const segment of segmentList) {
completeMessage += segment.text;
}
console.log(`File ${fileId}: complete (${completeMessage.length} characters)`);
console.log("Complete message:", completeMessage);
} else {
console.log(`File ${fileId}: incomplete (${segmentList.length}/${segmentCount} segments)`);
}
});Parses the decoded codewords from a PDF417 barcode into text, handling various compaction modes including text, byte, and numeric encoding.
/**
* Parser for decoding PDF417 bit streams from codewords
* Handles text, byte, and numeric compaction modes
* Also handles character set ECIs and macro PDF417 control blocks
*/
class PDF417DecodedBitStreamParser {
/**
* Decode codewords into text and metadata
* @param codewords - Int32Array: array of decoded codewords from the barcode
* @param ecLevel - string: error correction level as string ("0"-"8")
* @returns DecoderResult: decoded text, byte segments, and metadata
* @throws FormatException if the bit stream format is invalid
*/
static decode(
codewords: Int32Array,
ecLevel: string
): DecoderResult;
}Compaction Modes:
PDF417 uses different compaction modes to efficiently encode different types of data:
Text Compaction (mode 900): Encodes alphanumeric text using sub-modes
Byte Compaction (modes 901, 924): Encodes raw binary data
Numeric Compaction (mode 902): Efficiently encodes sequences of digits
ECI (Extended Channel Interpretation): Character set switching
Macro PDF417: Control blocks for structured append
Mode Codeword Values:
| Codeword | Mode | Description |
|---|---|---|
| 900 | TEXT_COMPACTION_MODE_LATCH | Switch to text compaction |
| 901 | BYTE_COMPACTION_MODE_LATCH | Switch to byte compaction (1 byte per codeword) |
| 902 | NUMERIC_COMPACTION_MODE_LATCH | Switch to numeric compaction |
| 913 | MODE_SHIFT_TO_BYTE_COMPACTION_MODE | Temporary byte shift |
| 922 | MACRO_PDF417_TERMINATOR | End macro block |
| 923 | BEGIN_MACRO_PDF417_OPTIONAL_FIELD | Start macro optional field |
| 924 | BYTE_COMPACTION_MODE_LATCH_6 | Switch to byte compaction (6 bytes per 5 codewords) |
| 925 | ECI_USER_DEFINED | User-defined ECI |
| 926 | ECI_GENERAL_PURPOSE | General purpose ECI |
| 927 | ECI_CHARSET | Character set ECI |
| 928 | BEGIN_MACRO_PDF417_CONTROL_BLOCK | Start macro control block |
Usage Examples:
import {
PDF417DecodedBitStreamParser,
DecoderResult
} from "@zxing/library";
// Decode codewords (typically called internally by PDF417Reader)
const codewords = new Int32Array([
/* codeword data */
]);
const ecLevel = "5"; // Error correction level (0-8)
try {
const decoderResult: DecoderResult = PDF417DecodedBitStreamParser.decode(
codewords,
ecLevel
);
console.log("Decoded text:", decoderResult.getText());
console.log("Error correction level:", decoderResult.getECLevel());
// Access byte segments if present (binary data)
const byteSegments: Uint8Array[] | null = decoderResult.getByteSegments();
if (byteSegments && byteSegments.length > 0) {
console.log("Binary data segments:", byteSegments.length);
byteSegments.forEach((segment, index) => {
console.log(`Segment ${index}: ${segment.length} bytes`);
console.log(` First bytes: ${Array.from(segment.slice(0, 10))}`);
});
}
// Check for structured append
if (decoderResult.hasStructuredAppend()) {
const seqNum: number = decoderResult.getStructuredAppendSequenceNumber();
const parity: number = decoderResult.getStructuredAppendParity();
console.log(`Structured append: sequence ${seqNum}, parity ${parity}`);
}
// Access additional metadata
const other = decoderResult.getOther();
if (other) {
console.log("Additional metadata:", other);
}
} catch (error) {
console.error("Bit stream parsing failed:", error);
}
// Understanding compaction modes
function explainCompaction(): void {
console.log("PDF417 Compaction Modes:");
console.log();
console.log("TEXT (900):");
console.log(" Alpha: A-Z, space");
console.log(" Lower: a-z");
console.log(" Mixed: 0-9, punctuation");
console.log(" Punct: special punctuation");
console.log(" Encoding: 'HELLO' uses Alpha mode");
console.log();
console.log("BYTE (901/924):");
console.log(" 901: 1 byte per codeword");
console.log(" 924: 6 bytes per 5 codewords (more efficient)");
console.log(" Example: Binary data, images, encrypted content");
console.log();
console.log("NUMERIC (902):");
console.log(" Base-900 encoding of digit sequences");
console.log(" Example: '1234567890' encoded more efficiently");
console.log(" Groups digits: 1 codeword ≈ 2.96 digits");
console.log();
console.log("ECI (927):");
console.log(" Charset switching: UTF-8, Shift_JIS, etc.");
console.log(" ECI 26 = UTF-8");
console.log(" ECI 3 = ISO-8859-1");
}
explainCompaction();Implements Reed-Solomon error correction specific to PDF417, using modular polynomial arithmetic over GF(929) to detect and correct errors in codewords.
/**
* PDF417 error correction implementation using Reed-Solomon algorithm
* Uses Galois Field GF(929) to correct errors and erasures in received codewords
* GF(929) matches the 929 possible codeword values (0-928)
*/
class PDF417DecoderErrorCorrection {
/**
* Constructor - initializes with PDF417's Galois Field (GF 929)
*/
constructor();
/**
* Decode and correct errors in received codewords
* Modifies received array in-place with corrected values
* @param received - Int32Array: array of received codewords (data + EC), modified in-place
* @param numECCodewords - number: number of error correction codewords
* @param erasures - Int32Array: array of known erasure positions (empty if none)
* @returns number: number of errors corrected
* @throws ChecksumException if too many errors to correct
*/
decode(
received: Int32Array,
numECCodewords: number,
erasures: Int32Array
): number;
}Error Correction Details:
PDF417 uses Reed-Solomon error correction with these properties:
t errors where 2t ≤ numECCodewords2e + t ≤ numECCodewords where e=erasures, t=errorsError Correction Levels:
PDF417 supports 9 error correction levels (0-8):
| Level | EC Codewords | Can Correct Errors | Can Detect Errors |
|---|---|---|---|
| 0 | 2 | 1 | 2 |
| 1 | 4 | 2 | 4 |
| 2 | 8 | 4 | 8 |
| 3 | 16 | 8 | 16 |
| 4 | 32 | 16 | 32 |
| 5 | 64 | 32 | 64 |
| 6 | 128 | 64 | 128 |
| 7 | 256 | 128 | 256 |
| 8 | 512 | 256 | 512 |
Usage Examples:
import {
PDF417DecoderErrorCorrection,
ChecksumException
} from "@zxing/library";
// Create error correction instance
const errorCorrection = new PDF417DecoderErrorCorrection();
// Simulate received codewords with potential errors
const received = new Int32Array([
/* received codewords: data + error correction */
]);
const numECCodewords = 64; // Error correction level 5
const erasures = new Int32Array([]); // No known erasures
try {
// Attempt error correction
const errorsCorreected: number = errorCorrection.decode(
received,
numECCodewords,
erasures
);
console.log(`Corrected ${errorsCorreected} errors`);
console.log("Corrected codewords:", Array.from(received));
// Extract data portion (exclude EC codewords)
const dataCount = received.length - numECCodewords;
const dataCodewords = received.slice(0, dataCount);
console.log("Data codewords:", Array.from(dataCodewords));
} catch (e) {
if (e instanceof ChecksumException) {
console.error("Too many errors to correct");
console.error("Maximum correctable errors:", Math.floor(numECCodewords / 2));
} else {
throw e;
}
}
// Example with known erasures (damaged barcode sections)
const receivedWithDamage = new Int32Array([
/* codewords with some known damaged positions */
]);
const knownErasures = new Int32Array([3, 7, 12, 18]); // Known damaged positions
const numEC = 64; // Level 5
try {
const errorsFixed: number = errorCorrection.decode(
receivedWithDamage,
numEC,
knownErasures
);
console.log(`Fixed ${errorsFixed} errors`);
console.log(`Known erasures: ${knownErasures.length}`);
console.log(`Unknown errors: ${errorsFixed - knownErasures.length}`);
// With erasures, can correct more total errors:
// 2 * erasures + errors ≤ numECCodewords
// With 4 erasures and 64 EC: can correct up to (64 - 2*4) = 56 additional errors
} catch (e) {
console.error("Correction failed even with known erasures:", e);
}
// Error correction capacity examples
const levels = [
{ level: 0, ec: 2 },
{ level: 1, ec: 4 },
{ level: 2, ec: 8 },
{ level: 3, ec: 16 },
{ level: 4, ec: 32 },
{ level: 5, ec: 64 },
{ level: 6, ec: 128 },
{ level: 7, ec: 256 },
{ level: 8, ec: 512 }
];
console.log("PDF417 Error Correction Capacity:");
levels.forEach(({ level, ec }) => {
const maxErrors = Math.floor(ec / 2);
console.log(`Level ${level}: ${ec} EC codewords, can correct ${maxErrors} errors`);
});
// With erasures formula: 2e + t ≤ numEC
// Examples with 64 EC codewords (level 5):
console.log("\nWith 64 EC codewords:");
console.log(" 0 erasures: can correct 32 errors");
console.log(" 10 erasures: can correct 22 errors (2*10 + 22 = 44 ≤ 64)");
console.log(" 20 erasures: can correct 12 errors (2*20 + 12 = 52 ≤ 64)");
console.log(" 32 erasures: can correct 0 errors (2*32 + 0 = 64 ≤ 64)");Common constants used throughout the PDF417 implementation:
/**
* PDF417 format constants
* Defines structure and limits of PDF417 barcodes
*/
interface PDF417Constants {
/**
* Total possible codeword values in PDF417
* Codewords range from 0 to 928
*/
NUMBER_OF_CODEWORDS: 929;
/**
* Maximum codewords in a single barcode (data + error correction)
* One less than NUMBER_OF_CODEWORDS (928) because stop pattern uses value 928
*/
MAX_CODEWORDS_IN_BARCODE: 928;
/**
* Minimum number of rows in a PDF417 barcode
*/
MIN_ROWS_IN_BARCODE: 3;
/**
* Maximum number of rows in a PDF417 barcode
*/
MAX_ROWS_IN_BARCODE: 90;
/**
* Number of modules (bars/spaces) in each codeword
*/
MODULES_IN_CODEWORD: 17;
/**
* Number of modules in the stop pattern
*/
MODULES_IN_STOP_PATTERN: 18;
/**
* Number of bars in each module
*/
BARS_IN_MODULE: 8;
/**
* Mode latch values
*/
TEXT_COMPACTION_MODE_LATCH: 900;
BYTE_COMPACTION_MODE_LATCH: 901;
NUMERIC_COMPACTION_MODE_LATCH: 902;
BYTE_COMPACTION_MODE_LATCH_6: 924;
MODE_SHIFT_TO_BYTE_COMPACTION_MODE: 913;
/**
* ECI mode values
*/
ECI_USER_DEFINED: 925;
ECI_GENERAL_PURPOSE: 926;
ECI_CHARSET: 927;
/**
* Macro PDF417 control codewords
*/
BEGIN_MACRO_PDF417_CONTROL_BLOCK: 928;
BEGIN_MACRO_PDF417_OPTIONAL_FIELD: 923;
MACRO_PDF417_TERMINATOR: 922;
}Usage Examples:
// Constants are used internally but can be referenced for understanding
// Codeword value range
console.log("Valid codeword range: 0-928");
console.log("Maximum codewords per barcode: 928");
// Structure limits
console.log("Rows: minimum 3, maximum 90");
console.log("Each codeword: 17 modules (bars and spaces)");
console.log("Stop pattern: 18 modules");
// Mode switching
console.log("Switch to text mode: codeword 900");
console.log("Switch to byte mode: codeword 901");
console.log("Switch to numeric mode: codeword 902");
console.log("Advanced byte mode: codeword 924 (6 bytes per 5 codewords)");
// ECI for character sets
console.log("ECI charset: codeword 927");
console.log("Example: 927 followed by 26 = UTF-8");
console.log("Example: 927 followed by 3 = ISO-8859-1");
// Macro PDF417 for structured data
console.log("Begin macro: codeword 928");
console.log("End macro: codeword 922");Production-ready implementation for handling multi-segment PDF417 file transfers:
import {
PDF417Reader,
PDF417ResultMetadata,
BinaryBitmap,
HybridBinarizer,
RGBLuminanceSource,
ResultMetadataType,
Result
} from "@zxing/library";
interface SegmentData {
index: number;
text: string;
metadata: PDF417ResultMetadata;
}
interface FileInfo {
fileName: string;
fileSize: number;
checksum: number;
timestamp: number;
sender: string;
addressee: string;
}
class PDF417FileTransfer {
private segments = new Map<string, SegmentData[]>();
/**
* Process a PDF417 barcode and handle multi-segment reconstruction
*/
processBarcode(
imageData: Uint8ClampedArray,
width: number,
height: number
): void {
const luminanceSource = new RGBLuminanceSource(imageData, width, height);
const binarizer = new HybridBinarizer(luminanceSource);
const bitmap = new BinaryBitmap(binarizer);
const reader = new PDF417Reader();
try {
// Try to decode multiple barcodes
const results: Result[] = reader.decodeMultiple(bitmap);
for (const result of results) {
this.processResult(result);
}
} catch (e) {
// Fallback to single decode
try {
const result: Result = reader.decode(bitmap);
this.processResult(result);
} catch (err) {
console.error("Failed to decode PDF417:", err);
}
}
}
private processResult(result: Result): void {
const metadata = result.getResultMetadata()?.get(
ResultMetadataType.PDF417_EXTRA_METADATA
);
if (!(metadata instanceof PDF417ResultMetadata)) {
console.log("Single barcode (no structured append):", result.getText());
return;
}
const fileId: string = metadata.getFileId();
const segmentIndex: number = metadata.getSegmentIndex();
const segmentCount: number = metadata.getSegmentCount();
// Initialize segment array for this file ID
if (!this.segments.has(fileId)) {
this.segments.set(fileId, []);
}
const fileSegments = this.segments.get(fileId)!;
fileSegments.push({
index: segmentIndex,
text: result.getText(),
metadata: metadata,
});
console.log(`Received segment ${segmentIndex + 1}/${segmentCount} for file ${fileId}`);
// Check if all segments received
if (fileSegments.length === segmentCount) {
this.reconstructFile(fileId, fileSegments);
}
}
private reconstructFile(fileId: string, segments: SegmentData[]): void {
// Sort segments by index
segments.sort((a, b) => a.index - b.index);
// Concatenate data
let completeData = "";
for (const segment of segments) {
completeData += segment.text;
}
// Get file metadata from first segment
const metadata: PDF417ResultMetadata = segments[0].metadata;
const fileInfo: FileInfo = {
fileName: metadata.getFileName(),
fileSize: metadata.getFileSize(),
checksum: metadata.getChecksum(),
timestamp: metadata.getTimestamp(),
sender: metadata.getSender(),
addressee: metadata.getAddressee()
};
console.log("\n=== File Reconstruction Complete ===");
console.log("File ID:", fileId);
console.log("File name:", fileInfo.fileName);
console.log("File size:", fileInfo.fileSize, "bytes");
console.log("Complete data length:", completeData.length, "characters");
if (fileInfo.timestamp !== -1) {
const date = new Date(fileInfo.timestamp * 1000);
console.log("Timestamp:", date.toISOString());
}
if (fileInfo.sender) {
console.log("Sender:", fileInfo.sender);
}
if (fileInfo.addressee) {
console.log("Addressee:", fileInfo.addressee);
}
// Verify checksum if present
if (fileInfo.checksum !== -1) {
const calculatedChecksum = this.calculateCCITT16Checksum(completeData);
if (calculatedChecksum === fileInfo.checksum) {
console.log("✓ Checksum valid");
} else {
console.warn("✗ Checksum mismatch!");
console.warn(` Expected: ${fileInfo.checksum}`);
console.warn(` Calculated: ${calculatedChecksum}`);
}
}
console.log("\nComplete data:");
console.log(completeData);
console.log("===================================\n");
// Clean up
this.segments.delete(fileId);
}
/**
* Calculate CCITT-16 CRC checksum
*/
private calculateCCITT16Checksum(data: string): number {
let crc = 0xFFFF;
for (let i = 0; i < data.length; i++) {
const byte = data.charCodeAt(i);
crc ^= byte << 8;
for (let j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021;
} else {
crc = crc << 1;
}
}
}
return crc & 0xFFFF;
}
/**
* Get status of all in-progress file transfers
*/
getStatus(): Map<string, { received: number; total: number }> {
const status = new Map<string, { received: number; total: number }>();
for (const [fileId, segments] of this.segments) {
const total = segments.length > 0 ? segments[0].metadata.getSegmentCount() : 0;
status.set(fileId, {
received: segments.length,
total: total,
});
}
return status;
}
}
// Usage
const fileTransfer = new PDF417FileTransfer();
// Process multiple images containing segments
fileTransfer.processBarcode(imageData1, width1, height1);
fileTransfer.processBarcode(imageData2, width2, height2);
fileTransfer.processBarcode(imageData3, width3, height3);
// Check status
const status = fileTransfer.getStatus();
for (const [fileId, info] of status) {
console.log(`File ${fileId}: ${info.received}/${info.total} segments received`);
if (info.received === info.total) {
console.log(` ✓ Complete`);
} else {
console.log(` ⏳ Waiting for ${info.total - info.received} more segments`);
}
}import {
PDF417Reader,
NotFoundException,
FormatException,
ChecksumException,
BinaryBitmap
} from "@zxing/library";
const reader = new PDF417Reader();
// Comprehensive error handling
function safePDF417Decode(bitmap: BinaryBitmap): Result | null {
try {
const result: Result = reader.decode(bitmap);
console.log("Success:", result.getText());
return result;
} catch (e) {
if (e instanceof NotFoundException) {
console.error("No PDF417 barcode found in image");
// Image may not contain PDF417, or code is too damaged
} else if (e instanceof FormatException) {
console.error("Invalid PDF417 format");
// Code structure is malformed
} else if (e instanceof ChecksumException) {
console.error("Error correction failed - too many errors");
// Barcode has too much damage for current EC level
} else {
console.error("Unknown error:", e);
}
return null;
}
}
// Retry with different binarizers
function decodeWithFallback(
imageData: Uint8ClampedArray,
width: number,
height: number
): Result | null {
const reader = new PDF417Reader();
const source = new RGBLuminanceSource(imageData, width, height);
// Try HybridBinarizer first (more accurate)
try {
const bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
return reader.decode(bitmap1);
} catch (e) {
console.debug("HybridBinarizer failed, trying GlobalHistogramBinarizer");
}
// Fall back to GlobalHistogramBinarizer (faster)
try {
const bitmap2 = new BinaryBitmap(new GlobalHistogramBinarizer(source));
return reader.decode(bitmap2);
} catch (e) {
console.debug("GlobalHistogramBinarizer also failed");
}
return null;
}/**
* Decode hints for PDF417 reading
*/
interface PDF417DecodeHints {
/**
* PURE_BARCODE: boolean
* Indicate image contains only barcode (no extra content)
* Enables faster processing without detection
*/
[DecodeHintType.PURE_BARCODE]?: boolean;
/**
* TRY_HARDER: boolean
* Try harder to find barcode (slower but more accurate)
* Scans more rows and tries more orientations
*/
[DecodeHintType.TRY_HARDER]?: boolean;
/**
* CHARACTER_SET: string
* Character set for text decoding
* Examples: "UTF-8", "ISO-8859-1", "Windows-1252"
*/
[DecodeHintType.CHARACTER_SET]?: string;
}Usage Examples:
import {
PDF417Reader,
DecodeHintType,
BinaryBitmap
} from "@zxing/library";
const reader = new PDF417Reader();
// Pure barcode (fastest)
const pureHints = new Map<DecodeHintType, any>();
pureHints.set(DecodeHintType.PURE_BARCODE, true);
const pureResult: Result = reader.decode(pureBitmap, pureHints);
// Try harder (most accurate)
const hardHints = new Map<DecodeHintType, any>();
hardHints.set(DecodeHintType.TRY_HARDER, true);
const hardResult: Result = reader.decode(difficultBitmap, hardHints);
// Character set specification
const utf8Hints = new Map<DecodeHintType, any>();
utf8Hints.set(DecodeHintType.CHARACTER_SET, "UTF-8");
const utf8Result: Result = reader.decode(bitmap, utf8Hints);
// Combined hints
const combinedHints = new Map<DecodeHintType, any>();
combinedHints.set(DecodeHintType.TRY_HARDER, true);
combinedHints.set(DecodeHintType.CHARACTER_SET, "UTF-8");
const combinedResult: Result = reader.decode(bitmap, combinedHints);import {
PDF417Reader,
BinaryBitmap,
HybridBinarizer,
GlobalHistogramBinarizer,
RGBLuminanceSource,
Result
} from "@zxing/library";
// Use GlobalHistogramBinarizer for faster processing
// (HybridBinarizer is more accurate but slower)
const luminanceSource = new RGBLuminanceSource(imageData, width, height);
const fastBinarizer = new GlobalHistogramBinarizer(luminanceSource);
const fastBitmap = new BinaryBitmap(fastBinarizer);
const reader = new PDF417Reader();
// For real-time scanning, reuse reader instance
const videoFrames: BinaryBitmap[] = getVideoFrames();
for (const frame of videoFrames) {
try {
const result: Result = reader.decode(frame);
console.log("Decoded:", result.getText());
break; // Stop on first successful decode
} catch (e) {
// Continue to next frame
}
}
// Reset reader state if needed (though PDF417Reader.reset() is a no-op)
reader.reset();
// Batch processing optimization
function batchDecodePDF417(
images: { data: Uint8ClampedArray; width: number; height: number }[]
): Result[] {
const reader = new PDF417Reader();
const results: Result[] = [];
for (const img of images) {
try {
const source = new RGBLuminanceSource(img.data, img.width, img.height);
const bitmap = new BinaryBitmap(new HybridBinarizer(source));
const result: Result = reader.decode(bitmap);
results.push(result);
} catch (error) {
console.error("Image decode failed:", error);
// Continue with remaining images
}
}
return results;
}
// Adaptive binarization
function adaptiveDecode(
imageData: Uint8ClampedArray,
width: number,
height: number
): Result | null {
const reader = new PDF417Reader();
const source = new RGBLuminanceSource(imageData, width, height);
// Strategy 1: HybridBinarizer (most accurate)
try {
const bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
return reader.decode(bitmap1);
} catch (e1) {
console.debug("HybridBinarizer failed");
}
// Strategy 2: GlobalHistogramBinarizer (faster)
try {
const bitmap2 = new BinaryBitmap(new GlobalHistogramBinarizer(source));
return reader.decode(bitmap2);
} catch (e2) {
console.debug("GlobalHistogramBinarizer failed");
}
// Strategy 3: Inverted image
try {
const inverted = source.invert();
const bitmap3 = new BinaryBitmap(new HybridBinarizer(inverted));
return reader.decode(bitmap3);
} catch (e3) {
console.debug("Inverted image failed");
}
return null;
}import { PDF417Reader, PDF417ResultMetadata, ResultMetadataType, Result } from "@zxing/library";
class StructuredAppendHandler {
private fileSegments = new Map<string, Map<number, string>>();
processResult(result: Result): void {
const meta = result.getResultMetadata()?.get(
ResultMetadataType.PDF417_EXTRA_METADATA
);
if (!(meta instanceof PDF417ResultMetadata)) {
console.log("Single segment:", result.getText());
return;
}
const fileId: string = meta.getFileId();
const index: number = meta.getSegmentIndex();
const count: number = meta.getSegmentCount();
if (!this.fileSegments.has(fileId)) {
this.fileSegments.set(fileId, new Map());
}
const segments = this.fileSegments.get(fileId)!;
segments.set(index, result.getText());
console.log(`File ${fileId}: segment ${index + 1}/${count} received`);
if (segments.size === count) {
this.reconstructMessage(fileId, count, segments);
}
}
private reconstructMessage(
fileId: string,
count: number,
segments: Map<number, string>
): void {
let message = "";
for (let i = 0; i < count; i++) {
const segment = segments.get(i);
if (segment) {
message += segment;
} else {
console.error(`Missing segment ${i} for file ${fileId}`);
return;
}
}
console.log(`\nFile ${fileId} complete:`);
console.log(message);
console.log();
// Clean up
this.fileSegments.delete(fileId);
}
getIncompleteFiles(): string[] {
return Array.from(this.fileSegments.keys());
}
}
// Usage
const handler = new StructuredAppendHandler();
// Process multiple barcode images
const results: Result[] = [
/* decoded PDF417 results */
];
results.forEach(result => {
handler.processResult(result);
});
// Check for incomplete files
const incomplete = handler.getIncompleteFiles();
if (incomplete.length > 0) {
console.log("Incomplete files:", incomplete);
}Install with Tessl CLI
npx tessl i tessl/npm-zxing--library