<commit_before>//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
// Original author: <NAME> (AstraZeneca)
// 27th May 2014
//

#include <GraphMol/MolDraw2D/MolDraw2D.h>
#include <GraphMol/MolDraw2D/MolDraw2DDetails.h>

#include <cstdlib>
#include <limits>

#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/tuple/tuple_comparison.hpp>
#include <boost/assign/list_of.hpp>

using namespace boost;
using namespace std;

namespace RDKit {

  // ****************************************************************************
  MolDraw2D::MolDraw2D( int width, int height ) :
  width_( width ) , height_( height ) , scale_( 1.0 ) , x_trans_( 0.0 ) ,
    y_trans_( 0.0 ) , font_size_( 0.5 ), curr_width_(2), fill_polys_(true) {
  }

  // ****************************************************************************
  void MolDraw2D::drawMolecule( const ROMol &mol ,
                                const vector<int> *highlight_atoms ,
                                const map<int,DrawColour> *highlight_atom_map,
                                const std::map<int,double> *highlight_radii,
                                int confId) {
    vector<int> highlight_bonds;
    if(highlight_atoms){
      for(vector<int>::const_iterator ai=highlight_atoms->begin();
          ai!=highlight_atoms->end();++ai){
        for(vector<int>::const_iterator aj=ai+1;
            aj!=highlight_atoms->end();++aj){
          const Bond *bnd=mol.getBondBetweenAtoms(*ai,*aj);
          if(bnd) highlight_bonds.push_back(bnd->getIdx());
        }
      }
    }
    drawMolecule(mol,highlight_atoms,&highlight_bonds,highlight_atom_map,NULL,highlight_radii,confId);
  }

  void MolDraw2D::doContinuousHighlighting( const ROMol &mol ,
                                            const vector<int> *highlight_atoms ,
                                            const vector<int> *highlight_bonds ,
                                            const map<int,DrawColour> *highlight_atom_map,
                                            const map<int,DrawColour> *highlight_bond_map,
                                            const std::map<int,double> *highlight_radii
                                            ) {
    int orig_lw=lineWidth();
    int tgt_lw=lineWidth()*8;
    // try to scale lw to reflect the overall scaling:
    tgt_lw = max(orig_lw*2,min(tgt_lw,(int)(scale_/25.*tgt_lw))); // the 25 here is extremely empirical
    bool orig_fp=fillPolys();
    ROMol::VERTEX_ITER this_at , end_at;
    if(highlight_bonds){
      boost::tie( this_at , end_at ) = mol.getVertices();
      while( this_at != end_at ) {
        int this_idx = mol[*this_at]->getIdx();
        ROMol::OEDGE_ITER nbr , end_nbr;
        boost::tie( nbr , end_nbr ) = mol.getAtomBonds( mol[*this_at].get() );
        while( nbr != end_nbr ) {
          const BOND_SPTR bond = mol[*nbr];
          ++nbr;
          int nbr_idx = bond->getOtherAtomIdx( this_idx );
          if( nbr_idx < static_cast<int>( at_cds_.size() ) && nbr_idx > this_idx ) {
            if(std::find(highlight_bonds->begin(),highlight_bonds->end(),bond->getIdx()) != highlight_bonds->end()){
              DrawColour col=drawOptions().highlightColour;
              if(highlight_bond_map &&
                 highlight_bond_map->find(bond->getIdx())!=highlight_bond_map->end()){
                col = highlight_bond_map->find(bond->getIdx())->second;
              }
              setLineWidth(tgt_lw);
              Point2D at1_cds = at_cds_[this_idx];
              Point2D at2_cds = at_cds_[nbr_idx];
              drawLine( at1_cds , at2_cds , col , col);
            }
          }
        }
        ++this_at;
      }
    }
    if(highlight_atoms){
      boost::tie( this_at , end_at ) = mol.getVertices();
      while( this_at != end_at ) {
        int this_idx = mol[*this_at]->getIdx();
        if(std::find(highlight_atoms->begin(),highlight_atoms->end(),this_idx) != highlight_atoms->end()){
          DrawColour col=drawOptions().highlightColour;
          if(highlight_atom_map &&
             highlight_atom_map->find(this_idx)!=highlight_atom_map->end()){
            col = highlight_atom_map->find(this_idx)->second;
          }
          Point2D p1=at_cds_[this_idx];
          Point2D p2=at_cds_[this_idx];
          double radius=0.4;
          if(highlight_radii && highlight_radii->find(this_idx)!=highlight_radii->end()){
            radius = highlight_radii->find(this_idx)->second;
          }
          Point2D offset(radius,radius);
          p1 -= offset;
          p2 += offset;
          setColour(col);
          setFillPolys(true);
          setLineWidth(1);
          drawEllipse(p1,p2);
        }
        ++this_at;
      }
    }
    setLineWidth(orig_lw);
    setFillPolys(orig_fp);
  }

