decodeRow method

  1. @override
Result decodeRow(
  1. int rowNumber,
  2. BitArray row,
  3. DecodeHint? hints
)
override

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