docs: public Result decodeRow(int rowNumber,
                            BitArray row,
                            int[] startGuardRange,
                            Map<DecodeHintType,?> hints)
        throws NotFoundException, ChecksumException, FormatException {
  
      ResultPointCallback resultPointCallback = hints == null ? null :
          (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
  
      if (resultPointCallback != null) {
        resultPointCallback.foundPossibleResultPoint(new ResultPoint(
            (startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber
        ));
      }
  
      StringBuilder result = decodeRowStringBuffer;
      result.setLength(0);
      int endStart = decodeMiddle(row, startGuardRange, result);
  
      if (resultPointCallback != null) {
        resultPointCallback.foundPossibleResultPoint(new ResultPoint(
            endStart, rowNumber
        ));
      }
  
      int[] endRange = decodeEnd(row, endStart);
  
      if (resultPointCallback != null) {
        resultPointCallback.foundPossibleResultPoint(new ResultPoint(
            (endRange[0] + endRange[1]) / 2.0f, rowNumber
        ));
      }
  
  
      // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
      // spec might want more whitespace, but in practice this is the maximum we can count on.
      int end = endRange[1];
      int quietEnd = end + (end - endRange[0]);
      if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
        throw NotFoundException.getNotFoundInstance();
      }
  
      String resultString = result.toString();
      // UPC/EAN should never be less than 8 chars anyway
      if (resultString.length() < 8) {
        throw FormatException.getFormatInstance();
      }
      if (!checkChecksum(resultString)) {
        throw ChecksumException.getChecksumInstance();
      }
  
      float left = (startGuardRange[1] + startGuardRange[0]) / 2.0f;
      float right = (endRange[1] + endRange[0]) / 2.0f;
      BarcodeFormat format = getBarcodeFormat();
      Result decodeResult = new Result(resultString,
          null, // no natural byte representation for these barcodes
          new ResultPoint[]{
              new ResultPoint(left, rowNumber),
              new ResultPoint(right, rowNumber)},
          format);
  
      int extensionLength = 0;
  
      try {
        Result extensionResult = extensionReader.decodeRow(rowNumber, row, endRange[1]);
        decodeResult.putMetadata(ResultMetadataType.UPC_EAN_EXTENSION, extensionResult.getText());
        decodeResult.putAllMetadata(extensionResult.getResultMetadata());
        decodeResult.addResultPoints(extensionResult.getResultPoints());
        extensionLength = extensionResult.getText().length();
      } catch (ReaderException re) {
        // continue
      }
  
      int[] allowedExtensions =
          hints == null ? null : (int[]) hints.get(DecodeHintType.ALLOWED_EAN_EXTENSIONS);
      if (allowedExtensions != null) {
        boolean valid = false;
        for (int length : allowedExtensions) {
          if (extensionLength == length) {
            valid = true;
            break;
          }
        }
        if (!valid) {
          throw NotFoundException.getNotFoundInstance();
        }
      }
  
      if (format == BarcodeFormat.EAN_13 || format == BarcodeFormat.UPC_A) {
        String countryID = eanManSupport.lookupCountryIdentifier(resultString);
        if (countryID != null) {
          decodeResult.putMetadata(ResultMetadataType.POSSIBLE_COUNTRY, countryID);
        }
      }
  
      return decodeResult;
    }