  void MolDraw2D::drawMolecule( const ROMol &mol ,
                                const vector<int> *highlight_atoms ,
                                const vector<int> *highlight_bonds ,
                                const map<int,DrawColour> *highlight_atom_map,
                                const map<int,DrawColour> *highlight_bond_map,
                                const std::map<int,double> *highlight_radii,
                                int confId ) {
    clearDrawing();
    extractAtomCoords( mol, confId );
    extractAtomSymbols( mol );
    calculateScale();
    setFontSize( font_size_ );

    if(drawOptions().includeAtomTags){
      tagAtoms(mol);
    }
    if(drawOptions().atomRegions.size()){
      BOOST_FOREACH(const std::vector<int> &region, drawOptions().atomRegions){
        if(region.size()>1){
          Point2D minv=at_cds_[region[0]];
          Point2D maxv=at_cds_[region[0]];
          BOOST_FOREACH(int idx,region){
            const Point2D &pt=at_cds_[idx];
            minv.x = std::min(minv.x,pt.x);
            minv.y = std::min(minv.y,pt.y);
            maxv.x = std::max(maxv.x,pt.x);
            maxv.y = std::max(maxv.y,pt.y);
          }
          Point2D center=(maxv+minv)/2;
          Point2D size=(maxv-minv);
          size *= 0.2;
          minv -= size/2;
          maxv += size/2;
          setColour(DrawColour(.8,.8,.8));
          //drawEllipse(minv,maxv);
          drawRect(minv,maxv);
        }
      }
    }
    
    if(drawOptions().continuousHighlight){
      // if we're doing continuous highlighting, start by drawing the highlights
      doContinuousHighlighting(mol,highlight_atoms,highlight_bonds,
                               highlight_atom_map,highlight_bond_map,highlight_radii);
      // at this point we shouldn't be doing any more higlighting, so blow out those variables:
      highlight_bonds=NULL;
      highlight_atoms=NULL;
    } else if(drawOptions().circleAtoms && highlight_atoms){
      ROMol::VERTEX_ITER this_at , end_at;
      boost::tie( this_at , end_at ) = mol.getVertices();
      setFillPolys(false);
      while( this_at != end_at ) {
        int this_idx = mol[*this_at]->getIdx();
        if(std::find(highlight_atoms->begin(),highlight_atoms->end(),this_idx) != highlight_atoms->end()){
          if(highlight_atom_map && highlight_atom_map->find(this_idx)!=highlight_atom_map->end()){
            setColour(highlight_atom_map->find(this_idx)->second);
          } else {
            setColour(drawOptions().highlightColour);
          }
          Point2D p1=at_cds_[this_idx];
          Point2D p2=at_cds_[this_idx];
          double radius=0.3;
          if(highlight_radii && highlight_radii->find(this_idx)!=highlight_radii->end()){
            radius = highlight_radii->find(this_idx)->second;
          }
          Point2D offset(radius,radius);
          p1 -= offset;
          p2 += offset;
          drawEllipse(p1,p2);
        }
        ++this_at;
      }
      setFillPolys(true);
    }


    ROMol::VERTEX_ITER this_at , end_at;
    boost::tie( this_at , end_at ) = mol.getVertices();
    while( this_at != end_at ) {
      int this_idx = mol[*this_at]->getIdx();
      ROMol::OEDGE_ITER nbr , end_nbr;
      boost::tie( nbr , end_nbr ) = mol.getAtomBonds( mol[*this_at].get() );
      while( nbr != end_nbr ) {
        const BOND_SPTR bond = mol[*nbr];
        ++nbr;
        int nbr_idx = bond->getOtherAtomIdx( this_idx );
        if( nbr_idx < static_cast<int>( at_cds_.size() ) && nbr_idx > this_idx ) {
          drawBond( mol , bond , this_idx , nbr_idx , highlight_atoms ,
                    highlight_atom_map, highlight_bonds, highlight_bond_map );
        }
      }
      ++this_at;
    }

    if(drawOptions().dummiesAreAttachments){
      ROMol::VERTEX_ITER atom , end_atom;
      boost::tie( atom , end_atom ) = mol.getVertices();
      while( atom != end_atom ) {
        const Atom *at1 = mol[*atom].get();
        ++atom;
        if(drawOptions().atomLabels.find(at1->getIdx()) != drawOptions().atomLabels.end() ){
          // skip dummies that explicitly have a label provided
          continue;
        }
        if(at1->getAtomicNum()==0 && at1->getDegree()==1){
          Point2D &at1_cds = at_cds_[at1->getIdx()];
          ROMol::ADJ_ITER nbrIdx,endNbrs;
          boost::tie(nbrIdx,endNbrs) = mol.getAtomNeighbors(at1);
          const ATOM_SPTR at2 = mol[*nbrIdx];
          Point2D &at2_cds = at_cds_[at2->getIdx()];
          drawAttachmentLine(at2_cds,at1_cds,DrawColour(.5,.5,.5));
        }
      }
    }
    
    for( int i = 0 , is = atom_syms_.size() ; i < is ; ++i ) {
      if( !atom_syms_[i].first.empty() ) {
        drawAtomLabel( i , highlight_atoms , highlight_atom_map );
      }
    }

    if(drawOptions().flagCloseContactsDist>=0){
      highlightCloseContacts();
    }
  }

