1 //
2 //  Copyright (C) 2014-2020 David Cosgrove and Greg Landrum
3 //
4 //   @@ All Rights Reserved @@
5 //  This file is part of the RDKit.
6 //  The contents are covered by the terms of the BSD license
7 //  which is included in the file license.txt, found at the root
8 //  of the RDKit source tree.
9 //
10 // Original author: David Cosgrove (AstraZeneca)
11 // 27th May 2014
12 //
13 // This class makes a 2D drawing of an RDKit molecule.
14 // It draws heavily on $RDBASE/GraphMol/MolDrawing/MolDrawing.h.
15 // One purpose of this is to make it easier to overlay annotations on top of
16 // the molecule drawing, which is difficult to do from the output of
17 // MolDrawing.h
18 // The class design philosophy echoes a standard one:
19 // a virtual base class defines the interface and does all
20 // the heavy lifting and concrete derived classes implement
21 // library-specific drawing code such as drawing lines, writing strings
22 // etc.
23 
24 #include <RDGeneral/export.h>
25 #ifndef RDKITMOLDRAW2D_H
26 #define RDKITMOLDRAW2D_H
27 
28 #include <vector>
29 
30 #include <Geometry/point.h>
31 #include <Geometry/Transform2D.h>
32 #include <GraphMol/RDKitBase.h>
33 #include <GraphMol/ChemReactions/Reaction.h>
34 
35 // ****************************************************************************
36 using RDGeom::Point2D;
37 
38 namespace RDKit {
39 
40 class DrawText;
41 // for aligning the drawing of text to the passed in coords.
42 enum class OrientType : unsigned char { C = 0, N, E, S, W };
43 enum class TextAlignType : unsigned char { MIDDLE = 0, START, END };
44 
45 struct DrawColour {
46   double r = 0.0, g = 0.0, b = 0.0, a = 1.0;
47   DrawColour() = default;
48   DrawColour(double r, double g, double b, double a = 1.0)
rDrawColour49       : r(r), g(g), b(b), a(a){};
50   bool operator==(const DrawColour &other) const {
51     return r == other.r && g == other.g && b == other.b && a == other.a;
52   }
53   bool feq(const DrawColour &other, double tol = 0.001,
54            bool ignoreAlpha = true) const {
55     return fabs(r - other.r) <= tol && fabs(g - other.g) <= tol &&
56            fabs(b - other.b) <= tol &&
57            (ignoreAlpha || fabs(a - other.a) <= tol);
58   };
59   DrawColour operator+(const DrawColour &other) const {
60     return {r + other.r, g + other.g, b + other.b, a + other.a};
61   }
62   DrawColour operator-(const DrawColour &other) const {
63     return {r - other.r, g - other.g, b - other.b, a - other.a};
64   }
65   DrawColour operator/(double v) const {
66     PRECONDITION(v != 0.0, "divide by zero");
67     return {r / v, g / v, b / v, a / v};
68   }
69   DrawColour operator*(double v) const { return {r * v, g * v, b * v, a * v}; }
70 };
71 
72 //! for annotating the type of the extra shapes
73 enum class MolDrawShapeType {
74   Arrow,  // ordering of points is: start, end, p1, p2
75   Polyline,
76   Ellipse,
77 };
78 
79 //! extra shape to add to canvas
80 struct MolDrawShape {
81   MolDrawShapeType shapeType = MolDrawShapeType::Polyline;
82   std::vector<Point2D> points;
83   DrawColour lineColour{0, 0, 0};
84   int lineWidth = 2;
85   bool fill = false;
86   bool scaleLineWidth = false;
87 };
88 
89 // for holding dimensions of the rectangle round a string.
90 struct StringRect {
91   Point2D trans_;     // Where to draw char relative to other chars in string
92   Point2D offset_;    // offset for draw coords so char is centred correctly
93   Point2D g_centre_;  // glyph centre relative to the origin of the char.
94   double y_shift_;  // shift the whole thing in y by this. For multi-line text.
95   double width_, height_;  // of the glyph itself, not the character cell
96   double rect_corr_;  // because if we move a char one way, we need to move the
97                       // rectangle the other.
98   int clash_score_;   // rough measure of how badly it clashed with other things
99                       // lower is better, 0 is no clash.
100 
StringRectStringRect101   StringRect()
102       : trans_(0.0, 0.0),
103         offset_(0.0, 0.0),
104         g_centre_(offset_),
105         y_shift_(0.0),
106         width_(0.0),
107         height_(0.0),
108         rect_corr_(0.0),
109         clash_score_(0) {}
StringRectStringRect110   StringRect(const Point2D &offset, const Point2D &g_centre, double w, double h)
111       : trans_(0.0, 0.0),
112         offset_(offset),
113         g_centre_(g_centre),
114         y_shift_(0.0),
115         width_(w),
116         height_(h),
117         rect_corr_(0.0),
118         clash_score_(0) {}
119   // tl is top, left; br is bottom, right of the glyph, relative to the
120   // centre. Padding in draw coords.
calcCornersStringRect121   void calcCorners(Point2D &tl, Point2D &tr, Point2D &br, Point2D &bl,
122                    double padding) const {
123     double wb2 = padding + width_ / 2.0;
124     double hb2 = padding + height_ / 2.0;
125     Point2D c = trans_ + g_centre_ - offset_;
126     c.y -= y_shift_;
127     tl = Point2D(c.x - wb2, c.y - hb2);
128     tr = Point2D(c.x + wb2, c.y - hb2);
129     br = Point2D(c.x + wb2, c.y + hb2);
130     bl = Point2D(c.x - wb2, c.y + hb2);
131   }
doesItIntersectStringRect132   bool doesItIntersect(const StringRect &other) const {
133     Point2D ttl, ttr, tbr, tbl;
134     calcCorners(ttl, ttr, tbr, tbl, 0.0);
135     // is +ve y up or down?
136     if (ttl.y < tbl.y) {
137       std::swap(ttl, tbl);
138       std::swap(ttr, tbr);
139     }
140     Point2D otl, otr, obr, obl;
141     other.calcCorners(otl, otr, obr, obl, 0.0);
142     if (otl.y < obl.y) {
143       std::swap(otl, obl);
144       std::swap(otr, obr);
145     }
146     if ((otl.x >= ttl.x && otl.x <= ttr.x && otl.y >= tbl.y &&
147          otl.y <= ttl.y) ||
148         (otr.x >= ttl.x && otr.x <= ttr.x && otr.y >= tbl.y &&
149          otr.y <= ttl.y) ||
150         (obr.x >= ttl.x && obr.x <= ttr.x && obr.y >= tbl.y &&
151          obr.y <= ttl.y) ||
152         (obl.x >= ttl.x && obl.x <= ttr.x && obl.y >= tbl.y &&
153          obl.y <= ttl.y)) {
154       return true;
155     }
156     if ((ttl.x >= otl.x && ttl.x <= otr.x && ttl.y >= obl.y &&
157          ttl.y <= otl.y) ||
158         (ttr.x >= otl.x && ttr.x <= otr.x && ttr.y >= obl.y &&
159          ttr.y <= otl.y) ||
160         (tbr.x >= otl.x && tbr.x <= otr.x && tbr.y >= obl.y &&
161          tbr.y <= otl.y) ||
162         (tbl.x >= otl.x && tbl.x <= otr.x && tbl.y >= obl.y &&
163          tbl.y <= otl.y)) {
164       return true;
165     }
166     return false;
167   }
168 };
169 struct AnnotationType {
170   std::string text_;
171   StringRect rect_;
172   OrientType orient_ = OrientType::C;
173   TextAlignType align_ = TextAlignType::MIDDLE;
174   bool scaleText_ = true;
175 };
176 
177 typedef std::map<int, DrawColour> ColourPalette;
178 typedef std::vector<double> DashPattern;
179 
assignDefaultPalette(ColourPalette & palette)180 inline void assignDefaultPalette(ColourPalette &palette) {
181   palette.clear();
182   palette[-1] = DrawColour(0, 0, 0);
183   palette[0] = DrawColour(0.1, 0.1, 0.1);
184   palette[1] = palette[6] = DrawColour(0.0, 0.0, 0.0);
185   palette[7] = DrawColour(0.0, 0.0, 1.0);
186   palette[8] = DrawColour(1.0, 0.0, 0.0);
187   palette[9] = DrawColour(0.2, 0.8, 0.8);
188   palette[15] = DrawColour(1.0, 0.5, 0.0);
189   palette[16] = DrawColour(0.8, 0.8, 0.0);
190   palette[17] = DrawColour(0.0, 0.802, 0.0);
191   palette[35] = DrawColour(0.5, 0.3, 0.1);
192   palette[53] = DrawColour(0.63, 0.12, 0.94);
193 };
194 
assignBWPalette(ColourPalette & palette)195 inline void assignBWPalette(ColourPalette &palette) {
196   palette.clear();
197   palette[-1] = DrawColour(0, 0, 0);
198 };
199 
200 struct RDKIT_MOLDRAW2D_EXPORT MolDrawOptions {
201   bool atomLabelDeuteriumTritium =
202       false;  // toggles replacing 2H with D and 3H with T
203   bool dummiesAreAttachments = false;  // draws "breaks" at dummy atoms
204   bool circleAtoms = true;             // draws circles under highlighted atoms
205   bool splitBonds = false;             // split bonds into per atom segments
206                             // most useful for dynamic manipulation of drawing
207                             // especially for svg
208   DrawColour highlightColour{1, 0.5, 0.5, 1.0};  // default highlight color
209   bool continuousHighlight = true;  // highlight by drawing an outline
210                                     // *underneath* the molecule
211   bool fillHighlights = true;     // fill the areas used to highlight atoms and
212                                   // atom regions
213   double highlightRadius = 0.3;   // default if nothing given for a particular
214                                   // atom. units are "Angstrom"
215   int flagCloseContactsDist = 3;  // if positive, this will be used as a cutoff
216                                   // (in pixels) for highlighting close contacts
217   bool includeAtomTags =
218       false;  // toggles inclusion of atom tags in the output. does
219               // not make sense for all renderers.
220   bool clearBackground = true;  // toggles clearing the background before
221                                 // drawing a molecule
222   DrawColour backgroundColour{
223       1, 1, 1, 1};          // color to be used while clearing the background
224   int legendFontSize = 16;  // font size (in pixels) to be used for the legend
225                             // (if present)
226   int maxFontSize = 40;  // maximum size in pixels for font in drawn molecule.
227                          // -1 means no max.
228   int minFontSize = 6;   // likewise for -1.
229   double annotationFontScale = 0.5;  // scales font relative to atom labels for
230                                      // atom and bond annotation.
231   std::string fontFile = "";  // name of font for freetype rendering.  If given,
232                               // over-rides default
233   DrawColour legendColour{0, 0,
234                           0};  // color to be used for the legend (if present)
235   double multipleBondOffset = 0.15;  // offset (in Angstrom) for the extra lines
236                                      // in a multiple bond
237   double padding =
238       0.05;  // fraction of empty space to leave around the molecule
239   double additionalAtomLabelPadding = 0.0;  // additional padding to leave
240                                             // around atom labels. Expressed as
241                                             // a fraction of the font size.
242   std::map<int, std::string> atomLabels;    // replacement labels for atoms
243   bool noAtomLabels =
244       false;  // disables inclusion of atom labels in the rendering
245   std::vector<std::vector<int>> atomRegions;  // regions
246   DrawColour symbolColour{
247       0, 0, 0, 1};  // color to be used for the symbols and arrows in reactions
248   DrawColour annotationColour{0, 0, 0, 1};  // color to be used for annotations
249   int bondLineWidth = 2;        // default line width when drawing bonds
250   bool scaleBondWidth = false;  // whether to apply scale() to the bond width
251   bool scaleHighlightBondWidth = true;   // likewise with bond highlights.
252   int highlightBondWidthMultiplier = 8;  // what to multiply standard bond width
253                                          // by for highlighting.
254   bool prepareMolsBeforeDrawing = true;  // call prepareMolForDrawing() on each
255                                          // molecule passed to drawMolecules()
256   std::vector<DrawColour> highlightColourPalette;  // defining 10 default colors
257   // for highlighting atoms and bonds
258   // or reactants in a reactions
259   ColourPalette atomColourPalette;  // the palette used to assign
260                                     // colors to atoms based on
261                                     // atomic number.
262   double fixedScale =
263       -1.0;  // fixes scale to this fraction of draw window width, so
264              // an average bond is this fraction of the width.  If
265              // scale comes out smaller than this, reduces scale, but
266              // won't make it larger.  The default of -1.0 means no fix.
267   double fixedBondLength =
268       -1.0;             // fixes the bond length (and hence the scale) to
269                         // always be this number of pixels.  Assuming a bond
270                         // length in coordinates is 1, as is normal.  If
271                         // scale comes out smaller than this, reduces scale,
272                         // but won't make it larger.  The default -1.0 means no
273                         // fix. If both fixedScale and fixedBondLength are >
274                         // 0.0, fixedScale wins.
275   double rotate = 0.0;  // angle in degrees to rotate coords by about centre
276                         // before drawing.
277   bool addAtomIndices = false;     // adds atom indices to drawings.
278   bool addBondIndices = false;     // adds bond indices to drawings.
279   bool isotopeLabels = true;       // adds isotope to non-dummy atoms.
280   bool dummyIsotopeLabels = true;  // adds isotope labels to dummy atoms.
281 
282   bool addStereoAnnotation = false;       // adds E/Z and R/S to drawings.
283   bool atomHighlightsAreCircles = false;  // forces atom highlights always to be
284                                           // circles. Default (false) is to put
285                                           // ellipses round longer labels.
286   bool centreMoleculesBeforeDrawing = false;  // moves the centre of the drawn
287                                               // molecule to (0,0)
288   bool explicitMethyl = false;  // draw terminal methyl and related as CH3
289   bool includeRadicals =
290       true;  // include radicals in the drawing (it can be useful to turn this
291              // off for reactions and queries)
292   bool includeMetadata =
293       true;  // when possible include metadata about molecules and reactions in
294              // the output to allow them to be reconstructed
295   bool comicMode = false;  // simulate hand-drawn lines for bonds. When combined
296                            // with a font like Comic-Sans or Comic-Neue, this
297                            // gives xkcd-like drawings.
298   int variableBondWidthMultiplier = 16;  // what to multiply standard bond width
299                                          // by for variable attachment points.
300   double variableAtomRadius = 0.4;  // radius value to use for atoms involved in
301                                     // variable attachment points.
302   DrawColour variableAttachmentColour = {
303       0.8, 0.8, 0.8, 1.0};  // colour to use for variable attachment points
304   bool includeChiralFlagLabel =
305       false;  // add a molecule annotation with "ABS" if the chiral flag is set
306   bool simplifiedStereoGroupLabel =
307       false;  // if all specified stereocenters are in a single StereoGroup,
308               // show a molecule-level annotation instead of the individual
309               // labels
310   bool singleColourWedgeBonds = false; // if true wedged and dashed bonds are drawn
311                                 // using symbolColour rather than inheriting
312                                 // their colour from the atoms
313 
MolDrawOptionsMolDrawOptions314   MolDrawOptions() {
315     highlightColourPalette.emplace_back(
316         DrawColour(1., 1., .67));  // popcorn yellow
317     highlightColourPalette.emplace_back(DrawColour(1., .8, .6));  // sand
318     highlightColourPalette.emplace_back(
319         DrawColour(1., .71, .76));  // light pink
320     highlightColourPalette.emplace_back(
321         DrawColour(.8, 1., .8));  // offwhitegreen
322     highlightColourPalette.emplace_back(DrawColour(.87, .63, .87));  // plum
323     highlightColourPalette.emplace_back(
324         DrawColour(.76, .94, .96));  // pastel blue
325     highlightColourPalette.emplace_back(
326         DrawColour(.67, .67, 1.));  // periwinkle
327     highlightColourPalette.emplace_back(DrawColour(.64, .76, .34));  // avocado
328     highlightColourPalette.emplace_back(
329         DrawColour(.56, .93, .56));  // light green
330     highlightColourPalette.emplace_back(DrawColour(.20, .63, .79));  // peacock
331     assignDefaultPalette(atomColourPalette);
332   };
333 };
334 
335 //! MolDraw2D is the base class for doing 2D renderings of molecules
336 class RDKIT_MOLDRAW2D_EXPORT MolDraw2D {
337  public:
338   //! constructor for a particular size
339   /*!
340     \param width       : width (in pixels) of the rendering
341     \param height      : height (in pixels) of the rendering
342     \param panelWidth  : (optional) width (in pixels) of a single panel
343     \param panelHeight : (optional) height (in pixels) of a single panel
344 
345     The \c panelWidth and \c panelHeight arguments are used to provide the
346     sizes of the panels individual molecules are drawn in when
347     \c drawMolecules() is called.
348   */
349   MolDraw2D(int width, int height, int panelWidth, int panelHeight);
350   virtual ~MolDraw2D();
351 
352   //! \name Methods that must be provided by child classes
353   //@{
354  private:
355   virtual void initDrawing() = 0;
356   virtual void initTextDrawer(bool noFreetype) = 0;
357 
358  public:
359   //! clears the contents of the drawing
360   virtual void clearDrawing() = 0;
361   //! draws a line from \c cds1 to \c cds2 using the current drawing style
362   // in atom coords.
363   virtual void drawLine(const Point2D &cds1, const Point2D &cds2) = 0;
364   //! draw a polygon.  Note that if fillPolys() returns false, it
365   //! doesn't close the path.  If you want it to in that case, you
366   //! do it explicitly yourself.
367   virtual void drawPolygon(const std::vector<Point2D> &cds) = 0;
368   //@}
369 
370   //! draw a single molecule
371   /*!
372     \param mol             : the molecule to draw
373     \param legend          : the legend (to be drawn under the molecule)
374     \param highlight_atoms : (optional) vector of atom ids to highlight
375     \param highlight_atoms : (optional) vector of bond ids to highlight
376     \param highlight_atom_map   : (optional) map from atomId -> DrawColour
377     providing the highlight colors. If not provided the default highlight colour
378     from \c drawOptions() will be used.
379     \param highlight_bond_map   : (optional) map from bondId -> DrawColour
380     providing the highlight colors. If not provided the default highlight colour
381     from \c drawOptions() will be used.
382     \param highlight_radii : (optional) map from atomId -> radius (in molecule
383     coordinates) for the radii of atomic highlights. If not provided the default
384     value from \c drawOptions() will be used.
385     \param confId          : (optional) conformer ID to be used for atomic
386     coordinates
387 
388   */
389   virtual void drawMolecule(
390       const ROMol &mol, const std::string &legend,
391       const std::vector<int> *highlight_atoms,
392       const std::vector<int> *highlight_bonds,
393       const std::map<int, DrawColour> *highlight_atom_map = nullptr,
394       const std::map<int, DrawColour> *highlight_bond_map = nullptr,
395       const std::map<int, double> *highlight_radii = nullptr, int confId = -1);
396 
397   //! \overload
398   virtual void drawMolecule(
399       const ROMol &mol, const std::vector<int> *highlight_atoms = nullptr,
400       const std::map<int, DrawColour> *highlight_map = nullptr,
401       const std::map<int, double> *highlight_radii = nullptr, int confId = -1);
402 
403   //! \overload
404   virtual void drawMolecule(
405       const ROMol &mol, const std::string &legend,
406       const std::vector<int> *highlight_atoms = nullptr,
407       const std::map<int, DrawColour> *highlight_map = nullptr,
408       const std::map<int, double> *highlight_radii = nullptr, int confId = -1);
409 
410   //! \overload
411   virtual void drawMolecule(
412       const ROMol &mol, const std::vector<int> *highlight_atoms,
413       const std::vector<int> *highlight_bonds,
414       const std::map<int, DrawColour> *highlight_atom_map = nullptr,
415       const std::map<int, DrawColour> *highlight_bond_map = nullptr,
416       const std::map<int, double> *highlight_radii = nullptr, int confId = -1);
417 
418   //! draw molecule with multiple colours allowed per atom.
419   /*!
420     \param mol             : the molecule to draw
421     \param legend          : the legend (to be drawn under the molecule)
422     \param highlight_atom_map   : map from atomId -> DrawColours
423     providing the highlight colours.
424     \param highlight_bond_map   : map from bondId -> DrawColours
425     providing the highlight colours.
426     \param highlight_radii : map from atomId -> radius (in molecule
427     coordinates) for the radii of atomic highlights. If not provided for an
428     index, the default value from \c drawOptions() will be used.
429     \param confId          : (optional) conformer ID to be used for atomic
430     coordinates
431   */
432   virtual void drawMoleculeWithHighlights(
433       const ROMol &mol, const std::string &legend,
434       const std::map<int, std::vector<DrawColour>> &highlight_atom_map,
435       const std::map<int, std::vector<DrawColour>> &highlight_bond_map,
436       const std::map<int, double> &highlight_radii,
437       const std::map<int, int> &highlight_linewidth_multipliers,
438       int confId = -1);
439 
440   //! draw multiple molecules in a grid
441   /*!
442     \param mols             : the molecules to draw
443     \param legends          : (optional) the legends (to be drawn under the
444     molecules)
445     \param highlight_atoms  : (optional) vectors of atom ids to highlight
446     \param highlight_atoms  : (optional) vectors of bond ids to highlight
447     \param highlight_atom_map   : (optional) maps from atomId -> DrawColour
448     providing the highlight colors. If not provided the default highlight colour
449     from \c drawOptions() will be used.
450     \param highlight_bond_map   : (optional) maps from bondId -> DrawColour
451     providing the highlight colors. If not provided the default highlight colour
452     from \c drawOptions() will be used.
453     \param highlight_radii  : (optional) maps from atomId -> radius (in molecule
454     coordinates) for the radii of atomic highlights. If not provided the default
455     value from \c drawOptions() will be used.
456     \param confId           : (optional) conformer IDs to be used for atomic
457     coordinates
458 
459     The \c panelWidth and \c panelHeight values will be used to determine the
460     number of rows and columns to be drawn. Theres not a lot of error checking
461     here, so if you provide too many molecules for the number of panes things
462     are likely to get screwed up.
463     If the number of rows or columns ends up being <= 1, molecules will be
464     being drawn in a single row/column.
465   */
466   virtual void drawMolecules(
467       const std::vector<ROMol *> &mols,
468       const std::vector<std::string> *legends = nullptr,
469       const std::vector<std::vector<int>> *highlight_atoms = nullptr,
470       const std::vector<std::vector<int>> *highlight_bonds = nullptr,
471       const std::vector<std::map<int, DrawColour>> *highlight_atom_maps =
472           nullptr,
473       const std::vector<std::map<int, DrawColour>> *highlight_bond_maps =
474           nullptr,
475       const std::vector<std::map<int, double>> *highlight_radii = nullptr,
476       const std::vector<int> *confIds = nullptr);
477 
478   //! draw a ChemicalReaction
479   /*!
480     \param rxn                 : the reaction to draw
481     \param highlightByReactant : (optional) if this is set, atoms and bonds will
482     be highlighted based on which reactant they come from. Atom map numbers
483     will not be shown.
484     \param highlightColorsReactants : (optional) provide a vector of colors for
485     the
486     reactant highlighting.
487     \param confIds   : (optional) vector of confIds to use for rendering. These
488     are numbered by reactants, then agents, then products.
489   */
490   virtual void drawReaction(
491       const ChemicalReaction &rxn, bool highlightByReactant = false,
492       const std::vector<DrawColour> *highlightColorsReactants = nullptr,
493       const std::vector<int> *confIds = nullptr);
494 
495   //! \name Transformations
496   //@{
497   // transform a set of coords in the molecule's coordinate system
498   // to drawing system coordinates and vice versa. Note that the coordinates
499   // have
500   // the origin in the top left corner, which is how Qt and Cairo have it, no
501   // doubt a holdover from X Windows. This means that a higher y value will be
502   // nearer the bottom of the screen. This doesn't really matter except when
503   // doing text superscripts and subscripts.
504 
505   //! transform a point from the molecule coordinate system into the drawing
506   //! coordinate system
507   virtual Point2D getDrawCoords(const Point2D &mol_cds) const;
508   //! returns the drawing coordinates of a particular atom
509   virtual Point2D getDrawCoords(int at_num) const;
510   virtual Point2D getAtomCoords(const std::pair<int, int> &screen_cds) const;
511   //! transform a point from drawing coordinates to the molecule coordinate
512   //! system
513   virtual Point2D getAtomCoords(
514       const std::pair<double, double> &screen_cds) const;
515   //! returns the molecular coordinates of a particular atom
516   virtual Point2D getAtomCoords(int at_num) const;
517   //@}
518   //! return the width of the drawing area.
width()519   virtual int width() const { return width_; }
520   //! return the height of the drawing area.
height()521   virtual int height() const { return height_; }
522   //! return the width of the drawing panels.
panelWidth()523   virtual int panelWidth() const { return panel_width_; }
524   //! return the height of the drawing panels.
panelHeight()525   virtual int panelHeight() const { return panel_height_; }
drawHeight()526   virtual int drawHeight() const { return panel_height_ - legend_height_; }
527 
528   //! returns the drawing scale (conversion from molecular coords -> drawing
529   // coords)
scale()530   double scale() const { return scale_; }
531   //! calculates the drawing scale (conversion from molecular coords -> drawing
532   // coords)
533   void calculateScale(int width, int height, const ROMol &mol,
534                       const std::vector<int> *highlight_atoms = nullptr,
535                       const std::map<int, double> *highlight_radii = nullptr,
536                       int confId = -1);
537   //! overload
538   // calculate a single scale that will suit all molecules.  For use by
539   // drawMolecules primarily.
540   void calculateScale(int width, int height, const std::vector<ROMol *> &mols,
541                       const std::vector<std::vector<int>> *highlight_atoms,
542                       const std::vector<std::map<int, double>> *highlight_radii,
543                       const std::vector<int> *confIds,
544                       std::vector<std::unique_ptr<RWMol>> &tmols);
545   // set [xy]_trans_ to the middle of the draw area in molecule coords
546   void centrePicture(int width, int height);
547 
548   //! explicitly sets the scaling factors for the drawing
549   void setScale(int width, int height, const Point2D &minv, const Point2D &maxv,
550                 const ROMol *mol = nullptr);
551   //! sets the drawing offset (in drawing coords)
setOffset(int x,int y)552   void setOffset(int x, int y) {
553     x_offset_ = x;
554     y_offset_ = y;
555   }
556   //! returns the drawing offset (in drawing coords)
offset()557   Point2D offset() const { return Point2D(x_offset_, y_offset_); }
558 
559   //! returns the minimum point of the drawing (in molecular coords)
minPt()560   Point2D minPt() const { return Point2D(x_min_, y_min_); }
561   //! returns the width and height of the grid (in molecular coords)
range()562   Point2D range() const { return Point2D(x_range_, y_range_); }
563 
564   //! font size in drawing coordinate units. That's probably pixels.
565   virtual double fontSize() const;
566   virtual void setFontSize(double new_size);
567 
568   //! sets the current draw color
setColour(const DrawColour & col)569   virtual void setColour(const DrawColour &col) { curr_colour_ = col; }
570   //! returns the current draw color
colour()571   virtual DrawColour colour() const { return curr_colour_; }
572   //! sets the current dash pattern
setDash(const DashPattern & patt)573   virtual void setDash(const DashPattern &patt) { curr_dash_ = patt; }
574   //! returns the current dash pattern
dash()575   virtual const DashPattern &dash() const { return curr_dash_; }
576 
577   //! sets the current line width
setLineWidth(int width)578   virtual void setLineWidth(int width) { drawOptions().bondLineWidth = width; }
579   //! returns the current line width
lineWidth()580   virtual int lineWidth() const { return drawOptions().bondLineWidth; }
581 
582   //! using the current scale, work out the size of the label in molecule
583   //! coordinates.
584   /*!
585      Bear in mind when implementing this, that, for example, NH2 will appear as
586      NH<sub>2</sub> to convey that the 2 is a subscript, and this needs to
587      accounted for in the width and height.
588    */
589   virtual void getStringSize(const std::string &label, double &label_width,
590                              double &label_height) const;
591   // get the overall size of the label, allowing for it being split
592   // into pieces according to orientation.
593   void getLabelSize(const std::string &label, OrientType orient,
594                     double &label_width, double &label_height) const;
595   // return extremes for string in molecule coords.
596   void getStringExtremes(const std::string &label, OrientType orient,
597                          const Point2D &cds, double &x_min, double &y_min,
598                          double &x_max, double &y_max) const;
599 
600   //! drawString centres the string on cds.
601   virtual void drawString(const std::string &str, const Point2D &cds);
602   // unless the specific drawer over-rides this overload, it will just call
603   // the first one.  SVG for one needs the alignment flag.
604   virtual void drawString(const std::string &str, const Point2D &cds,
605                           TextAlignType align);
606   //! draw a triangle
607   virtual void drawTriangle(const Point2D &cds1, const Point2D &cds2,
608                             const Point2D &cds3);
609   //! draw an ellipse
610   virtual void drawEllipse(const Point2D &cds1, const Point2D &cds2);
611   // draw the arc of a circle between ang1 and ang2.  Note that 0 is
612   // at 3 o-clock and 90 at 12 o'clock as you'd expect from your maths.
613   // ang2 must be > ang1 - it won't draw backwards.  This is not enforced.
614   // Angles in degrees.
615   virtual void drawArc(const Point2D &centre, double radius, double ang1,
616                        double ang2);
617   // and a general ellipse form
618   virtual void drawArc(const Point2D &centre, double xradius, double yradius,
619                        double ang1, double ang2);
620   //! draw a rectangle
621   virtual void drawRect(const Point2D &cds1, const Point2D &cds2);
622   //! draw a line indicating the presence of an attachment point (normally a
623   //! squiggle line perpendicular to a bond)
624   virtual void drawAttachmentLine(const Point2D &cds1, const Point2D &cds2,
625                                   const DrawColour &col, double len = 1.0,
626                                   unsigned int nSegments = 16);
627   //! draw a wavy line like that used to indicate unknown stereochemistry
628   virtual void drawWavyLine(const Point2D &cds1, const Point2D &cds2,
629                             const DrawColour &col1, const DrawColour &col2,
630                             unsigned int nSegments = 16,
631                             double vertOffset = 0.05);
632   //! draw a line where the ends are different colours
633   virtual void drawLine(const Point2D &cds1, const Point2D &cds2,
634                         const DrawColour &col1, const DrawColour &col2);
635   //! adds additional information about the atoms to the output. Does not make
636   //! sense for all renderers.
tagAtoms(const ROMol & mol)637   virtual void tagAtoms(const ROMol &mol) { RDUNUSED_PARAM(mol); };
638   //! set whether or not polygons are being filled
fillPolys()639   virtual bool fillPolys() const { return fill_polys_; }
640   //! returns either or not polygons should be filled
setFillPolys(bool val)641   virtual void setFillPolys(bool val) { fill_polys_ = val; }
642 
643   //! returns our current drawing options
drawOptions()644   MolDrawOptions &drawOptions() { return options_; }
645   //! \overload
drawOptions()646   const MolDrawOptions &drawOptions() const { return options_; }
647 
648   //! returns the coordinates of the atoms of the current molecule in molecular
649   //! coordinates
atomCoords()650   const std::vector<Point2D> &atomCoords() const {
651     PRECONDITION(activeMolIdx_ >= 0, "no index");
652     return at_cds_[activeMolIdx_];
653   };
654   //! returns the atomic symbols of the current molecule
atomSyms()655   const std::vector<std::pair<std::string, OrientType>> &atomSyms() const {
656     PRECONDITION(activeMolIdx_ >= 0, "no index");
657     return atom_syms_[activeMolIdx_];
658   };
659   //! Draw an arrow with either lines or a filled head (when asPolygon is true)
660   virtual void drawArrow(const Point2D &cds1, const Point2D &cds2,
661                          bool asPolygon = false, double frac = 0.05,
662                          double angle = M_PI / 6);
663 
664   // reset to default values all the things the c'tor sets
665   void tabulaRasa();
666 
supportsAnnotations()667   virtual bool supportsAnnotations() { return true; }
668   virtual void drawAnnotation(const AnnotationType &annotation);
669 
hasActiveAtmIdx()670   bool hasActiveAtmIdx() { return activeAtmIdx1_ >= 0; }
getActiveAtmIdx1()671   int getActiveAtmIdx1() { return activeAtmIdx1_; }
getActiveAtmIdx2()672   int getActiveAtmIdx2() { return activeAtmIdx2_; }
673   void setActiveAtmIdx(int at_idx1 = -1, int at_idx2 = -1) {
674     at_idx1 = (at_idx1 < 0 ? -1 : at_idx1);
675     at_idx2 = (at_idx2 < 0 ? -1 : at_idx2);
676     if (at_idx2 >= 0 && at_idx1 < 0) {
677       std::swap(at_idx1, at_idx2);
678     }
679     activeAtmIdx1_ = at_idx1;
680     activeAtmIdx2_ = at_idx2;
681   }
682 
683  protected:
684   std::unique_ptr<DrawText> text_drawer_;
685 
686  private:
687   bool needs_scale_;
688   int width_, height_, panel_width_, panel_height_, legend_height_;
689   double scale_;
690   double x_min_, y_min_, x_range_, y_range_;
691   double x_trans_, y_trans_;
692   int x_offset_, y_offset_;  // translation in screen coordinates
693   bool fill_polys_;
694   int activeMolIdx_;
695   int activeAtmIdx1_;
696   int activeAtmIdx2_;
697 
698   DrawColour curr_colour_;
699   DashPattern curr_dash_;
700   MolDrawOptions options_;
701 
702   std::vector<std::vector<Point2D>> at_cds_;  // from mol
703   std::vector<std::vector<int>> atomic_nums_;
704   std::vector<std::vector<std::pair<std::string, OrientType>>> atom_syms_;
705   // by the time annotations_ are drawn, we're only ever using the trans_ member
706   // of the StringRect, but it is convenient to keep the whole thing rather than
707   // just a StringPos for the position for calculating the scale of the drawing.
708   // Went a long way down the rabbit hole before realising this, hence this
709   // note.
710   std::vector<std::vector<AnnotationType>> annotations_;
711   std::vector<std::vector<std::pair<std::shared_ptr<StringRect>, OrientType>>>
712       radicals_;
713   Point2D bbox_[2];
714   std::vector<std::vector<MolDrawShape>> pre_shapes_;
715   std::vector<std::vector<MolDrawShape>> post_shapes_;
716 
717   // return a DrawColour based on the contents of highlight_atoms or
718   // highlight_map, falling back to atomic number by default
719   DrawColour getColour(
720       int atom_idx, const std::vector<int> *highlight_atoms = nullptr,
721       const std::map<int, DrawColour> *highlight_map = nullptr);
722   DrawColour getColourByAtomicNum(int atomic_num);
723 
724   // set the system up to draw the molecule including calculating the scale.
725   std::unique_ptr<RWMol> setupDrawMolecule(
726       const ROMol &mol, const std::vector<int> *highlight_atoms,
727       const std::map<int, double> *highlight_radii, int confId, int width,
728       int height);
729   // copies of atom coords, atomic symbols etc. are stashed for convenience.
730   // these put empty collections onto the stack and pop the off when done.
731   void pushDrawDetails();
732   void popDrawDetails();
733 
734   // do the initial setup bits for drawing a molecule.
735   std::unique_ptr<RWMol> setupMoleculeDraw(
736       const ROMol &mol, const std::vector<int> *highlight_atoms,
737       const std::map<int, double> *highlight_radii, int confId = -1);
738   void setupTextDrawer();
739 
740   // if bond_colours is given, it must have an entry for every bond, and it
741   // trumps everything else.  First in pair is bonds begin atom, second is
742   // end atom.
743   void drawBonds(const ROMol &draw_mol,
744                  const std::vector<int> *highlight_atoms = nullptr,
745                  const std::map<int, DrawColour> *highlight_atom_map = nullptr,
746                  const std::vector<int> *highlight_bonds = nullptr,
747                  const std::map<int, DrawColour> *highlight_bond_map = nullptr,
748                  const std::vector<std::pair<DrawColour, DrawColour>>
749                      *bond_colours = nullptr);
750   // do the finishing touches to the drawing
751   void finishMoleculeDraw(const ROMol &draw_mol,
752                           const std::vector<DrawColour> &atom_colours);
753   void drawLegend(const std::string &legend);
754   // draw a circle in the requested colour(s) around the atom.
755   void drawHighlightedAtom(int atom_idx, const std::vector<DrawColour> &colours,
756                            const std::map<int, double> *highlight_radii);
757   // calculate the rectangle that goes round the string, taking its
758   // orientation into account.  Centre of StringRect
759   // won't be the same as label_coords, necessarily, as the string might
760   // be offset according to orient.
761   StringRect calcLabelRect(const std::string &label, OrientType orient,
762                            const Point2D &label_coords) const;
763   // calculate parameters for an ellipse that roughly goes round the label
764   // of the given atom.
765   void calcLabelEllipse(int atom_idx,
766                         const std::map<int, double> *highlight_radii,
767                         Point2D &centre, double &xradius,
768                         double &yradius) const;
769   // StringRect will have a width of -1.0 if there's a problem.
770   StringRect calcAnnotationPosition(const ROMol &mol, const Atom *atom,
771                                     const std::string &note);
772   StringRect calcAnnotationPosition(const ROMol &mol, const Bond *bond,
773                                     const std::string &note);
774   StringRect calcAnnotationPosition(const ROMol &mol, const std::string &note);
775   // find where to put the given annotation around an atom.  Starting
776   // search at angle start_ang, in degrees.
777   void calcAtomAnnotationPosition(const ROMol &mol, const Atom *atom,
778                                   double start_ang, StringRect &rect,
779                                   const std::string &note);
780 
781   // draw 1 or more coloured line along bonds
782   void drawHighlightedBonds(
783       const ROMol &mol,
784       const std::map<int, std::vector<DrawColour>> &highlight_bond_map,
785       const std::map<int, int> &highlight_linewidth_multipliers,
786       const std::map<int, double> *highlight_radii);
787   int getHighlightBondWidth(
788       int bond_idx,
789       const std::map<int, int> *highlight_linewidth_multipliers) const;
790   // move p2 so that the line defined by p1 to p2 touches the ellipse for the
791   // atom highlighted.
792   void adjustLineEndForHighlight(int at_idx,
793                                  const std::map<int, double> *highlight_radii,
794                                  Point2D p1, Point2D &p2) const;
795 
796   void extractAtomCoords(const ROMol &mol, int confId, bool updateBBox);
797   void extractAtomSymbols(const ROMol &mol);
798   void extractMolNotes(const ROMol &mol);
799   void extractAtomNotes(const ROMol &mol);
800   void extractBondNotes(const ROMol &mol);
801   void extractRadicals(const ROMol &mol);
802   void extractSGroupData(const ROMol &mol);
803   void extractVariableBonds(const ROMol &mol);
804   void extractBrackets(const ROMol &mol);
805   void extractLinkNodes(const ROMol &mol);
806 
807   void drawAtomLabel(int atom_num,
808                      const std::vector<int> *highlight_atoms = nullptr,
809                      const std::map<int, DrawColour> *highlight_map = nullptr);
810   OrientType calcRadicalRect(const ROMol &mol, const Atom *atom,
811                              StringRect &rad_rect);
812   void drawRadicals(const ROMol &mol);
813   // find a good starting point for scanning round the annotation
814   // atom.  If we choose well, the first angle should be the one.
815   // Returns angle in radians.
816   double getNoteStartAngle(const ROMol &mol, const Atom *atom) const;
817   // see if the note will clash with anything else drawn on the molecule.
818   // note_vec should have unit length.  note_rad is the radius along
819   // note_vec that the note will be drawn.
820   bool doesAtomNoteClash(StringRect &note_rect,
821                          const std::vector<std::shared_ptr<StringRect>> &rects,
822                          const ROMol &mol, unsigned int atom_idx);
823   bool doesBondNoteClash(StringRect &note_rect,
824                          const std::vector<std::shared_ptr<StringRect>> &rects,
825                          const ROMol &mol, const Bond *bond);
826   // does the note_vec form an unacceptably acute angle with one of the
827   // bonds from atom to its neighbours.
828   bool doesNoteClashNbourBonds(
829       const StringRect &note_rect,
830       const std::vector<std::shared_ptr<StringRect>> &rects, const ROMol &mol,
831       const Atom *atom) const;
832   // does the note intersect with atsym, and if not, any other atom symbol.
833   bool doesNoteClashAtomLabels(
834       const StringRect &note_rect,
835       const std::vector<std::shared_ptr<StringRect>> &rects, const ROMol &mol,
836       unsigned int atom_idx) const;
837   bool doesNoteClashOtherNotes(
838       const StringRect &note_rect,
839       const std::vector<std::shared_ptr<StringRect>> &rects) const;
840 
841   // take the coords for atnum, with neighbour nbr_cds, and move cds out to
842   // accommodate
843   // the label associated with it.
844   void adjustBondEndForLabel(const std::pair<std::string, OrientType> &lbl,
845                              const Point2D &nbr_cds, Point2D &cds) const;
846 
847   // adds LaTeX-like annotation for super- and sub-script.
848   std::pair<std::string, OrientType> getAtomSymbolAndOrientation(
849       const Atom &atom) const;
850   std::string getAtomSymbol(const Atom &atom, OrientType orientation) const;
851   OrientType getAtomOrientation(const Atom &atom) const;
852 
853   // things used by calculateScale.
854   void adjustScaleForAtomLabels(const std::vector<int> *highlight_atoms,
855                                 const std::map<int, double> *highlight_radii);
856   void adjustScaleForRadicals(const ROMol &mol);
857   void adjustScaleForAnnotation(const std::vector<AnnotationType> &notes);
858 
859  private:
updateMetadata(const ROMol & mol,int confId)860   virtual void updateMetadata(const ROMol &mol, int confId) {
861     RDUNUSED_PARAM(mol);
862     RDUNUSED_PARAM(confId);
863   };
updateMetadata(const ChemicalReaction & rxn)864   virtual void updateMetadata(const ChemicalReaction &rxn) {
865     RDUNUSED_PARAM(rxn);
866   };
867 
868  protected:
869   std::vector<std::pair<std::string, std::string>> d_metadata;
870   unsigned int d_numMetadataEntries = 0;
871 
872   virtual void doContinuousHighlighting(
873       const ROMol &mol, const std::vector<int> *highlight_atoms,
874       const std::vector<int> *highlight_bonds,
875       const std::map<int, DrawColour> *highlight_atom_map,
876       const std::map<int, DrawColour> *highlight_bond_map,
877       const std::map<int, double> *highlight_radii);
878 
879   virtual void highlightCloseContacts();
880   // if bond_colours is given, it must have an entry for every bond, and it
881   // trumps everything else.  First in pair is bonds begin atom, second is
882   // end atom.
883   virtual void drawBond(
884       const ROMol &mol, const Bond *bond, int at1_idx, int at2_idx,
885       const std::vector<int> *highlight_atoms = nullptr,
886       const std::map<int, DrawColour> *highlight_atom_map = nullptr,
887       const std::vector<int> *highlight_bonds = nullptr,
888       const std::map<int, DrawColour> *highlight_bond_map = nullptr,
889       const std::vector<std::pair<DrawColour, DrawColour>> *bond_colours =
890           nullptr);
891   virtual void drawAtomLabel(int atom_num, const DrawColour &draw_colour);
892   //! DEPRECATED
drawAnnotation(const std::string & note,const StringRect & note_rect)893   virtual void drawAnnotation(const std::string &note,
894                               const StringRect &note_rect) {
895     AnnotationType annot;
896     annot.text_ = note;
897     annot.rect_ = note_rect;
898     drawAnnotation(annot);
899   }
900 
901   // calculate the width to draw a line in draw coords.
902   virtual double getDrawLineWidth() const;
903 
904   // sort out coords and scale for drawing reactions.
905   void get2DCoordsForReaction(ChemicalReaction &rxn, Point2D &arrowBegin,
906                               Point2D &arrowEnd, std::vector<double> &plusLocs,
907                               double spacing, const std::vector<int> *confIds);
908   // despite the name, this is only ever used for molecules in a reaction.
909   void get2DCoordsMol(RWMol &mol, double &offset, double spacing, double &maxY,
910                       double &minY, int confId, bool shiftAgents,
911                       double coordScale);
912 };
913 
914 // return true if the line l1s->l1f intersects line l2s->l2f.  If ip is not
915 // nullptr, the intersection point is stored in it.
916 RDKIT_MOLDRAW2D_EXPORT bool doLinesIntersect(const Point2D &l1s,
917                                              const Point2D &l1f,
918                                              const Point2D &l2s,
919                                              const Point2D &l2f,
920                                              Point2D *ip = nullptr);
921 // return true if line ls->lf intersects (or is fully inside) the
922 // rectangle of the string.
923 RDKIT_MOLDRAW2D_EXPORT bool doesLineIntersectLabel(const Point2D &ls,
924                                                    const Point2D &lf,
925                                                    const StringRect &lab_rect,
926                                                    double padding = 0.0);
927 
928 }  // namespace RDKit
929 
930 #endif  // RDKITMOLDRAW2D_H
931