MayaChemTools

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