MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: RDKitDrawMolecules.py
    4 # Author: Manish Sud <msud@san.rr.com>
    5 #
    6 # Copyright (C) 2024 Manish Sud. All rights reserved.
    7 #
    8 # The functionality available in this script is implemented using RDKit, an
    9 # open source toolkit for cheminformatics developed by Greg Landrum.
   10 #
   11 # This file is part of MayaChemTools.
   12 #
   13 # MayaChemTools is free software; you can redistribute it and/or modify it under
   14 # the terms of the GNU Lesser General Public License as published by the Free
   15 # Software Foundation; either version 3 of the License, or (at your option) any
   16 # later version.
   17 #
   18 # MayaChemTools is distributed in the hope that it will be useful, but without
   19 # any warranty; without even the implied warranty of merchantability of fitness
   20 # for a particular purpose.  See the GNU Lesser General Public License for more
   21 # details.
   22 #
   23 # You should have received a copy of the GNU Lesser General Public License
   24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
   25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
   26 # Boston, MA, 02111-1307, USA.
   27 #
   28 
   29 from __future__ import print_function
   30 
   31 # Add local python path to the global path and import standard library modules...
   32 import os
   33 import sys;  sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
   34 import time
   35 import re
   36 
   37 # RDKit imports...
   38 try:
   39     from rdkit import rdBase
   40     from rdkit import Chem
   41     from rdkit.Chem import AllChem
   42     from rdkit.Chem import Draw
   43     from rdkit.Chem.Draw.MolDrawing import DrawingOptions
   44 except ImportError as ErrMsg:
   45     sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
   46     sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
   47     sys.exit(1)
   48 
   49 # MayaChemTools imports...
   50 try:
   51     from docopt import docopt
   52     import MiscUtil
   53     import RDKitUtil
   54 except ImportError as ErrMsg:
   55     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
   56     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
   57     sys.exit(1)
   58 
   59 ScriptName = os.path.basename(sys.argv[0])
   60 Options = {}
   61 OptionsInfo = {}
   62 
   63 def main():
   64     """Start execution of the script."""
   65     
   66     MiscUtil.PrintInfo("\n%s (RDKit v%s; MayaChemTools v%s; %s): Starting...\n" % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime()))
   67     
   68     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
   69     
   70     # Retrieve command line arguments and options...
   71     RetrieveOptions()
   72     
   73     # Process and validate command line arguments and options...
   74     ProcessOptions()
   75     
   76     # Perform actions required by the script...
   77     DrawMolecules()
   78     
   79     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   80     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   81 
   82 def DrawMolecules():
   83     """Draw molecules."""
   84     
   85     Infile = OptionsInfo["Infile"]
   86     Outfile = OptionsInfo["Outfile"]
   87     
   88     # Read molecules...
   89     MiscUtil.PrintInfo("\nReading file %s..." % Infile)
   90 
   91     ValidMols, MolCount, ValidMolCount  = RDKitUtil.ReadAndValidateMolecules(Infile, **OptionsInfo["InfileParams"])
   92     
   93     MiscUtil.PrintInfo("Total number of molecules: %d" % MolCount)
   94     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
   95     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
   96 
   97     # Compute 2D coordinates...
   98     if OptionsInfo["Compute2DCoords"]:
   99         MiscUtil.PrintInfo("\nComputing 2D coordinates...")
  100         for Mol in ValidMols:
  101             AllChem.Compute2DCoords(Mol)
  102     
  103     MiscUtil.PrintInfo("Generating image grid...")
  104     
  105     # Setup atoms lists for highlighting atoms and bonds...
  106     AtomLists = SetupAtomListsToHighlight(ValidMols)
  107     BondLists = None
  108         
  109     # Set up legends...
  110     MolNames = None
  111     if OptionsInfo["ShowMolName"]:
  112         MolNames = []
  113         MolCount = 0
  114         for Mol in ValidMols:
  115             MolCount += 1
  116             MolName = RDKitUtil.GetMolName(Mol, MolCount)
  117             MolNames.append(MolName)
  118     
  119     # Perform alignment to a common template...
  120     PerformAlignment(ValidMols)
  121 
  122     # Generate appropriate output files...
  123     if MiscUtil.CheckFileExt(Outfile, "svg"):
  124         GenerateSVGImageFile(ValidMols, MolNames, AtomLists, BondLists)
  125     elif MiscUtil.CheckFileExt(Outfile, "html htm"):
  126         GenerateHTMLTableFile(ValidMols, MolNames, AtomLists, BondLists)
  127     else:
  128         GenerateImageFile(ValidMols, MolNames, AtomLists, BondLists)
  129     
  130 def GenerateSVGImageFile(ValidMols, MolNames, AtomLists, BondLists):
  131     """Generate a SVG image file."""
  132     
  133     MolsSVGText = RDKitUtil.GetSVGForMolecules(ValidMols, OptionsInfo["NumOfMolsPerRow"], OptionsInfo["MolImageWidth"], OptionsInfo["MolImageHeight"], Legends = MolNames, AtomListsToHighlight = AtomLists, BondListsToHighlight = BondLists, BoldText =  OptionsInfo["FontBold"])
  134     
  135     MiscUtil.PrintInfo("\nGenerating SVG image file %s..." % OptionsInfo["Outfile"])
  136     
  137     OutFH = open(OptionsInfo["Outfile"], "w")
  138     OutFH.write(MolsSVGText)
  139     OutFH.close()
  140     
  141 def GenerateImageFile(ValidMols, MolNames, AtomLists, BondLists):
  142     """Generate a non SVG image file."""
  143 
  144     Outfile = OptionsInfo["Outfile"]
  145     
  146     NumOfMolsPerRow = OptionsInfo["NumOfMolsPerRow"]
  147     Width = OptionsInfo["MolImageWidth"]
  148     Height = OptionsInfo["MolImageHeight"]
  149     
  150     # Setup drawing options...
  151     UpdatedDrawingOptions = DrawingOptions()
  152     UpdatedDrawingOptions.atomLabelFontSize = int(OptionsInfo["AtomLabelFontSize"])
  153     UpdatedDrawingOptions.bondLineWidth = float(OptionsInfo["BondLineWidth"])
  154 
  155     try:
  156         MolsImage = Draw.MolsToGridImage(ValidMols, molsPerRow = NumOfMolsPerRow,  subImgSize = (Width,Height), legends = MolNames, highlightAtomLists = AtomLists, highlightBondLists = BondLists, useSVG = False, kekulize = OptionsInfo["Kekulize"],  options = UpdatedDrawingOptions)
  157     except Exception as ErrMsg:
  158         # MolsToGridImage doesn't appear to handle the following parameters in the latest version of RDKit:
  159         #      . kekulize = OptionsInfo["Kekulize"], options = UpdatedDrawingOptions
  160         MolsImage = Draw.MolsToGridImage(ValidMols, molsPerRow = NumOfMolsPerRow,  subImgSize = (Width,Height), legends = MolNames, highlightAtomLists = AtomLists, highlightBondLists = BondLists, useSVG = False, returnPNG = False)
  161     
  162     MiscUtil.PrintInfo("\nGenerating image file %s..." % Outfile)
  163     
  164     if MiscUtil.CheckFileExt(Outfile, "pdf"):
  165         if MolsImage.mode == 'RGBA':
  166             MolsImage = MolsImage.convert('RGB')
  167     
  168     MolsImage.save(Outfile)
  169     
  170 def GenerateHTMLTableFile(ValidMols, MolNames, HighlightAtomLists, HighlightBondLists):
  171     """Generate a HTML table file."""
  172 
  173     Outfile = OptionsInfo["Outfile"]
  174     
  175     Writer = open(Outfile, "w")
  176     if Writer is None:
  177         MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
  178     
  179     MiscUtil.PrintInfo("\nGenerating HTML table file %s..." % Outfile)
  180 
  181     WriteHTMLPageHeader(Writer, len(ValidMols))
  182     WriteHTMLPageTitle(Writer)
  183     
  184     WriteHTMLTableHeader(Writer)
  185     WriteHTMLTableRows(Writer, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists)
  186     WriteHTMLTableEnd(Writer)
  187     
  188     WriteHTMLPageFooter(Writer)
  189     WriteHTMLPageEnd(Writer)
  190     
  191     if Writer is not None:
  192         Writer.close()
  193 
  194 def WriteHTMLTableRows(Writer, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists):
  195     """Write out HTML table rows."""
  196 
  197     WriteTableHeaderRow(Writer, ValidMols)
  198     WriteTableDataRows(Writer, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists)
  199     WriteTableFooterRow(Writer, ValidMols)
  200 
  201 def WriteTableDataRows(Writer, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists):
  202     """Write out table data row."""
  203 
  204     Writer.write("""        <tbody>\n""")
  205 
  206     MolCount = len(ValidMols)
  207     ColCount = GetColCount(MolCount)
  208 
  209     for Index in range(0, MolCount, ColCount):
  210         Writer.write("""          <tr>\n""")
  211     
  212         if OptionsInfo["CounterCol"]:
  213             Writer.write("""            <td></td>\n""")
  214 
  215         for MolIndex in range(Index, (Index + ColCount)):
  216             SetupStructureDataDrawing(Writer, MolIndex, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists)
  217         
  218         Writer.write("""          </tr>\n""")
  219         
  220     Writer.write("""        </tbody>\n""")
  221 
  222 def SetupStructureDataDrawing(Writer, MolIndex, Mols, MolNames, HighlightAtomLists, HighlightBondLists):
  223     """Setup structure data drawing for a tabel cell."""
  224     
  225     if MolIndex >= len(Mols):
  226         Writer.write("""            <td></td>\n""")
  227         return
  228 
  229     Mol = Mols[MolIndex]
  230     MolName = None if MolNames is None else MolNames[MolIndex]
  231     HighlightAtomList = None if HighlightAtomLists is None else HighlightAtomLists[MolIndex]
  232     HighlightBondList = None if HighlightBondLists is None else HighlightBondLists[MolIndex]
  233     
  234     SVGText = RDKitUtil.GetInlineSVGForMolecule(Mol, OptionsInfo["MolImageWidth"], OptionsInfo["MolImageHeight"], Legend = MolName, AtomListToHighlight = HighlightAtomList, BondListToHighlight = HighlightBondList, BoldText = OptionsInfo["FontBold"], Base64Encoded = OptionsInfo["MolImageEncoded"])
  235 
  236     PopoverTag = GetMolPopoverTag(Mol)
  237     ImageTag = "img" if PopoverTag is None else "img %s" % PopoverTag
  238     
  239     if OptionsInfo["MolImageEncoded"]:
  240         SVGInlineImageTag = "%s src=\"data:image/svg+xml;base64,\n%s\"" % (ImageTag, SVGText)
  241     else:
  242         SVGInlineImageTag = "%s src=\"data:image/svg+xml;charset=UTF-8,\n%s\"" % (ImageTag, SVGText)
  243     
  244     Writer.write("""            <td bgcolor="white"><%s></td>\n""" % SVGInlineImageTag)
  245 
  246 def WriteTableHeaderRow(Writer, ValidMols):
  247     """Write out table header row."""
  248 
  249     if not OptionsInfo["TableHeader"]:
  250         return
  251     
  252     TableHeaderStyle = OptionsInfo["TableHeaderStyle"]
  253     if TableHeaderStyle is None:
  254         Writer.write("""      <thead>\n""")
  255         Writer.write("""        <tr>\n""")
  256     elif re.match("^(thead|table)", TableHeaderStyle):
  257         Writer.write("""      <thead class="%s">\n""" % TableHeaderStyle)
  258         Writer.write("""        <tr>\n""")
  259     else:
  260         Writer.write("""      <thead>\n""")
  261         Writer.write("""        <tr bgcolor="%s"\n""" % TableHeaderStyle)
  262 
  263     if OptionsInfo["CounterCol"]:
  264         Writer.write("""          <th></th>\n""")
  265     
  266     # Write out rest of the column headers...
  267     MolCount = len(ValidMols)
  268     ColCount = GetColCount(MolCount)
  269     for ColIndex in range(0, ColCount):
  270         ColLabel = MiscUtil.GetExcelStyleColumnLabel(ColIndex + 1)
  271         Writer.write("""          <th>%s</th>\n""" % ColLabel)
  272         
  273     Writer.write("""        </tr>\n""")
  274     Writer.write("""      </thead>\n""")
  275 
  276 def WriteTableFooterRow(Writer, ValidMols):
  277     """Write out table footer row."""
  278 
  279     if not OptionsInfo["TableFooter"]:
  280         return
  281     
  282     Writer.write("""      <tfoot>\n""")
  283     Writer.write("""        <tr>\n""")
  284 
  285     if OptionsInfo["CounterCol"]:
  286         Writer.write("""          <td></td>\n""")
  287 
  288     # Write out rest of the column headers...
  289     MolCount = len(ValidMols)
  290     ColCount = GetColCount(MolCount)
  291     for ColIndex in range(0, ColCount):
  292         ColLabel = MiscUtil.GetExcelStyleColumnLabel(ColIndex + 1)
  293         Writer.write("""          <td>%s</td>\n""" % ColLabel)
  294         
  295     Writer.write("""        </tr>\n""")
  296     Writer.write("""      </tfoot>\n""")
  297 
  298 def WriteHTMLPageHeader(Writer, MolCount):
  299     """Write out HTML page header."""
  300 
  301     ColCount = GetColCount(MolCount)
  302     
  303     # Exclude counter and structure columns from sorting and searching...
  304     if OptionsInfo["CounterCol"]:
  305         ColIndicesList = ["0"]
  306         ColVisibilityExcludeColIndicesList = ["0"]
  307         ColIndexOffset = 1
  308         FreezeLeftColumns = "1"
  309     else:
  310         ColIndicesList = []
  311         ColVisibilityExcludeColIndicesList = []
  312         ColIndexOffset = 0
  313 
  314     MaxDataColVisColCount = 25
  315     for Index in range(0, ColCount):
  316         ColIndex = Index + ColIndexOffset
  317         ColIndicesList.append("%s" % ColIndex)
  318         
  319         if OptionsInfo["ColVisibility"]:
  320             if Index >= MaxDataColVisColCount:
  321                 ColVisibilityExcludeColIndicesList.append("%s" %ColIndex)
  322     
  323     ColIndices = MiscUtil.JoinWords(ColIndicesList, ", ") if  len(ColIndicesList) else ""
  324     ColVisibilityExcludeColIndices = MiscUtil.JoinWords(ColVisibilityExcludeColIndicesList, ", ") if len(ColVisibilityExcludeColIndicesList) else ""
  325         
  326     DataColVisibilityExclude = False
  327     if OptionsInfo["ColVisibility"]:
  328         if ColCount > MaxDataColVisColCount:
  329             DataColVisibilityExclude = True
  330             MiscUtil.PrintWarning("The number of data columns, %d, is quite large. Only first %d data columns will be available in column visibility pulldown." % (ColCount, MaxDataColVisColCount))
  331             
  332     DisplayButtons = False
  333     if OptionsInfo["ColVisibility"]:
  334         if ColCount > 0:
  335             DisplayButtons = True
  336     
  337     FreezeCols = False
  338     if (OptionsInfo["CounterCol"] and OptionsInfo["ScrollX"]):
  339         FreezeCols = True
  340     
  341     Paging = "true" if OptionsInfo["Paging"] else "false"
  342     PageLength = "%d" % OptionsInfo["PageLength"]
  343     PagingType = "\"%s\"" % OptionsInfo["PagingType"]
  344 
  345     ScrollX = "true" if OptionsInfo["ScrollX"] else "false"
  346     
  347     ScrollY = ""
  348     if OptionsInfo["ScrollY"]:
  349         if re.search("vh$", OptionsInfo["ScrollYSize"]):
  350             ScrollY = "\"%s\"" % OptionsInfo["ScrollYSize"]
  351         else:
  352             ScrollY = "%s" % OptionsInfo["ScrollYSize"]
  353     
  354     # Start HTML header...
  355     Title = "Molecules table" if OptionsInfo["Header"] is None else OptionsInfo["Header"]
  356     
  357     Writer.write("""\
  358 <!doctype html>
  359 <html lang="en">
  360 <head>
  361     <title>%s</title>
  362     <meta charset="utf-8">
  363     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  364     <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
  365     <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css">
  366   
  367 """ % (Title))
  368 
  369     if (FreezeCols):
  370         Writer.write("""\
  371     <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.bootstrap4.min.css">
  372 """)
  373     
  374     if (OptionsInfo["KeysNavigation"]):
  375         Writer.write("""\
  376     <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.3.2/css/keyTable.bootstrap4.min.css">
  377 """)
  378     
  379     Writer.write("""\
  380 
  381     <script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
  382     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
  383     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
  384 
  385 """)
  386 
  387     if (OptionsInfo["Popover"]):
  388         Writer.write("""\
  389     <script type="text/javascript" language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
  390     <script type="text/javascript" language="javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
  391 
  392 """)
  393         
  394     if DisplayButtons:
  395         Writer.write("""\
  396     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
  397     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.bootstrap4.min.js"></script>
  398     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.colVis.min.js"></script>
  399 
  400 """)
  401     
  402     if (FreezeCols):
  403         Writer.write("""\
  404     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/fixedcolumns/3.2.4/js/dataTables.fixedColumns.min.js"></script>
  405 """)
  406     
  407     if (OptionsInfo["KeysNavigation"]):
  408         Writer.write("""\
  409     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/keytable/2.3.2/js/dataTables.keyTable.min.js"></script>
  410 """)
  411     
  412     # Intialize table using Bootstrap, DataTables and JQuery frameworks...
  413     Writer.write("""\
  414 
  415     <script type="text/javascript" class="init">
  416 
  417 $(document).ready(function() {
  418 """)
  419 
  420     if (OptionsInfo["Popover"]):
  421         Writer.write("""\
  422     $('.MolPopover').popover();
  423 
  424 """)
  425         
  426     Writer.write("""\
  427     var MolsTable = $('#MolsTable').DataTable( {
  428         "columnDefs": [
  429             {
  430                 "orderable": false,
  431                 "searchable": false,
  432                 "targets": [%s]
  433             },
  434 """ % (ColIndices))
  435 
  436     if OptionsInfo["ColVisibility"]:
  437         Writer.write("""\
  438             {
  439                 "className": "noColVisCtrl",
  440                 "targets": [%s]
  441             }
  442 """ % (ColVisibilityExcludeColIndices))
  443 
  444     Writer.write("""\
  445         ],
  446 """)
  447 
  448     # Set up dom for displaying button and other options...
  449     if OptionsInfo["ColVisibility"]:
  450         if OptionsInfo["Paging"]:
  451             Writer.write("""\
  452         "dom":  "<'row'<'col-sm-6'l><'col-sm-6'<'float-right'B>>>" +
  453             "<'row'<'col-sm-12'tr>>" +
  454             "<'row'<'col-sm-5'i><'col-sm-7'p>>",
  455 """)
  456         else:
  457             Writer.write("""\
  458         "dom":  "<'row'<'col'<'float-right'B>>>" +
  459             "<'row'<'col-sm-12'tr>>" +
  460             "<'row'<'col-sm-5'i><'col-sm-7'p>>",
  461 """)
  462     else:
  463             Writer.write("""\
  464         "dom":  "<'row'<'col'l>>" +
  465             "<'row'<'col-sm-12'tr>>" +
  466             "<'row'<'col-sm-5'i><'col-sm-7'p>>",
  467 """)
  468     
  469     #
  470     if OptionsInfo["ColVisibility"]:
  471         # Set up buttons...
  472         Writer.write("""\
  473         "buttons": [
  474             {
  475                 "extend": "colvis",
  476                 "text": "Column visibility",
  477                 "className": "btn btn-outline-light text-dark",
  478                 "columns": ":not(.noColVisCtrl)",
  479 """)
  480         if not DataColVisibilityExclude:
  481             Writer.write("""\
  482                 "prefixButtons": [ "colvisRestore" ],
  483 """)
  484         
  485         Writer.write("""\
  486                 "columnText": function ( dt, colIndex, colLabel ) {
  487                     return "Column " + (colIndex + 1);
  488                 },
  489             }
  490         ],
  491 """)
  492     
  493     # Write out rest of the variables for DataTables...
  494     if FreezeCols:
  495         Writer.write("""\
  496         "fixedColumns": {
  497             "leftColumns": %s
  498         },
  499 """ % (FreezeLeftColumns))
  500     
  501     if (OptionsInfo["KeysNavigation"]):
  502         Writer.write("""\
  503         "keys": true,
  504 """)
  505     
  506     Writer.write("""\
  507         "pageLength": %s,
  508         "lengthMenu": [ [5, 10, 15, 25, 50, 100, 500, 1000, -1], [5, 10, 15, 25, 50, 100, 500, 1000, "All"] ],
  509         "paging": %s,
  510         "pagingType": %s,
  511         "scrollX": %s,
  512         "scrollY": %s,
  513         "scrollCollapse": true,
  514         "order": [],
  515     } );
  516 """ % (PageLength, Paging, PagingType, ScrollX, ScrollY))
  517     
  518     if OptionsInfo["CounterCol"]:
  519         Writer.write("""\
  520     MolsTable.on( 'order.dt search.dt', function () {
  521         MolsTable.column(0, {search:'applied', order:'applied'}).nodes().each( function (cell, rowIndex) {
  522             cell.innerHTML = rowIndex + 1;
  523         } );
  524     } ).draw();
  525 """)
  526     
  527     # End of Javacscript code...
  528     Writer.write("""\
  529 } );
  530 
  531     </script>
  532 """)
  533 
  534     # Finish up HTML header...
  535     Writer.write("""\
  536   
  537 </head>
  538 <body>
  539   <div class="container-fluid">
  540     <br/>
  541 """)
  542         
  543 def WriteHTMLPageEnd(Writer):
  544     """Write out HTML page end."""
  545 
  546     Writer.write("""\
  547   </div>
  548 </body>
  549 </html>
  550 """)
  551 
  552 def WriteHTMLPageTitle(Writer):
  553     """Write out HTML page title."""
  554 
  555     if OptionsInfo["Header"] is None:
  556         return
  557 
  558     Writer.write("""    <%s class="text-center">%s</%s>\n""" % (OptionsInfo["HeaderStyle"], OptionsInfo["Header"], OptionsInfo["HeaderStyle"]))
  559 
  560 def WriteHTMLPageFooter(Writer):
  561     """Write out HTML page footer."""
  562 
  563     if OptionsInfo["Footer"] is None:
  564         return
  565 
  566     Writer.write("""    <br/>\n    <p class="%s">%s</p>\n""" % (OptionsInfo["FooterClass"], OptionsInfo["Footer"]))
  567 
  568 def WriteHTMLTableHeader(Writer):
  569     """Write out HTML table header."""
  570 
  571     if OptionsInfo["TableStyle"] is None:
  572         Writer.write("""\n    <table id="MolsTable" cellspacing="0" width="100%">\n""")
  573     else:
  574         Writer.write("""    <table id="MolsTable" class="%s" cellspacing="0" width="100%s">\n""" % (OptionsInfo["TableStyle"], "%"))
  575         
  576 def WriteHTMLTableEnd(Writer):
  577     """Write out HTML table end."""
  578 
  579     Writer.write("""    </table>\n\n""")
  580 
  581 def GetColCount(MolCount):
  582     """Get tabke column count."""
  583     
  584     ColCount = OptionsInfo["NumOfMolsPerRow"] if OptionsInfo["NumOfMolsPerRow"] <= MolCount else MolCount
  585     
  586     return ColCount
  587 
  588 def SetupAtomListsToHighlight(ValidMols):
  589     """Set up atom lists to highlight using specified SMARTS pattern."""
  590 
  591     AtomListsToHighlight = None
  592     if OptionsInfo["HighlightSMARTSPattern"] is None:
  593         return  AtomListsToHighlight
  594     
  595     PatternMol = Chem.MolFromSmarts(OptionsInfo["HighlightSMARTSPattern"])
  596     AtomListsToHighlight = []
  597     for ValidMol in ValidMols:
  598         # Get matched atom lists and flatten it...
  599         MatchedAtomsLists = ValidMol.GetSubstructMatches(PatternMol)
  600         MatchedAtoms = [ Atom for AtomsList in MatchedAtomsLists for Atom in AtomsList] 
  601         AtomListsToHighlight.append(MatchedAtoms)
  602     
  603     return  AtomListsToHighlight
  604 
  605 def PerformAlignment(ValidMols):
  606     """Perform alignment to a common template specified by a SMARTS pattern."""
  607     
  608     if OptionsInfo["AlignmentSMARTSPattern"] is None:
  609         return
  610     
  611     PatternMol = Chem.MolFromSmarts(OptionsInfo["AlignmentSMARTSPattern"])
  612     AllChem.Compute2DCoords(PatternMol)
  613         
  614     MatchedValidMols = [ValidMol for ValidMol in ValidMols if ValidMol.HasSubstructMatch(PatternMol)]
  615     for ValidMol in MatchedValidMols:
  616         AllChem.GenerateDepictionMatching2DStructure(ValidMol, PatternMol)
  617 
  618 def GetMolPopoverTag(Mol):
  619     """Set up a popover window containing any additional information about molecule."""
  620     
  621     if not OptionsInfo["Popover"]:
  622         return None
  623     
  624     # Set up data label and values...
  625     AvailableDataLabels = Mol.GetPropNames(includePrivate = False, includeComputed = False)
  626     
  627     DataContentLines = []
  628     MaxDataCharWidth = OptionsInfo["PopoverTextWidth"]
  629     MaxDataDisplayCount = OptionsInfo["PopoverDataCount"]
  630 
  631     DataDisplayCount = 0
  632     SkippedDataDisplay = False
  633     for DataLabel in AvailableDataLabels:
  634         DataDisplayCount += 1
  635         if DataDisplayCount > MaxDataDisplayCount:
  636             SkippedDataDisplay = True
  637             break
  638         
  639         DataValue = "%s" % Mol.GetProp(DataLabel)
  640         DataValue = DataValue.strip()
  641         if MiscUtil.IsEmpty(DataValue):
  642             continue
  643 
  644         # Change any new lines to ;
  645         if re.search("(\r\n|\r|\n)", DataValue):
  646             DataValue = re.sub("(\r\n|\r|\n)", "; ", DataValue)
  647         
  648         DataValue = MiscUtil.TruncateText(DataValue, MaxDataCharWidth, "...")
  649         DataValue = MiscUtil.ReplaceHTMLEntitiesInText(DataValue)
  650         
  651         DataContent = "<b>%s</b>: %s" % (DataLabel, DataValue)
  652         DataContentLines.append(DataContent)
  653 
  654     if not len(DataContentLines):
  655         return None
  656 
  657     if SkippedDataDisplay:
  658         DataContent = "<b>... ... ...</b>"
  659         DataContentLines.append(DataContent)
  660         
  661         DataContent = "Showing 1 to %s of %s" % (MaxDataDisplayCount, len(AvailableDataLabels))
  662         DataContentLines.append(DataContent)
  663     else:
  664         DataContent = "Showing 1 to %s of %s" % (DataDisplayCount, len(AvailableDataLabels))
  665         DataContentLines.append(DataContent)
  666         
  667     DataContent = MiscUtil.JoinWords(DataContentLines, "<br/>")
  668     PopoverTag = """class="MolPopover" data-toggle="popover" data-html="true" data-trigger="click" data-placement="right" title="<span class='small'><b>Additional Information</b></span>" data-content="<span class='small'>%s</span>" """ % DataContent
  669     
  670     return PopoverTag
  671 
  672 def ProcessOptions():
  673     """Process and validate command line arguments and options."""
  674     
  675     MiscUtil.PrintInfo("Processing options...")
  676     
  677     # Validate options...
  678     ValidateOptions()
  679     
  680     OptionsInfo["Infile"] = Options["--infile"]
  681     OptionsInfo["Outfile"] = Options["--outfile"]
  682     OptionsInfo["Overwrite"] = Options["--overwrite"]
  683     
  684     # No need for any RDKit specific --outfileParams....
  685     OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters("--infileParams", Options["--infileParams"], OptionsInfo["Infile"])
  686 
  687     AlignmentSMARTSPattern = None
  688     if not re.match("^None$", Options["--alignmentSMARTS"], re.I):
  689         AlignmentSMARTSPattern = Options["--alignmentSMARTS"]
  690     OptionsInfo["AlignmentSMARTSPattern"]  = AlignmentSMARTSPattern
  691     
  692     OptionsInfo["AtomLabelFontSize"] = Options["--atomLabelFontSize"]
  693     OptionsInfo["BondLineWidth"] = Options["--bondLineWidth"]
  694     
  695     Compute2DCoords = True
  696     if re.match("^yes$", Options["--compute2DCoords"], re.I):
  697         Compute2DCoords = True
  698     elif re.match("^no$", Options["--compute2DCoords"], re.I):
  699         Compute2DCoords = False
  700     OptionsInfo["Compute2DCoords"]  = Compute2DCoords
  701 
  702     CounterCol = True
  703     if re.match("^no$", Options["--counterCol"], re.I):
  704         CounterCol = False
  705     OptionsInfo["CounterCol"]  = CounterCol
  706     
  707     ColVisibility = True
  708     if re.match("^no$", Options["--colVisibility"], re.I):
  709         ColVisibility = False
  710     OptionsInfo["ColVisibility"]  = ColVisibility
  711     
  712     OptionsInfo["FontBold"] = True
  713     if re.match("^no$", Options["--fontBold"], re.I):
  714         OptionsInfo["FontBold"] = False
  715         
  716     Footer = None
  717     if not re.match("^None$", Options["--footer"], re.I):
  718         Footer = Options["--footer"]
  719     OptionsInfo["Footer"]  = Footer
  720 
  721     FooterClass = Options["--footerClass"].strip()
  722     if MiscUtil.IsEmpty(FooterClass):
  723         MiscUtil.PrintError("The value specified using option \"--footerClass\" is empty.")
  724     OptionsInfo["FooterClass"]  = FooterClass
  725     
  726     Header = None
  727     if not re.match("^None$", Options["--header"], re.I):
  728         Header = Options["--header"]
  729     OptionsInfo["Header"]  = Header
  730     
  731     HeaderStyle = Options["--headerStyle"].strip()
  732     if MiscUtil.IsEmpty(HeaderStyle):
  733         MiscUtil.PrintError("The value specified using option \"--headerStyle\" is empty.")
  734     OptionsInfo["HeaderStyle"]  = HeaderStyle
  735     
  736     HighlightSMARTSPattern = None
  737     if not re.match("^None$", Options["--highlightSMARTS"], re.I):
  738         HighlightSMARTSPattern = Options["--highlightSMARTS"]
  739     OptionsInfo["HighlightSMARTSPattern"]  = HighlightSMARTSPattern
  740     
  741     OptionsInfo["Kekulize"] = True
  742     if re.match("^no$", Options["--kekulize"], re.I):
  743         OptionsInfo["Kekulize"] = False
  744         
  745     OptionsInfo["KeysNavigation"] = True
  746     if re.match("^no$", Options["--keysNavigation"], re.I):
  747         OptionsInfo["KeysNavigation"] = False
  748     
  749     SizeValues = Options["--molImageSize"].split(",")
  750     OptionsInfo["MolImageWidth"] = int(SizeValues[0])
  751     OptionsInfo["MolImageHeight"] = int(SizeValues[1])
  752 
  753     OptionsInfo["MolImageEncoded"] = True
  754     if re.match("^no$", Options["--molImageEncoded"], re.I):
  755         OptionsInfo["MolImageEncoded"] = False
  756     
  757     OptionsInfo["NumOfMolsPerRow"] = int(Options["--numOfMolsPerRow"])
  758 
  759     OptionsInfo["Paging"] = True
  760     if re.match("^no$", Options["--paging"], re.I):
  761         OptionsInfo["Paging"] = False
  762     
  763     PagingType = Options["--pagingType"]
  764     if not re.match("^(numbers|simple|simple_numbers|full|full_numbers|simple_number)$", Options["--pagingType"], re.I):
  765         MiscUtil.PrintWarning("The paging type name, %s, specified using option \"--pagingType\" appears to be a unknown type..." % (PagingType))
  766     OptionsInfo["PagingType"] = PagingType.lower()
  767     
  768     OptionsInfo["PageLength"] = int(Options["--pageLength"])
  769     
  770     OptionsInfo["Popover"] = True
  771     if re.match("^no$", Options["--popover"], re.I):
  772         OptionsInfo["Popover"] = False
  773     OptionsInfo["PopoverDataCount"] = int(Options["--popoverDataCount"])
  774     OptionsInfo["PopoverTextWidth"] = int(Options["--popoverTextWidth"])
  775     
  776     OptionsInfo["ShowMolName"] = True
  777     if re.match("^no$", Options["--showMolName"], re.I):
  778         OptionsInfo["ShowMolName"] = False
  779     
  780     OptionsInfo["ScrollX"] = True
  781     if re.match("^no$", Options["--scrollX"], re.I):
  782         OptionsInfo["ScrollX"] = False
  783         
  784     OptionsInfo["ScrollY"] = True
  785     if re.match("^no$", Options["--scrollY"], re.I):
  786         OptionsInfo["ScrollY"] = False
  787 
  788     OptionsInfo["ScrollYSize"] = Options["--scrollYSize"]
  789     if re.match("vh$", Options["--scrollYSize"], re.I):
  790         ScrollYSize = int(re.sub("vh$", "", Options["--scrollYSize"]))
  791         if ScrollYSize <= 0:
  792             MiscUtil.PrintError("The value specified, %s, for option \"--scrollYSize\" is not valid. Supported value: > 0 followed by \"vh\"" % Options["--scrollYSize"])
  793     
  794     TableStyle = None
  795     if not re.match("^None$", Options["--tableStyle"], re.I):
  796         if re.match("^All$", Options["--tableStyle"], re.I):
  797             TableStyle = "table table-striped table-bordered table-hover table-dark"
  798         else:
  799             TableStyle = re.sub(" ", "", Options["--tableStyle"])
  800             for Style in [Style for Style in TableStyle.split(",")]:
  801                 if not re.match("^(table|table-striped|table-bordered|table-hover|table-dark|table-sm)$", Style, re.I):
  802                     MiscUtil.PrintWarning("The table style name, %s, specified using option \"-t, --tableStyle\" appears to be a unknown style..." % (Style))
  803             TableStyle = re.sub(",", " ", TableStyle.lower())
  804     OptionsInfo["TableStyle"]  = TableStyle
  805 
  806     OptionsInfo["TableFooter"] = True
  807     if re.match("^no$", Options["--tableFooter"], re.I):
  808         OptionsInfo["TableFooter"] = False
  809     
  810     OptionsInfo["TableHeader"] = True
  811     if re.match("^no$", Options["--tableHeader"], re.I):
  812         OptionsInfo["TableHeader"] = False
  813     
  814     TableHeaderStyle = None
  815     if not re.match("^None$", Options["--tableHeaderStyle"], re.I):
  816         TableHeaderStyle = Options["--tableHeaderStyle"]
  817         TableHeaderStyle = TableHeaderStyle.lower()
  818         CheckOptionTableClassColorValues("--tableHeaderStyle", [TableHeaderStyle])
  819     OptionsInfo["TableHeaderStyle"]  = TableHeaderStyle
  820 
  821 def CheckOptionTableClassColorValues(OptionName, ColorsList):
  822     """Check names of table color classes and issue a warning for unknown names."""
  823 
  824     TableClassColors = ["thead-dark", "thead-light", "table-primary", "table-success", "table-danger", "table-info", "table-warning", "table-active", "table-secondary", "table-light", "table-dark", "bg-primary", "bg-success", "bg-danger",  "bg-info", "bg-warning", "bg-secondary", "bg-dark", "bg-light"]
  825 
  826     for Color in ColorsList:
  827         if not Color in TableClassColors:
  828             MiscUtil.PrintWarning("The color class name, %s, specified using option \"%s\" appears to be a unknown name..." % (Color, OptionName))
  829         
  830 def RetrieveOptions():
  831     """Retrieve command line arguments and options."""
  832     
  833     # Get options...
  834     global Options
  835     Options = docopt(_docoptUsage_)
  836     
  837     # Set current working directory to the specified directory...
  838     WorkingDir = Options["--workingdir"]
  839     if WorkingDir:
  840         os.chdir(WorkingDir)
  841     
  842     # Handle examples option...
  843     if "--examples" in Options and Options["--examples"]:
  844         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
  845         sys.exit(0)
  846 
  847 def ValidateOptions():
  848     """Validate option values."""
  849     
  850     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
  851     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol smi csv tsv txt")
  852 
  853     MiscUtil.ValidateOptionsOutputFileOverwrite("-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"])
  854     MiscUtil.ValidateOptionsDistinctFileNames("-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"])
  855     
  856     if not re.match("^None$", Options["--alignmentSMARTS"], re.I):
  857         PatternMol = Chem.MolFromSmarts(Options["--alignmentSMARTS"])
  858         if PatternMol is None:
  859             MiscUtil.PrintError("The value specified, %s, using option \"--alignmentSMARTS\" is not a valid SMARTS: Failed to create pattern molecule" % Options["--alignmentSMARTS"])
  860     
  861     MiscUtil.ValidateOptionIntegerValue("--atomLabelFontSize", Options["--atomLabelFontSize"], {">": 0})
  862     MiscUtil.ValidateOptionFloatValue("-b, --bondLineWidth", Options["--bondLineWidth"], {">": 0.0})
  863     
  864     MiscUtil.ValidateOptionTextValue("--compute2DCoords", Options["--compute2DCoords"], "yes no auto")
  865     
  866     MiscUtil.ValidateOptionTextValue("--counterCol", Options["--counterCol"], "yes no")
  867     MiscUtil.ValidateOptionTextValue("--colVisibility", Options["--colVisibility"], "yes no")
  868     
  869     MiscUtil.ValidateOptionTextValue("--f, -fontBold", Options["--fontBold"], "yes no")
  870     
  871     if not re.match("^None$", Options["--highlightSMARTS"], re.I):
  872         PatternMol = Chem.MolFromSmarts(Options["--highlightSMARTS"])
  873         if PatternMol is None:
  874             MiscUtil.PrintError("The value specified, %s, using option \"--highlightSMARTS\" is not a valid SMARTS: Failed to create pattern molecule" % Options["--highlightSMARTS"])
  875     
  876     MiscUtil.ValidateOptionTextValue("--kekulize", Options["--kekulize"], "yes no")
  877     
  878     MiscUtil.ValidateOptionTextValue("-k, --keysNavigation", Options["--keysNavigation"], "yes no")
  879     
  880     MiscUtil.ValidateOptionNumberValues("-m, --molImageSize", Options["--molImageSize"], 2, ",", "integer", {">": 0})
  881     MiscUtil.ValidateOptionTextValue("--molImageEncoded", Options["--molImageEncoded"], "yes no")
  882     
  883     MiscUtil.ValidateOptionIntegerValue("--numOfMolsPerRow", Options["--numOfMolsPerRow"], {">": 0})
  884     
  885     MiscUtil.ValidateOptionTextValue("-p, --paging", Options["--paging"], "yes no")
  886     MiscUtil.ValidateOptionIntegerValue("--pageLength", Options["--pageLength"], {">": 0})
  887     
  888     MiscUtil.ValidateOptionTextValue("--popover", Options["--popover"], "yes no")
  889     MiscUtil.ValidateOptionIntegerValue("--popoverDataCount", Options["--popoverDataCount"], {">": 0})
  890     MiscUtil.ValidateOptionIntegerValue("--popoverTextWidth", Options["--popoverTextWidth"], {">": 0})
  891     
  892     MiscUtil.ValidateOptionTextValue("--showMolName", Options["--showMolName"], "yes no")
  893     
  894     MiscUtil.ValidateOptionTextValue("--scrollX", Options["--scrollX"], "yes no")
  895     MiscUtil.ValidateOptionTextValue("--scrollY", Options["--scrollY"], "yes no")
  896     if not re.search("vh$", Options["--scrollYSize"], re.I):
  897         MiscUtil.ValidateOptionIntegerValue("--scrollYSize", Options["--scrollYSize"], {">": 0})
  898 
  899     MiscUtil.ValidateOptionTextValue("--tableFooter", Options["--tableFooter"], "yes no")
  900     MiscUtil.ValidateOptionTextValue("--tableHeader", Options["--tableHeader"], "yes no")
  901     
  902 # Setup a usage string for docopt...
  903 _docoptUsage_ = """
  904 RDKitDrawMolecules.py - Draw molecules and generate an image or HTML file
  905 
  906 Usage:
  907     RDKitDrawMolecules.py [--alignmentSMARTS <SMARTS>] [--atomLabelFontSize <number>]
  908                              [--bondLineWidth <number>] [--compute2DCoords <yes | no>] [--counterCol <yes or no>]
  909                              [--colVisibility <yes or no>] [--fontBold <yes or no>] [--footer <text>] [--footerClass <text>] 
  910                              [--header <text>] [--headerStyle <text>] [--highlightSMARTS <SMARTS>]
  911                              [--infileParams <Name,Value,...>] [--kekulize <yes or no>] [--keysNavigation <yes or no>]
  912                              [--molImageSize <width,height>] [--molImageEncoded <yes or no> ]
  913                              [--numOfMolsPerRow <number>] [--overwrite] [--paging <yes or no>]
  914                              [--pagingType <numbers, simple, ...>] [--pageLength <number>]
  915                              [--popover <yes or no>] [--popoverDataCount <number>] [--popoverTextWidth <number>]
  916                              [--showMolName <yes or no>] [--scrollX <yes or no>] [--scrollY <yes or no>]
  917                              [--scrollYSize <number>] [--tableFooter <yes or no>] [--tableHeader <yes or no>]
  918                              [--tableHeaderStyle <thead-dark,thead-light,...>]
  919                              [--tableStyle <table,table-striped,...>] [-w <dir>] -i <infile> -o <outfile>
  920     RDKitDrawMolecules.py -h | --help | -e | --examples
  921 
  922 Description:
  923     Draw molecules in a grid and write them out as an image file or a HTML table file. The
  924     SVG image or HTML table file appears to be the best among all the available image file
  925     options, as rendered in a browser. The Python modules aggdraw/cairo are required to
  926     generate high quality PNG images.
  927     
  928     The drawing of the molecules are embedded in HTML table columns as in line SVG
  929     images. The HTML table is an interactive table and requires internet access for viewing
  930     in a browser. It employs he following frameworks: JQuery, Bootstrap, and DataTable.
  931     
  932     The options '--atomLabelFontSize' and '--bondLineWidth' don't appear to work
  933     during the generation of a SVG image. In addition, these may not work for other
  934     image types in the latest version of RDKIT.
  935 
  936     The supported input file formats are: Mol (.mol), SD (.sdf, .sd), SMILES (.smi,
  937     .txt, .csv, .tsv)
  938 
  939     The output image file can be saved in any format supported by the Python Image
  940     Library (PIL). The image format is automatically detected from the output file extension.
  941 
  942     Some of the most common output image file formats are: GIF (.gif), JPEG (.jpg),
  943     PNG (.png), SVG (.svg), TIFF (.tif). In addition, a HTML (.html) file format
  944     containing a table is supported.
  945 
  946 Options:
  947     -a, --alignmentSMARTS <SMARTS>  [default: none]
  948         SMARTS pattern for aligning molecules to a common template.
  949     --atomLabelFontSize <number>  [default: 12]
  950         Font size for drawing atom labels. This option is ignored during the generation of
  951         a SVG and HTML output file. This option may not work in the latest version of RDKit.
  952     -b, --bondLineWidth <number>  [default: 1.2]
  953         Line width for drawing bonds. This option is ignored during the generation of a SVG
  954         and HTML output file. This option may not work in the latest version of RDKit.
  955     -c, --compute2DCoords <yes or no>  [default: auto]
  956         Compute 2D coordinates of molecules before drawing. Default: yes for all file
  957         formats.
  958     --counterCol <yes or no>  [default: yes]
  959         Show a counter column as the first column in the table. It contains the position
  960         for each row in the HTML table. This option is only used during the generation of
  961         a HTML table file.
  962     --colVisibility <yes or no>  [default: yes]
  963         Show a dropdown button to toggle visibility of columns in the table. This option is
  964         only used during the generation of a HTML table file.
  965     -e, --examples
  966         Print examples.
  967     -f --fontBold <yes or no>  [default: yes]
  968         Make all text fonts bold during the generation of  a SVG and HTML output file. This
  969         option is ignored for all other output files. This option may not work in the latest
  970         version of RDKit.
  971     --footer <text>  [default: none]
  972         Footer text to insert at the bottom of the HTML page after the table. This option is
  973         only used during the generation of a HTML table file.
  974     --footerClass <text>  [default: small text-center text-muted]
  975         Footer class style to use with <p> tag. This option is only used during the
  976         generation of a HTML table file.
  977     -h, --help
  978         Print this help message.
  979     --header <text>  [default: none]
  980         Header text to insert at the top of the HTML page before the table. This option is
  981         only used during the generation of a HTML table file.
  982     --headerStyle <text>  [default: h5]
  983         Header style to use. Possible values: h1 to h6. This option is only used during the
  984         generation of a HTML table file.
  985     --highlightSMARTS <SMARTS>  [default: none]
  986         SMARTS pattern for highlighting atoms and bonds in molecules. All matched
  987         substructures are highlighted.
  988     -i, --infile <infile>
  989         Input file name.
  990     --infileParams <Name,Value,...>  [default: auto]
  991         A comma delimited list of parameter name and value pairs for reading
  992         molecules from files. The supported parameter names for different file
  993         formats, along with their default values, are shown below:
  994             
  995             SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes
  996             SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space,
  997                 smilesTitleLine,auto,sanitize,yes
  998             
  999         Possible values for smilesDelimiter: space, comma or tab.
 1000     -k, --kekulize <yes or no>  [default: yes]
 1001         Perform kekulization on molecules. This option is ignored during the generation of
 1002         a SVG and HTML output file.
 1003     --keysNavigation <yes or no>  [default: yes]
 1004         Provide Excel like keyboard cell navigation for the table. This option is only used
 1005         during the generation of a HTML table file.
 1006     -m, --molImageSize <width,height>  [default: 250,200]
 1007         Image size of a molecule in pixels.
 1008     --molImageEncoded <yes or no>  [default: yes]
 1009         Base64 encode SVG image of a molecule for inline embedding in a HTML page.
 1010         The inline SVG image may fail to display in browsers without encoding.
 1011     -n, --numOfMolsPerRow <number>  [default: 4]
 1012         Number of molecules to draw in a row.
 1013     -o, --outfile <outfile>
 1014         Output file name.
 1015     --overwrite
 1016         Overwrite existing files.
 1017     -p, --paging <yes or no>  [default: yes]
 1018         Provide page navigation for browsing data in the table. This option is only used
 1019         during the generation of a HTML table file.
 1020     --pagingType <numbers, simple, ...>  [default: full_numbers]
 1021         Type of page navigation. Possible values: numbers, simple, simple_numbers,
 1022         full, full_numbers, or first_last_numbers.
 1023             
 1024             numbers - Page number buttons only
 1025             simple - 'Previous' and 'Next' buttons only
 1026             simple_numbers - 'Previous' and 'Next' buttons, plus page numbers
 1027             full - 'First', 'Previous', 'Next' and 'Last' buttons
 1028             full_numbers - 'First', 'Previous', 'Next' and 'Last' buttons, plus
 1029                 page numbers
 1030             first_last_numbers - 'First' and 'Last' buttons, plus page numbers
 1031             
 1032         This option is only used during the generation of a HTML table file.
 1033     --pageLength <number>  [default: 5]
 1034         Number of rows to show per page. This option is only used during the
 1035         generation of a HTML table file.
 1036     --popover <yes or no>  [default: yes]
 1037         Display a popover window containing additional information about the
 1038         molecule. The popover is opened after a click on the drawing of a
 1039         molecule. A subsequent click on the same drawing closes the popover.
 1040         This option is only used during the generation of a HTML table file.
 1041     --popoverDataCount <number>  [default: 25]
 1042         Maximum number of data fields to show in a popover window. This option is
 1043         only used during the generation of a HTML table file.
 1044     --popoverTextWidth <number>  [default: 50]
 1045         Maximum width in characters for text display in a popover window before
 1046         truncating the text. This option is only used during the generation of a HTML
 1047         table file.
 1048     -s, --showMolName <yes or no>  [default: yes]
 1049         Show molecule names under the images.This option is only used during the
 1050         generation of a HTML table file.
 1051     --scrollX <yes or no>  [default: yes]
 1052         Provide horizontal scroll bar in the table as needed.This option is only used
 1053         during the generation of a HTML table file.
 1054     --scrollY <yes or no>  [default: yes]
 1055         Provide vertical scroll bar in the table as needed.This option is only used during
 1056         the generation of a HTML table file.
 1057     --scrollYSize <number>  [default: 75vh]
 1058         Maximum height of table viewport either in pixels or percentage of the browser
 1059         window height before providing a vertical scroll bar. Default: 75% of the height of
 1060         browser window.This option is only used during the generation of a HTML table file.
 1061     -t, --tableStyle <table,table-striped,...>  [default: table,table-hover,table-sm]
 1062         Style of table. Possible values: table, table-striped, table-bordered,
 1063         table-hover, table-dark, table-sm, none, or All. Default: 'table,table-hover'. A
 1064         comma delimited list of any valid Bootstrap table styles is also supported
 1065         
 1066         This option is only used during the generation of a HTML table file.
 1067     --tableFooter <yes or no>  [default: yes]
 1068         Show Excel style column headers at the end of  the table. This option is only
 1069         used during the generation of a HTML table file.
 1070     --tableHeader <yes or no>  [default: yes]
 1071         Show Excel style column headers in the table. This option is only used
 1072         during the generation of a HTML table file.
 1073     --tableHeaderStyle <thead-dark,thead-light,...>  [default: thead-dark]
 1074         Style of table header. Possible values: thead-dark, thead-light, or none.
 1075         The names of the following contextual color classes are also supported:
 1076         table-primary (Blue), table-success (Green), table-danger (Red), table-info
 1077         (Light blue), table-warning (Orange), table-active (Grey), table-light (Light
 1078         grey), and  table-dark (Dark grey).
 1079         
 1080         This option is only used during the generation of a HTML table file.
 1081     -w, --workingdir <dir>
 1082         Location of working directory which defaults to the current directory.
 1083 
 1084 Examples:
 1085     To automatically compute 2D coordinates for molecules in a SMILES file and
 1086     generate a SVG image file containing 4 molecules per row in a grid with cell
 1087     size of 250 x 200 pixels, type:
 1088 
 1089         % RDKitDrawMolecules.py -i Sample.smi -o SampleOut.svg
 1090 
 1091     To automatically compute 2D coordinates for molecules in a SMILES file and
 1092     generate a SVG image file containing 2 molecules per row in a grid with cell
 1093     size of 400 x 300 pixels and without any keulization along with highlighting
 1094     a specific set of atoms and bonds indicated by a SMARTS pattern, type:
 1095 
 1096         % RDKitDrawMolecules.py -n 2 -m "400,300" -k no --fontBold no
 1097           --highlightSMARTS  'c1ccccc1' -i Sample.smi -o SampleOut.svg
 1098 
 1099     To generate a PNG image file for molecules in a SD file using existing 2D
 1100     coordinates, type
 1101 
 1102         % RDKitDrawMolecules.py --compute2DCoords no -i Sample.sdf
 1103           -o SampleOut.png
 1104 
 1105     To automatically compute 2D coordinates for molecules in a SD file and
 1106     generate a HTML file containing 4 molecules per row in a table, along with
 1107     all the bells and whistles to interact with the table, type:
 1108 
 1109         % RDKitDrawMolecules.py -i Sample.sdf -o SampleOut.html
 1110 
 1111     To automatically compute 2D coordinates for molecules in a SD file and
 1112     generate a HTML file containing 4 molecules per row in a table without
 1113     any bells and whistles to interact with the table, type:
 1114 
 1115         % RDKitDrawMolecules.py --counterCol no --colVisibility no
 1116           --keysNavigation no --paging  no --popover no --scrollX no
 1117           --scrollY no --tableFooter no --tableHeader  no -i Sample.sdf
 1118           -o SampleOut.html
 1119 
 1120     To automatically compute 2D coordinates for molecules in a CSV SMILES file
 1121     with column headers, SMILES strings in column 1, and name in column 2 and
 1122     generate a PDF image file, type:
 1123 
 1124         % RDKitDrawMolecules.py --infileParams "smilesDelimiter,comma,
 1125           smilesTitleLine,yes,smilesColumn,1,smilesNameColumn,2"
 1126           -i SampleSMILES.csv -o SampleOut.pdf
 1127 
 1128 Author:
 1129     Manish Sud(msud@san.rr.com)
 1130 
 1131 See also:
 1132     RDKitConvertFileFormat.py, RDKitDrawMoleculesAndDataTable.py, RDKitRemoveDuplicateMolecules.py,
 1133     RDKitSearchFunctionalGroups.py, RDKitSearchSMARTS.py
 1134 
 1135 Copyright:
 1136     Copyright (C) 2024 Manish Sud. All rights reserved.
 1137 
 1138     The functionality available in this script is implemented using RDKit, an
 1139     open source toolkit for cheminformatics developed by Greg Landrum.
 1140 
 1141     This file is part of MayaChemTools.
 1142 
 1143     MayaChemTools is free software; you can redistribute it and/or modify it under
 1144     the terms of the GNU Lesser General Public License as published by the Free
 1145     Software Foundation; either version 3 of the License, or (at your option) any
 1146     later version.
 1147 
 1148 """
 1149 
 1150 if __name__ == "__main__":
 1151     main()