decodeRow method
Attempts to decode a one-dimensional barcode format given a single row of an image.
@param rowNumber row number from top of the row @param row the black/white pixel data of the row @param hints decode hints @return Result containing encoded string and start/end of barcode @throws NotFoundException if no potential barcode is found @throws ChecksumException if a potential barcode is found but does not pass its checksum @throws FormatException if a potential barcode is found but format is invalid
Implementation
@override
Result decodeRow(
int rowNumber,
BitArray row,
DecodeHint? hints,
) {
final convertFNC1 = hints?.assumeGs1 ?? false;
int symbologyModifier = 0;
final startPatternInfo = _findStartPattern(row);
final startCode = startPatternInfo[2];
final rawCodes = <int>[];
rawCodes.add(startCode);
int codeSet;
switch (startCode) {
case _codeStartA:
codeSet = _codeCodeA;
break;
case _codeStartB:
codeSet = _codeCodeB;
break;
case _codeStartC:
codeSet = _codeCodeC;
break;
default:
throw FormatsException.instance;
}
bool done = false;
bool isNextShifted = false;
final result = StringBuilder();
int lastStart = startPatternInfo[0];
int nextStart = startPatternInfo[1];
final counters = List.filled(6, 0);
int lastCode = 0;
int code = 0;
int checksumTotal = startCode;
int multiplier = 0;
bool lastCharacterWasPrintable = true;
bool upperMode = false;
bool shiftUpperMode = false;
while (!done) {
final unshift = isNextShifted;
isNextShifted = false;
// Save off last code
lastCode = code;
// Decode another code from image
code = _decodeCode(row, counters, nextStart);
rawCodes.add(code);
// Remember whether the last code was printable or not (excluding CODE_STOP)
if (code != _codeStop) {
lastCharacterWasPrintable = true;
}
// Add to checksum computation (if not CODE_STOP of course)
if (code != _codeStop) {
multiplier++;
checksumTotal += multiplier * code;
}
// Advance to where the next code will to start
lastStart = nextStart;
for (int counter in counters) {
nextStart += counter;
}
// Take care of illegal start codes
switch (code) {
case _codeStartA:
case _codeStartB:
case _codeStartC:
throw FormatsException.instance;
}
switch (codeSet) {
case _codeCodeA:
if (code < 64) {
if (shiftUpperMode == upperMode) {
result.writeCharCode(32 /* */ + code);
} else {
result.writeCharCode(32 /* */ + code + 128);
}
shiftUpperMode = false;
} else if (code < 96) {
if (shiftUpperMode == upperMode) {
result.writeCharCode(code - 64);
} else {
result.writeCharCode(code + 64);
}
shiftUpperMode = false;
} else {
// Don't let CODE_STOP, which always appears, affect whether whether we think the last
// code was printable or not.
if (code != _codeStop) {
lastCharacterWasPrintable = false;
}
switch (code) {
case _codeFnc1:
if (result.length == 0) {
// FNC1 at first or second character determines the symbology
symbologyModifier = 1;
} else if (result.length == 1) {
symbologyModifier = 2;
}
if (convertFNC1) {
if (result.length == 0) {
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
// is FNC1 then this is GS1-128. We add the symbology identifier.
result.write(']C1');
} else {
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
result.writeCharCode(29);
}
}
break;
case _codeFnc2:
symbologyModifier = 4;
break;
case _codeFnc3:
// do nothing?
break;
case _codeFnc4A:
if (!upperMode && shiftUpperMode) {
upperMode = true;
shiftUpperMode = false;
} else if (upperMode && shiftUpperMode) {
upperMode = false;
shiftUpperMode = false;
} else {
shiftUpperMode = true;
}
break;
case _codeShift:
isNextShifted = true;
codeSet = _codeCodeB;
break;
case _codeCodeB:
codeSet = _codeCodeB;
break;
case _codeCodeC:
codeSet = _codeCodeC;
break;
case _codeStop:
done = true;
break;
}
}
break;
case _codeCodeB:
if (code < 96) {
if (shiftUpperMode == upperMode) {
result.writeCharCode(32 /* */ + code);
} else {
result.writeCharCode(32 /* */ + code + 128);
}
shiftUpperMode = false;
} else {
if (code != _codeStop) {
lastCharacterWasPrintable = false;
}
switch (code) {
case _codeFnc1:
if (result.length == 0) {
// FNC1 at first or second character determines the symbology
symbologyModifier = 1;
} else if (result.length == 1) {
symbologyModifier = 2;
}
if (convertFNC1) {
if (result.length == 0) {
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
// is FNC1 then this is GS1-128. We add the symbology identifier.
result.write(']C1');
} else {
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
result.writeCharCode(29);
}
}
break;
case _codeFnc2:
symbologyModifier = 4;
break;
case _codeFnc3:
// do nothing?
break;
case _codeFnc4B:
if (!upperMode && shiftUpperMode) {
upperMode = true;
shiftUpperMode = false;
} else if (upperMode && shiftUpperMode) {
upperMode = false;
shiftUpperMode = false;
} else {
shiftUpperMode = true;
}
break;
case _codeShift:
isNextShifted = true;
codeSet = _codeCodeA;
break;
case _codeCodeA:
codeSet = _codeCodeA;
break;
case _codeCodeC:
codeSet = _codeCodeC;
break;
case _codeStop:
done = true;
break;
}
}
break;
case _codeCodeC:
if (code < 100) {
if (code < 10) {
result.write('0');
}
// This is not char
result.write(code);
} else {
if (code != _codeStop) {
lastCharacterWasPrintable = false;
}
switch (code) {
case _codeFnc1:
if (result.length == 0) {
// FNC1 at first or second character determines the symbology
symbologyModifier = 1;
} else if (result.length == 1) {
symbologyModifier = 2;
}
if (convertFNC1) {
if (result.length == 0) {
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
// is FNC1 then this is GS1-128. We add the symbology identifier.
result.write(']C1');
} else {
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
result.writeCharCode(29);
}
}
break;
case _codeCodeA:
codeSet = _codeCodeA;
break;
case _codeCodeB:
codeSet = _codeCodeB;
break;
case _codeStop:
done = true;
break;
}
}
break;
}
// Unshift back to another code set if we were shifted
if (unshift) {
codeSet = (codeSet == _codeCodeA) ? _codeCodeB : _codeCodeA;
}
}
final lastPatternSize = nextStart - lastStart;
// Check for ample whitespace following pattern, but, to do this we first need to remember that
// we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left
// to read off. Would be slightly better to properly read. Here we just skip it:
nextStart = row.getNextUnset(nextStart);
if (!row.isRange(
nextStart,
math.min(row.size, nextStart + (nextStart - lastStart) ~/ 2),
false,
)) {
throw NotFoundException.instance;
}
// Pull out from sum the value of the penultimate check code
checksumTotal -= multiplier * lastCode;
// lastCode is the checksum then:
if (checksumTotal % 103 != lastCode) {
throw ChecksumException.getChecksumInstance();
}
// Need to pull out the check digits from string
final resultLength = result.length;
if (resultLength == 0) {
// false positive
throw NotFoundException.instance;
}
// Only bother if the result had at least one character, and if the checksum digit happened to
// be a printable character. If it was just interpreted as a control code, nothing to remove.
if (resultLength > 0 && lastCharacterWasPrintable) {
if (codeSet == _codeCodeC) {
result.delete(resultLength - 2, resultLength);
} else {
result.delete(resultLength - 1, resultLength);
}
}
final left = (startPatternInfo[1] + startPatternInfo[0]) / 2.0;
final right = lastStart + lastPatternSize / 2.0;
final rawCodesSize = rawCodes.length;
final rawBytes = Uint8List(rawCodesSize);
for (int i = 0; i < rawCodesSize; i++) {
rawBytes[i] = rawCodes[i];
}
final resultObject = Result(
result.toString(),
rawBytes,
[
ResultPoint(left, rowNumber.toDouble()),
ResultPoint(right, rowNumber.toDouble()),
],
BarcodeFormat.code128,
);
resultObject.putMetadata(
ResultMetadataType.symbologyIdentifier,
']C$symbologyModifier',
);
return resultObject;
}