  void MolDraw2D::highlightCloseContacts(){
    if(drawOptions().flagCloseContactsDist<0) return;
    int tol=drawOptions().flagCloseContactsDist * drawOptions().flagCloseContactsDist;
    boost::dynamic_bitset<> flagged(at_cds_.size());
    for(unsigned int i=0;i<at_cds_.size();++i){
      if(flagged[i]) continue;
      Point2D ci=getDrawCoords(at_cds_[i]);
      for(unsigned int j=i+1;j<at_cds_.size();++j){
        if(flagged[j]) continue;
        Point2D cj=getDrawCoords(at_cds_[j]);
        double d=(cj-ci).lengthSq();
        if( d<= tol ){
          flagged.set(i);
          flagged.set(j);
          break;
        }
      }
      if(flagged[i]){
        Point2D p1=at_cds_[i];
        Point2D p2=p1;
        Point2D offset(0.1,0.1);
        p1 -= offset;
        p2 += offset;
        bool ofp=fillPolys();
        setFillPolys(false);
        DrawColour odc=colour();
        setColour(DrawColour(1,0,0));
        drawRect(p1,p2);
        setColour(odc);
        setFillPolys(ofp);
      }
    }
  }
  
  // ****************************************************************************
  // transform a set of coords in the molecule's coordinate system
  // to drawing system coordinates
  Point2D MolDraw2D::getDrawCoords( const Point2D &mol_cds ) const {
    double x = scale_ * ( mol_cds.x - x_min_ + x_trans_ );
    double y = scale_ * ( mol_cds.y - y_min_ + y_trans_ );

    return Point2D( x , y );
  }

  // ****************************************************************************
  Point2D MolDraw2D::getDrawCoords( int at_num ) const {
    return getDrawCoords( at_cds_[at_num] );
  }

  // ****************************************************************************
  Point2D MolDraw2D::getAtomCoords( const pair<int, int> &screen_cds) const {
    int x = int( double( screen_cds.first ) / scale_ + x_min_ - x_trans_ );
    int y = int( double( screen_cds.second ) / scale_ + y_min_ - y_trans_ );

    return Point2D( x , y );
  }

  // ****************************************************************************
  Point2D MolDraw2D::getAtomCoords( int at_num ) const {
    return at_cds_[at_num];
  }

  // ****************************************************************************
  void MolDraw2D::setFontSize( double new_size ) {
    font_size_ = new_size;
  }

  // ****************************************************************************
  void MolDraw2D::calculateScale() {
    x_min_ = y_min_ = numeric_limits<double>::max();
    double x_max( -numeric_limits<double>::max() ) , y_max( -numeric_limits<double>::max() );
    for( int i = 0 , is = at_cds_.size() ; i < is ; ++i ) {
      const Point2D &pt = at_cds_[i];
      x_min_ = std::min( pt.x , x_min_ );
      y_min_ = std::min( pt.y , y_min_ );
      x_max = std::max( pt.x , x_max );
      y_max = std::max( pt.y , y_max );
    }

    x_range_ = x_max - x_min_;
    y_range_ = y_max - y_min_;
    scale_ = std::min( double( width_ ) / x_range_ , double( height_ ) / y_range_ );

    // we may need to adjust the scale if there are atom symbols that go off
    // the edges, and we probably need to do it iteratively because get_string_size
    // uses the current value of scale_.
    while( 1 ) {
      for( int i = 0 , is = atom_syms_.size() ; i < is ; ++i ) {
        if( !atom_syms_[i].first.empty() ) {
          double atsym_width , atsym_height;
          getStringSize( atom_syms_[i].first , atsym_width , atsym_height );
          double this_x_min = at_cds_[i].x;
          double this_x_max = at_cds_[i].x;
          double this_y = at_cds_[i].y - atsym_height/2;
          if( W == atom_syms_[i].second ) {
            this_x_min -= atsym_width;
          } else if( E == atom_syms_[i].second ) {
            this_x_max += atsym_width;
          } else {
            this_x_max += atsym_width/2;
            this_x_min -= atsym_width/2;
          }
          x_max = std::max( x_max , this_x_max );
          x_min_ = std::min( x_min_ , this_x_min );
          y_max = std::max( y_max , this_y );
        }
      }
      double old_scale = scale_;
      x_range_ = x_max - x_min_;
      y_range_ = y_max - y_min_;
      scale_ = std::min( double( width_ ) / x_range_ , double( height_ ) / y_range_ );
      if( fabs( scale_ - old_scale ) < 0.1 ) {
        break;
      }
    }

    // put a 5% buffer round the drawing and calculate a final scale
    x_min_ -= 0.05 * x_range_;
    x_range_ *= 1.1;
    y_min_ -= 0.05 * y_range_;
    y_range_ *= 1.1;

    scale_ = std::min( double( width_ ) / x_range_ , double( height_ ) / y_range_ );

    double x_mid = x_min_ + 0.5 * x_range_;
    double y_mid = y_min_ + 0.5 * y_range_;
    Point2D mid = getDrawCoords( Point2D( x_mid , y_mid ) );
    x_trans_ = ( width_ / 2 - mid.x ) / scale_;
    y_trans_ = ( height_ / 2 - mid.y ) / scale_;
  }

  // ****************************************************************************
  // establishes whether to put string draw mode into super- or sub-script
  // mode based on contents of instring from i onwards. Increments i appropriately
  // and returns true or false depending on whether it did something or not.
  bool MolDraw2D::setStringDrawMode( const string &instring , int &draw_mode ,
                                     int &i ) const {

    string bit1 = instring.substr( i , 5 );
    string bit2 = instring.substr( i , 6 );

    // could be markup for super- or sub-script
    if( string( "<sub>" ) == bit1 ) {
      draw_mode = 2;
      i += 4;
      return true;
    } else if( string( "<sup>" ) == bit1 ) {
      draw_mode = 1;
      i += 4;
      return true;
    } else if( string( "</sub>") == bit2 ) {
      draw_mode = 0;
      i += 5;
      return true;
    } else if( string( "</sup>") == bit2 ) {
      draw_mode = 0;
      i += 5;
      return true;
    }

    return false;
  }

  // ****************************************************************************
  void MolDraw2D::drawLine( const Point2D &cds1 ,
                            const Point2D &cds2 ,
                            const DrawColour &col1 , const DrawColour &col2) {

    if( col1 == col2 ) {
      setColour( col1 );
      drawLine( cds1 , cds2 );
    } else {
      Point2D mid=(cds1+cds2);
      mid *= .5;

      setColour( col1 );
      drawLine( cds1 , mid );
      setColour( col2 );
      drawLine( mid , cds2 );
    }
  }

  // ****************************************************************************
  // draws the string centred on cds
  void MolDraw2D::drawString( const string &str ,
                              const Point2D &cds ) {

    double string_width , string_height;
    getStringSize( str , string_width , string_height );

    double draw_x = cds.x - string_width / 2.0;
    double draw_y = cds.y - string_height / 2.0;

    double full_font_size = fontSize();
    int draw_mode = 0; // 0 for normal, 1 for superscript, 2 for subscript
    string next_char( " " );

    for( int i = 0 , is = str.length() ; i < is ; ++i ) {

      // setStringDrawMode moves i along to the end of any <sub> or <sup>
      // markup
      if( '<' == str[i] && setStringDrawMode( str , draw_mode , i ) ) {
        continue;
      }

      char next_c = str[i];
      next_char[0] = next_c;
      double char_width , char_height;
      getStringSize( next_char , char_width , char_height );

      // these font sizes and positions work best for Qt, IMO. They may want
      // tweaking for a more general solution.
      if( 2 == draw_mode ) {
        // y goes from top to bottom, so add for a subscript!
        setFontSize( 0.75 * full_font_size );
        char_width *= 0.5;
        drawChar( next_c , getDrawCoords( Point2D( draw_x , draw_y + 0.5 * char_height ) ) );
        setFontSize( full_font_size );
      } else if( 1 == draw_mode ) {
        setFontSize( 0.75 * full_font_size );
        char_width *= 0.5;
        drawChar( next_c , getDrawCoords( Point2D( draw_x , draw_y - 0.25 * char_height ) ) );
        setFontSize( full_font_size );
      } else {
        drawChar( next_c , getDrawCoords( Point2D( draw_x , draw_y ) ) );
      }
      draw_x += char_width;
    }
  }

  // ****************************************************************************
  DrawColour MolDraw2D::getColour( int atom_idx ,
                                   const std::vector<int> *highlight_atoms ,
                                   const std::map<int,DrawColour> *highlight_map ) {

    PRECONDITION(atom_idx>=0,"bad atom_idx");
    PRECONDITION(atomic_nums_.size()>atom_idx,"bad atom_idx");
    DrawColour retval = getColourByAtomicNum( atomic_nums_[atom_idx] );

    // set contents of highlight_atoms to red
    if( highlight_atoms && highlight_atoms->end() != find( highlight_atoms->begin() ,
                                                           highlight_atoms->end() , atom_idx )  ) {
      retval = drawOptions().highlightColour;
    }
    // over-ride with explicit colour from highlight_map if there is one
    if(highlight_map){
      map<int,DrawColour>::const_iterator p = highlight_map->find( atom_idx );
      if( p != highlight_map->end() ) {
        retval = p->second;<commit_msg>MolDraw2D: Fix in highlighting atoms<commit_after>//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
// Original author: <NAME> (AstraZeneca)
// 27th May 2014
//

#include <GraphMol/MolDraw2D/MolDraw2D.h>
#include <GraphMol/MolDraw2D/MolDraw2DDetails.h>

#include <cstdlib>
#include <limits>

#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/tuple/tuple_comparison.hpp>
#include <boost/assign/list_of.hpp>

using namespace boost;
using namespace std;

namespace RDKit {

  // ****************************************************************************
  MolDraw2D::MolDraw2D( int width, int height ) :
  width_( width ) , height_( height ) , scale_( 1.0 ) , x_trans_( 0.0 ) ,
    y_trans_( 0.0 ) , font_size_( 0.5 ), curr_width_(2), fill_polys_(true) {
  }

  // ****************************************************************************
  void MolDraw2D::drawMolecule( const ROMol &mol ,
                                const vector<int> *highlight_atoms ,
                                const map<int,DrawColour> *highlight_atom_map,
                                const std::map<int,double> *highlight_radii,
                                int confId) {
    vector<int> highlight_bonds;
    if(highlight_atoms){
      for(vector<int>::const_iterator ai=highlight_atoms->begin();
          ai!=highlight_atoms->end();++ai){
        for(vector<int>::const_iterator aj=ai+1;
            aj!=highlight_atoms->end();++aj){
          const Bond *bnd=mol.getBondBetweenAtoms(*ai,*aj);
          if(bnd) highlight_bonds.push_back(bnd->getIdx());
        }
      }
    }
    drawMolecule(mol,highlight_atoms,&highlight_bonds,highlight_atom_map,NULL,highlight_radii,confId);
  }

  void MolDraw2D::doContinuousHighlighting( const ROMol &mol ,
                                            const vector<int> *highlight_atoms ,
                                            const vector<int> *highlight_bonds ,
                                            const map<int,DrawColour> *highlight_atom_map,
                                            const map<int,DrawColour> *highlight_bond_map,
                                            const std::map<int,double> *highlight_radii
                                            ) {
    int orig_lw=lineWidth();
    int tgt_lw=lineWidth()*8;
    // try to scale lw to reflect the overall scaling:
    tgt_lw = max(orig_lw*2,min(tgt_lw,(int)(scale_/25.*tgt_lw))); // the 25 here is extremely empirical
    bool orig_fp=fillPolys();
    ROMol::VERTEX_ITER this_at , end_at;
    if(highlight_bonds){
      boost::tie( this_at , end_at ) = mol.getVertices();
      while( this_at != end_at ) {
        int this_idx = mol[*this_at]->getIdx();
        ROMol::OEDGE_ITER nbr , end_nbr;
        boost::tie( nbr , end_nbr ) = mol.getAtomBonds( mol[*this_at].get() );
        while( nbr != end_nbr ) {
          const BOND_SPTR bond = mol[*nbr];
          ++nbr;
          int nbr_idx = bond->getOtherAtomIdx( this_idx );
          if( nbr_idx < static_cast<int>( at_cds_.size() ) && nbr_idx > this_idx ) {
            if(std::find(highlight_bonds->begin(),highlight_bonds->end(),bond->getIdx()) != highlight_bonds->end()){
              DrawColour col=drawOptions().highlightColour;
              if(highlight_bond_map &&
                 highlight_bond_map->find(bond->getIdx())!=highlight_bond_map->end()){
                col = highlight_bond_map->find(bond->getIdx())->second;
              }
              setLineWidth(tgt_lw);
              Point2D at1_cds = at_cds_[this_idx];
              Point2D at2_cds = at_cds_[nbr_idx];
              drawLine( at1_cds , at2_cds , col , col);
            }
          }
        }
        ++this_at;
      }
    }
    if(highlight_atoms){
      boost::tie( this_at , end_at ) = mol.getVertices();
      while( this_at != end_at ) {
        int this_idx = mol[*this_at]->getIdx();
        if(std::find(highlight_atoms->begin(),highlight_atoms->end(),this_idx) != highlight_atoms->end()){
          DrawColour col=drawOptions().highlightColour;
          if(highlight_atom_map &&
             highlight_atom_map->find(this_idx)!=highlight_atom_map->end()){
            col = highlight_atom_map->find(this_idx)->second;
          }
          Point2D p1=at_cds_[this_idx];
          Point2D p2=at_cds_[this_idx];
          double radius=0.4;
          if(highlight_radii && highlight_radii->find(this_idx)!=highlight_radii->end()){
            radius = highlight_radii->find(this_idx)->second;
          }
          Point2D offset(radius,radius);
          p1 -= offset;
          p2 += offset;
          setColour(col);
          setFillPolys(true);
          setLineWidth(1);
          drawEllipse(p1,p2);
        }
        ++this_at;
      }
    }
    setLineWidth(orig_lw);
    setFillPolys(orig_fp);
  }

  void MolDraw2D::drawMolecule( const ROMol &mol ,
                                const vector<int> *highlight_atoms ,
                                const vector<int> *highlight_bonds ,
                                const map<int,DrawColour> *highlight_atom_map,
                                const map<int,DrawColour> *highlight_bond_map,
                                const std::map<int,double> *highlight_radii,
                                int confId ) {
    clearDrawing();
    extractAtomCoords( mol, confId );
    extractAtomSymbols( mol );
    calculateScale();
    setFontSize( font_size_ );

    if(drawOptions().includeAtomTags){
      tagAtoms(mol);
    }
    if(drawOptions().atomRegions.size()){
      BOOST_FOREACH(const std::vector<int> &region, drawOptions().atomRegions){
        if(region.size()>1){
          Point2D minv=at_cds_[region[0]];
          Point2D maxv=at_cds_[region[0]];
          BOOST_FOREACH(int idx,region){
            const Point2D &pt=at_cds_[idx];
            minv.x = std::min(minv.x,pt.x);
            minv.y = std::min(minv.y,pt.y);
            maxv.x = std::max(maxv.x,pt.x);
            maxv.y = std::max(maxv.y,pt.y);
          }
          Point2D center=(maxv+minv)/2;
          Point2D size=(maxv-minv);
          size *= 0.2;
          minv -= size/2;
          maxv += size/2;
          setColour(DrawColour(.8,.8,.8));
          //drawEllipse(minv,maxv);
          drawRect(minv,maxv);
        }
      }
    }
    
    if(drawOptions().continuousHighlight){
      // if we're doing continuous highlighting, start by drawing the highlights
      doContinuousHighlighting(mol,highlight_atoms,highlight_bonds,
                               highlight_atom_map,highlight_bond_map,highlight_radii);
      // at this point we shouldn't be doing any more higlighting, so blow out those variables:
      highlight_bonds=NULL;
      highlight_atoms=NULL;
    } else if(drawOptions().circleAtoms && highlight_atoms){
      ROMol::VERTEX_ITER this_at , end_at;
      boost::tie( this_at , end_at ) = mol.getVertices();
      setFillPolys(false);
      while( this_at != end_at ) {
        int this_idx = mol[*this_at]->getIdx();
        if(std::find(highlight_atoms->begin(),highlight_atoms->end(),this_idx) != highlight_atoms->end()){
          if(highlight_atom_map && highlight_atom_map->find(this_idx)!=highlight_atom_map->end()){
            setColour(highlight_atom_map->find(this_idx)->second);
          } else {
            setColour(drawOptions().highlightColour);
          }
          Point2D p1=at_cds_[this_idx];
          Point2D p2=at_cds_[this_idx];
          double radius=0.3;
          if(highlight_radii && highlight_radii->find(this_idx)!=highlight_radii->end()){
            radius = highlight_radii->find(this_idx)->second;
          }
          Point2D offset(radius,radius);
          p1 -= offset;
          p2 += offset;
          drawEllipse(p1,p2);
        }
        ++this_at;
      }
      setFillPolys(true);
    }


    ROMol::VERTEX_ITER this_at , end_at;
    boost::tie( this_at , end_at ) = mol.getVertices();
    while( this_at != end_at ) {
      int this_idx = mol[*this_at]->getIdx();
      ROMol::OEDGE_ITER nbr , end_nbr;
      boost::tie( nbr , end_nbr ) = mol.getAtomBonds( mol[*this_at].get() );
      while( nbr != end_nbr ) {
        const BOND_SPTR bond = mol[*nbr];
        ++nbr;
        int nbr_idx = bond->getOtherAtomIdx( this_idx );
        if( nbr_idx < static_cast<int>( at_cds_.size() ) && nbr_idx > this_idx ) {
          drawBond( mol , bond , this_idx , nbr_idx , highlight_atoms ,
                    highlight_atom_map, highlight_bonds, highlight_bond_map );
        }
      }
      ++this_at;
    }

    if(drawOptions().dummiesAreAttachments){
      ROMol::VERTEX_ITER atom , end_atom;
      boost::tie( atom , end_atom ) = mol.getVertices();
      while( atom != end_atom ) {
        const Atom *at1 = mol[*atom].get();
        ++atom;
        if(drawOptions().atomLabels.find(at1->getIdx()) != drawOptions().atomLabels.end() ){
          // skip dummies that explicitly have a label provided
          continue;
        }
        if(at1->getAtomicNum()==0 && at1->getDegree()==1){
          Point2D &at1_cds = at_cds_[at1->getIdx()];
          ROMol::ADJ_ITER nbrIdx,endNbrs;
          boost::tie(nbrIdx,endNbrs) = mol.getAtomNeighbors(at1);
          const ATOM_SPTR at2 = mol[*nbrIdx];
          Point2D &at2_cds = at_cds_[at2->getIdx()];
          drawAttachmentLine(at2_cds,at1_cds,DrawColour(.5,.5,.5));
        }
      }
    }
    
    for( int i = 0 , is = atom_syms_.size() ; i < is ; ++i ) {
      if( !atom_syms_[i].first.empty() ) {
        drawAtomLabel( i , highlight_atoms , highlight_atom_map );
      }
    }

    if(drawOptions().flagCloseContactsDist>=0){
      highlightCloseContacts();
    }
  }

  void MolDraw2D::highlightCloseContacts(){
    if(drawOptions().flagCloseContactsDist<0) return;
    int tol=drawOptions().flagCloseContactsDist * drawOptions().flagCloseContactsDist;
    boost::dynamic_bitset<> flagged(at_cds_.size());
    for(unsigned int i=0;i<at_cds_.size();++i){
      if(flagged[i]) continue;
      Point2D ci=getDrawCoords(at_cds_[i]);
      for(unsigned int j=i+1;j<at_cds_.size();++j){
        if(flagged[j]) continue;
        Point2D cj=getDrawCoords(at_cds_[j]);
        double d=(cj-ci).lengthSq();
        if( d<= tol ){
          flagged.set(i);
          flagged.set(j);
          break;
        }
      }
      if(flagged[i]){
        Point2D p1=at_cds_[i];
        Point2D p2=p1;
        Point2D offset(0.1,0.1);
        p1 -= offset;
        p2 += offset;
        bool ofp=fillPolys();
        setFillPolys(false);
        DrawColour odc=colour();
        setColour(DrawColour(1,0,0));
        drawRect(p1,p2);
        setColour(odc);
        setFillPolys(ofp);
      }
    }
  }
  
  // ****************************************************************************
  // transform a set of coords in the molecule's coordinate system
  // to drawing system coordinates
  Point2D MolDraw2D::getDrawCoords( const Point2D &mol_cds ) const {
    double x = scale_ * ( mol_cds.x - x_min_ + x_trans_ );
    double y = scale_ * ( mol_cds.y - y_min_ + y_trans_ );

    return Point2D( x , y );
  }

  // ****************************************************************************
  Point2D MolDraw2D::getDrawCoords( int at_num ) const {
    return getDrawCoords( at_cds_[at_num] );
  }

  // ****************************************************************************
  Point2D MolDraw2D::getAtomCoords( const pair<int, int> &screen_cds) const {
    int x = int( double( screen_cds.first ) / scale_ + x_min_ - x_trans_ );
    int y = int( double( screen_cds.second ) / scale_ + y_min_ - y_trans_ );

    return Point2D( x , y );
  }

  // ****************************************************************************
  Point2D MolDraw2D::getAtomCoords( int at_num ) const {
    return at_cds_[at_num];
  }

  // ****************************************************************************
  void MolDraw2D::setFontSize( double new_size ) {
    font_size_ = new_size;
  }

  // ****************************************************************************
  void MolDraw2D::calculateScale() {
    x_min_ = y_min_ = numeric_limits<double>::max();
    double x_max( -numeric_limits<double>::max() ) , y_max( -numeric_limits<double>::max() );
    for( int i = 0 , is = at_cds_.size() ; i < is ; ++i ) {
      const Point2D &pt = at_cds_[i];
      x_min_ = std::min( pt.x , x_min_ );
      y_min_ = std::min( pt.y , y_min_ );
      x_max = std::max( pt.x , x_max );
      y_max = std::max( pt.y , y_max );
    }

    x_range_ = x_max - x_min_;
    y_range_ = y_max - y_min_;
    scale_ = std::min( double( width_ ) / x_range_ , double( height_ ) / y_range_ );

    // we may need to adjust the scale if there are atom symbols that go off
    // the edges, and we probably need to do it iteratively because get_string_size
    // uses the current value of scale_.
    while( 1 ) {
      for( int i = 0 , is = atom_syms_.size() ; i < is ; ++i ) {
        if( !atom_syms_[i].first.empty() ) {
          double atsym_width , atsym_height;
          getStringSize( atom_syms_[i].first , atsym_width , atsym_height );
          double this_x_min = at_cds_[i].x;
          double this_x_max = at_cds_[i].x;
          double this_y = at_cds_[i].y - atsym_height/2;
          if( W == atom_syms_[i].second ) {
            this_x_min -= atsym_width;
          } else if( E == atom_syms_[i].second ) {
            this_x_max += atsym_width;
          } else {
            this_x_max += atsym_width/2;
            this_x_min -= atsym_width/2;
          }
          x_max = std::max( x_max , this_x_max );
          x_min_ = std::min( x_min_ , this_x_min );
          y_max = std::max( y_max , this_y );
        }
      }
      double old_scale = scale_;
      x_range_ = x_max - x_min_;
      y_range_ = y_max - y_min_;
      scale_ = std::min( double( width_ ) / x_range_ , double( height_ ) / y_range_ );
      if( fabs( scale_ - old_scale ) < 0.1 ) {
        break;
      }
    }

    // put a 5% buffer round the drawing and calculate a final scale
    x_min_ -= 0.05 * x_range_;
    x_range_ *= 1.1;
    y_min_ -= 0.05 * y_range_;
    y_range_ *= 1.1;

    scale_ = std::min( double( width_ ) / x_range_ , double( height_ ) / y_range_ );

    double x_mid = x_min_ + 0.5 * x_range_;
    double y_mid = y_min_ + 0.5 * y_range_;
    Point2D mid = getDrawCoords( Point2D( x_mid , y_mid ) );
    x_trans_ = ( width_ / 2 - mid.x ) / scale_;
    y_trans_ = ( height_ / 2 - mid.y ) / scale_;
  }

  // ****************************************************************************
  // establishes whether to put string draw mode into super- or sub-script
  // mode based on contents of instring from i onwards. Increments i appropriately
  // and returns true or false depending on whether it did something or not.
  bool MolDraw2D::setStringDrawMode( const string &instring , int &draw_mode ,
                                     int &i ) const {

    string bit1 = instring.substr( i , 5 );
    string bit2 = instring.substr( i , 6 );

    // could be markup for super- or sub-script
    if( string( "<sub>" ) == bit1 ) {
      draw_mode = 2;
      i += 4;
      return true;
    } else if( string( "<sup>" ) == bit1 ) {
      draw_mode = 1;
      i += 4;
      return true;
    } else if( string( "</sub>") == bit2 ) {
      draw_mode = 0;
      i += 5;
      return true;
    } else if( string( "</sup>") == bit2 ) {
      draw_mode = 0;
      i += 5;
      return true;
    }

    return false;
  }

  // ****************************************************************************
  void MolDraw2D::drawLine( const Point2D &cds1 ,
                            const Point2D &cds2 ,
                            const DrawColour &col1 , const DrawColour &col2) {

    if( col1 == col2 ) {
      setColour( col1 );
      drawLine( cds1 , cds2 );
    } else {
      Point2D mid=(cds1+cds2);
      mid *= .5;

      setColour( col1 );
      drawLine( cds1 , mid );
      setColour( col2 );
      drawLine( mid , cds2 );
    }
  }

  // ****************************************************************************
  // draws the string centred on cds
  void MolDraw2D::drawString( const string &str ,
                              const Point2D &cds ) {

    double string_width , string_height;
    getStringSize( str , string_width , string_height );

    double draw_x = cds.x - string_width / 2.0;
    double draw_y = cds.y - string_height / 2.0;

    double full_font_size = fontSize();
    int draw_mode = 0; // 0 for normal, 1 for superscript, 2 for subscript
    string next_char( " " );

    for( int i = 0 , is = str.length() ; i < is ; ++i ) {

      // setStringDrawMode moves i along to the end of any <sub> or <sup>
      // markup
      if( '<' == str[i] && setStringDrawMode( str , draw_mode , i ) ) {
        continue;
      }

      char next_c = str[i];
      next_char[0] = next_c;
      double char_width , char_height;
      getStringSize( next_char , char_width , char_height );

      // these font sizes and positions work best for Qt, IMO. They may want
      // tweaking for a more general solution.
      if( 2 == draw_mode ) {
        // y goes from top to bottom, so add for a subscript!
        setFontSize( 0.75 * full_font_size );
        char_width *= 0.5;
        drawChar( next_c , getDrawCoords( Point2D( draw_x , draw_y + 0.5 * char_height ) ) );
        setFontSize( full_font_size );
      } else if( 1 == draw_mode ) {
        setFontSize( 0.75 * full_font_size );
        char_width *= 0.5;
        drawChar( next_c , getDrawCoords( Point2D( draw_x , draw_y - 0.25 * char_height ) ) );
        setFontSize( full_font_size );
      } else {
        drawChar( next_c , getDrawCoords( Point2D( draw_x , draw_y ) ) );
      }
      draw_x += char_width;
    }
  }

  // ****************************************************************************
  DrawColour MolDraw2D::getColour( int atom_idx ,
                                   const std::vector<int> *highlight_atoms ,
                                   const std::map<int,DrawColour> *highlight_map ) {

    PRECONDITION(atom_idx>=0,"bad atom_idx");
    PRECONDITION(atomic_nums_.size()>atom_idx,"bad atom_idx");
    DrawColour retval = getColourByAtomicNum( atomic_nums_[atom_idx] );

    // set contents of highlight_atoms to red
    if(!drawOptions().circleAtoms && !drawOptions().continuousHighlight){
      if( highlight_atoms && highlight_atoms->end() != find( highlight_atoms->begin() ,
          highlight_atoms->end() , atom_idx )  ) {
        retval = drawOptions().highlightColour;
      }
      // over-ride with explicit colour from highlight_map if there is one
      if(highlight_map){
        map<int,DrawColour>::const_iterator p = highlight_map->find( atom_idx );
        if( p != highlight_map->end() ) {
          retval = p->second;
        }
{'fraction_non_alphanumeric': 0.16998205994090335, 'fraction_numerical': 0.012267834529337274, 'mean_word_length': 2.2915074678707885, 'pattern_counts': {'":': 0, '<': 111, '<?xml version=': 0, '>': 205, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 38, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1993280', 'n_tokens_mistral': 12811, 'n_tokens_neox': 12204, 'n_words': 2481}