1 //
2 //  Copyright (C) 2019-2020 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 #include "catch.hpp"
11 
12 #include <GraphMol/RDKitBase.h>
13 
14 #include <GraphMol/SmilesParse/SmilesParse.h>
15 #include <GraphMol/MolDraw2D/MolDraw2D.h>
16 #include <GraphMol/MolDraw2D/MolDraw2DSVG.h>
17 #include <GraphMol/MolDraw2D/MolDraw2DUtils.h>
18 #include <GraphMol/MolDraw2D/MolDraw2DDetails.h>
19 #include <GraphMol/FileParsers/FileParsers.h>
20 #include <GraphMol/FileParsers/PNGParser.h>
21 #include <boost/algorithm/string/split.hpp>
22 #include <GraphMol/ChemReactions/Reaction.h>
23 #include <GraphMol/ChemReactions/ReactionParser.h>
24 #include <GraphMol/CIPLabeler/CIPLabeler.h>
25 #include <GraphMol/Depictor/RDDepictor.h>
26 #include <regex>
27 
28 #ifdef RDK_BUILD_CAIRO_SUPPORT
29 #include <cairo.h>
30 #include "MolDraw2DCairo.h"
31 #endif
32 
33 // a lot of the tests check <text> flags in the SVG.  That doesn't
34 // happen with the Freetype versions
35 static const bool NO_FREETYPE = true;
36 
37 using namespace RDKit;
38 
39 TEST_CASE("prepareAndDrawMolecule", "[drawing]") {
40   SECTION("basics") {
41     auto m1 = "C1N[C@@H]2OCC12"_smiles;
42     REQUIRE(m1);
43 
44     // we will be able to recognize that the prep worked because there
45     // will be an H in the output:
46     MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
47     MolDraw2DUtils::prepareAndDrawMolecule(drawer, *m1);
48     drawer.finishDrawing();
49     std::string text = drawer.getDrawingText();
50     CHECK(text.find(">H</text>") != std::string::npos);
51   }
52 }
53 
54 TEST_CASE("tag atoms in SVG", "[drawing][SVG]") {
55   SECTION("basics") {
56     auto m1 = "C1N[C@@H]2OCC12"_smiles;
57     REQUIRE(m1);
58 
59     MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
60     MolDraw2DUtils::prepareMolForDrawing(*m1);
61     drawer.drawMolecule(*m1);
62     std::map<std::string, std::string> actions;
63     actions["onclick"] = "alert";
64     double radius = 0.2;
65     drawer.tagAtoms(*m1, radius, actions);
66     drawer.finishDrawing();
67     std::string text = drawer.getDrawingText();
68     std::ofstream outs("testAtomTags_1.svg");
69     outs << text;
70     outs.flush();
71 
72     CHECK(text.find("<circle") != std::string::npos);
73     CHECK(text.find("<circle") != std::string::npos);
74     CHECK(text.find("atom-selector") != std::string::npos);
75     CHECK(text.find("bond-selector") != std::string::npos);
76   }
77   SECTION("inject prop to class") {
78     auto m1 = "C1N[C@@H]2OCC12"_smiles;
79     REQUIRE(m1);
80 
81     for (auto atom : m1->atoms()) {
82       auto prop = boost::format("__prop_class_atom_%d") % atom->getIdx();
83       atom->setProp("_tagClass", prop.str());
84     }
85     for (auto bond : m1->bonds()) {
86       auto prop = boost::format("__prop_class_bond_%d") % bond->getIdx();
87       bond->setProp("_tagClass", prop.str());
88     }
89 
90     MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
91     MolDraw2DUtils::prepareMolForDrawing(*m1);
92     drawer.drawMolecule(*m1);
93     drawer.tagAtoms(*m1);
94     drawer.finishDrawing();
95     std::string text = drawer.getDrawingText();
96     std::ofstream outs("testAtomTags_2.svg");
97     outs << text;
98     outs.flush();
99 
100     size_t i = 0;
101     size_t c = 0;
102     while (true) {
103       auto i2 = text.find("__prop_class_atom_", i);
104       if (i2 == std::string::npos) {
105         break;
106       }
107       i = i2 + 1;
108       c++;
109     }
110     CHECK(c == 6);
111 
112     i = 0;
113     c = 0;
114     while (true) {
115       auto i2 = text.find("__prop_class_bond_", i);
116       if (i2 == std::string::npos) {
117         break;
118       }
119       i = i2 + 1;
120       c++;
121     }
122     CHECK(c == 7);
123   }
124 }
125 
126 TEST_CASE("metadata in SVG", "[drawing][SVG]") {
127   SECTION("inject prop to metada") {
128     auto m1 = "C1N[C@@H]2OCC12"_smiles;
129     REQUIRE(m1);
130 
131     for (auto atom : m1->atoms()) {
132       auto prop = boost::format("__prop_metadata_atom_%d") % atom->getIdx();
133       atom->setProp("_metaData-atom-inject-prop", prop.str());
134     }
135     for (auto bond : m1->bonds()) {
136       auto prop = boost::format("__prop_metadata_bond_%d") % bond->getIdx();
137       bond->setProp("_metaData-bond-inject-prop", prop.str());
138     }
139 
140     MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
141     MolDraw2DUtils::prepareMolForDrawing(*m1);
142     drawer.drawMolecule(*m1);
143     drawer.addMoleculeMetadata(*m1);
144     drawer.finishDrawing();
145     std::string text = drawer.getDrawingText();
146     std::ofstream outs("testAtomTags_2.svg");
147     outs << text;
148     outs.flush();
149 
150     size_t i = 0;
151     size_t c = 0;
152     while (true) {
153       auto i2 = text.find("atom-inject-prop=\"__prop_metadata_atom_", i);
154       if (i2 == std::string::npos) {
155         break;
156       }
157       i = i2 + 1;
158       c++;
159     }
160     CHECK(c == 6);
161 
162     i = 0;
163     c = 0;
164     while (true) {
165       auto i2 = text.find("bond-inject-prop=\"__prop_metadata_bond_", i);
166       if (i2 == std::string::npos) {
167         break;
168       }
169       i = i2 + 1;
170       c++;
171     }
172     CHECK(c == 7);
173   }
174 }
175 
176 TEST_CASE("contour data", "[drawing][conrec]") {
177   auto m1 = "C1N[C@@H]2OCC12"_smiles;
178   REQUIRE(m1);
179   SECTION("grid basics") {
180     MolDraw2DSVG drawer(250, 250, -1, -1, NO_FREETYPE);
181     MolDraw2DUtils::prepareMolForDrawing(*m1);
182 
183     const size_t gridSz = 100;
184     auto *grid = new double[gridSz * gridSz];
185     std::vector<double> xps(gridSz);
186     std::vector<double> yps(gridSz);
187 
188     double minX = 1000, minY = 1000, maxX = -1000, maxY = -1000;
189     const auto conf = m1->getConformer();
190     for (size_t i = 0; i < conf.getNumAtoms(); ++i) {
191       minX = std::min(minX, conf.getAtomPos(i).x);
192       minY = std::min(minY, conf.getAtomPos(i).y);
193       maxX = std::max(maxX, conf.getAtomPos(i).x);
194       maxY = std::max(maxY, conf.getAtomPos(i).y);
195     }
196     double x1 = minX - 0.5, y1 = minY - 0.5, x2 = maxX + 0.5, y2 = maxY + 0.5;
197     double dx = (x2 - x1) / gridSz, dy = (y2 - y1) / gridSz;
198     double maxV = 0.0;
199     for (size_t ix = 0; ix < gridSz; ++ix) {
200       auto px = x1 + ix * dx;
201       xps[ix] = px;
202       for (size_t iy = 0; iy < gridSz; ++iy) {
203         auto py = y1 + iy * dy;
204         if (ix == 0) {
205           yps[iy] = py;
206         }
207         RDGeom::Point2D loc(px, py);
208         double val = 0.0;
209         for (size_t ia = 0; ia < conf.getNumAtoms(); ++ia) {
210           auto dv = loc - RDGeom::Point2D(conf.getAtomPos(ia).x,
211                                           conf.getAtomPos(ia).y);
212           auto r = dv.length();
213           if (r > 0.1) {
214             val += 1 / r;
215           }
216         }
217         maxV = std::max(val, maxV);
218         grid[ix * gridSz + iy] = val;
219       }
220     }
221 
222     std::vector<double> levels;
223     drawer.clearDrawing();
224     MolDraw2DUtils::contourAndDrawGrid(drawer, grid, xps, yps, 10, levels,
225                                        MolDraw2DUtils::ContourParams(),
226                                        m1.get());
227     drawer.drawOptions().clearBackground = false;
228     drawer.drawMolecule(*m1);
229     drawer.finishDrawing();
230     std::string text = drawer.getDrawingText();
231     std::ofstream outs("contourMol_1.svg");
232     outs << text;
233     outs.flush();
234     delete[] grid;
235   }
236   SECTION("gaussian basics") {
237     MolDraw2DSVG drawer(250, 250, -1, -1, NO_FREETYPE);
238     MolDraw2DUtils::prepareMolForDrawing(*m1);
239     drawer.drawOptions().padding = 0.1;
240 
241     const auto conf = m1->getConformer();
242     std::vector<Point2D> cents(conf.getNumAtoms());
243     std::vector<double> weights(conf.getNumAtoms());
244     std::vector<double> widths(conf.getNumAtoms());
245     for (size_t i = 0; i < conf.getNumAtoms(); ++i) {
246       cents[i] = Point2D(conf.getAtomPos(i).x, conf.getAtomPos(i).y);
247       weights[i] = 1;
248       widths[i] = 0.4 * PeriodicTable::getTable()->getRcovalent(
249                             m1->getAtomWithIdx(i)->getAtomicNum());
250     }
251 
252     std::vector<double> levels;
253     drawer.clearDrawing();
254     MolDraw2DUtils::contourAndDrawGaussians(
255         drawer, cents, weights, widths, 10, levels,
256         MolDraw2DUtils::ContourParams(), m1.get());
257 
258     drawer.drawOptions().clearBackground = false;
259     drawer.drawMolecule(*m1);
260     drawer.finishDrawing();
261     std::string text = drawer.getDrawingText();
262     std::ofstream outs("contourMol_2.svg");
263     outs << text;
264     outs.flush();
265   }
266   SECTION("gaussian fill") {
267     MolDraw2DSVG drawer(250, 250, -1, -1, NO_FREETYPE);
268     MolDraw2DUtils::prepareMolForDrawing(*m1);
269     drawer.drawOptions().padding = 0.1;
270 
271     const auto conf = m1->getConformer();
272     std::vector<Point2D> cents(conf.getNumAtoms());
273     std::vector<double> weights(conf.getNumAtoms());
274     std::vector<double> widths(conf.getNumAtoms());
275     for (size_t i = 0; i < conf.getNumAtoms(); ++i) {
276       cents[i] = Point2D(conf.getAtomPos(i).x, conf.getAtomPos(i).y);
277       weights[i] = i % 2 ? -0.5 : 1;
278       widths[i] = 0.4 * PeriodicTable::getTable()->getRcovalent(
279                             m1->getAtomWithIdx(i)->getAtomicNum());
280     }
281 
282     std::vector<double> levels;
283     MolDraw2DUtils::ContourParams cps;
284     cps.fillGrid = true;
285     drawer.clearDrawing();
286     MolDraw2DUtils::contourAndDrawGaussians(drawer, cents, weights, widths, 10,
287                                             levels, cps, m1.get());
288 
289     drawer.drawOptions().clearBackground = false;
290     drawer.drawMolecule(*m1);
291     drawer.finishDrawing();
292     std::string text = drawer.getDrawingText();
293     std::ofstream outs("contourMol_3.svg");
294     outs << text;
295     outs.flush();
296   }
297 
298   SECTION("gaussian fill 2") {
299     auto m2 = "C1N[C@@H]2OCC12C=CC"_smiles;
300     REQUIRE(m2);
301 
302     MolDraw2DSVG drawer(450, 250, -1, -1, NO_FREETYPE);
303     MolDraw2DUtils::prepareMolForDrawing(*m2);
304     drawer.drawOptions().padding = 0.1;
305 
306     const auto conf = m2->getConformer();
307     std::vector<Point2D> cents(conf.getNumAtoms());
308     std::vector<double> weights(conf.getNumAtoms());
309     std::vector<double> widths(conf.getNumAtoms());
310     for (size_t i = 0; i < conf.getNumAtoms(); ++i) {
311       cents[i] = Point2D(conf.getAtomPos(i).x, conf.getAtomPos(i).y);
312       weights[i] = i % 2 ? -0.5 : 1;
313       widths[i] = 0.3 * PeriodicTable::getTable()->getRcovalent(
314                             m2->getAtomWithIdx(i)->getAtomicNum());
315     }
316 
317     std::vector<double> levels;
318     MolDraw2DUtils::ContourParams cps;
319     cps.fillGrid = true;
320     cps.gridResolution = 0.5;
321     drawer.clearDrawing();
322     MolDraw2DUtils::contourAndDrawGaussians(drawer, cents, weights, widths, 10,
323                                             levels, cps, m2.get());
324 
325     drawer.drawOptions().clearBackground = false;
326     drawer.drawMolecule(*m2);
327     drawer.finishDrawing();
328     std::string text = drawer.getDrawingText();
329     std::ofstream outs("contourMol_4.svg");
330     outs << text;
331     outs.flush();
332   }
333 }
334 
335 TEST_CASE("dative bonds", "[drawing][organometallics]") {
336   SECTION("basics") {
337     auto m1 = "N->[Pt]"_smiles;
338     REQUIRE(m1);
339     MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
340     MolDraw2DUtils::prepareMolForDrawing(*m1);
341     drawer.drawMolecule(*m1);
342     drawer.finishDrawing();
343     std::string text = drawer.getDrawingText();
344     std::ofstream outs("testDativeBonds_1.svg");
345     outs << text;
346     outs.flush();
347 
348     CHECK(
349         text.find(
350             "<path class='bond-0 atom-0 atom-1' d='M 126.052,100 L 85.9675,100'"
351             " style='fill:none;fill-rule:evenodd;"
352             "stroke:#0000FF;") != std::string::npos);
353   }
354   SECTION("more complex") {
355     auto m1 = "N->1[C@@H]2CCCC[C@H]2N->[Pt]11OC(=O)C(=O)O1"_smiles;
356     REQUIRE(m1);
357     MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
358     MolDraw2DUtils::prepareMolForDrawing(*m1);
359     drawer.drawMolecule(*m1);
360     drawer.finishDrawing();
361     std::string text = drawer.getDrawingText();
362     std::ofstream outs("testDativeBonds_2.svg");
363     outs << text;
364     outs.flush();
365 
366     CHECK(text.find("<path class='bond-7 atom-7 atom-8' d='M 101.307,79.424 "
367                     "L 95.669,87.1848' style='fill:none;"
368                     "fill-rule:evenodd;stroke:#0000FF;") != std::string::npos);
369   }
370   SECTION("test colours") {
371     // the dative bonds point the wrong way, but the point is to test
372     // if the tip of the arrow is blue.
373     auto m1 = "[Cu++]->1->2.N1CCN2"_smiles;
374     REQUIRE(m1);
375     MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
376     MolDraw2DUtils::prepareMolForDrawing(*m1);
377     drawer.drawMolecule(*m1);
378     drawer.finishDrawing();
379     std::string text = drawer.getDrawingText();
380     std::ofstream outs("testDativeBonds_3.svg");
381     outs << text;
382     outs.flush();
383 
384     CHECK(text.find("<path class='bond-2 atom-3 atom-4' d='M 53.289,140.668"
385                     " L 81.0244,149.68' style='fill:none;"
386                     "fill-rule:evenodd;stroke:#0000FF;") != std::string::npos);
387   }
388   SECTION("dative series") {
389     auto m1 = "N->1[C@@H]2CCCC[C@H]2N->[Pt]11OC(=O)C(=O)O1"_smiles;
390     REQUIRE(m1);
391     {
392       MolDraw2DSVG drawer(150, 150, -1, -1, NO_FREETYPE);
393       MolDraw2DUtils::prepareMolForDrawing(*m1);
394       drawer.drawMolecule(*m1);
395       drawer.finishDrawing();
396       std::string text = drawer.getDrawingText();
397       std::ofstream outs("testDativeBonds_2a.svg");
398       outs << text;
399       outs.flush();
400     }
401     {
402       MolDraw2DSVG drawer(250, 250, -1, -1, NO_FREETYPE);
403       MolDraw2DUtils::prepareMolForDrawing(*m1);
404       drawer.drawMolecule(*m1);
405       drawer.finishDrawing();
406       std::string text = drawer.getDrawingText();
407       std::ofstream outs("testDativeBonds_2b.svg");
408       outs << text;
409       outs.flush();
410     }
411     {
412       MolDraw2DSVG drawer(350, 350, -1, -1, NO_FREETYPE);
413       MolDraw2DUtils::prepareMolForDrawing(*m1);
414       drawer.drawMolecule(*m1);
415       drawer.finishDrawing();
416       std::string text = drawer.getDrawingText();
417       std::ofstream outs("testDativeBonds_2c.svg");
418       outs << text;
419       outs.flush();
420     }
421     {
422       MolDraw2DSVG drawer(450, 450, -1, -1, NO_FREETYPE);
423       MolDraw2DUtils::prepareMolForDrawing(*m1);
424       drawer.drawMolecule(*m1);
425       drawer.finishDrawing();
426       std::string text = drawer.getDrawingText();
427       std::ofstream outs("testDativeBonds_2d.svg");
428       outs << text;
429       outs.flush();
430     }
431   }
432 }
433 
434 TEST_CASE("zero-order bonds", "[drawing][organometallics]") {
435   SECTION("basics") {
436     auto m1 = "N-[Pt]"_smiles;
437     REQUIRE(m1);
438     m1->getBondWithIdx(0)->setBondType(Bond::ZERO);
439     MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
440     MolDraw2DUtils::prepareMolForDrawing(*m1);
441     drawer.drawMolecule(*m1);
442     drawer.finishDrawing();
443     std::string text = drawer.getDrawingText();
444     std::ofstream outs("testZeroOrderBonds_1.svg");
445     outs << text;
446     outs.flush();
447 
448     CHECK(text.find("stroke-dasharray:2,2") != std::string::npos);
449   }
450 }
451 
452 TEST_CASE("copying drawing options", "[drawing]") {
453   auto m1 = "C1N[C@@H]2OCC12"_smiles;
454   REQUIRE(m1);
455   SECTION("foundations") {
456     {
457       MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
458       MolDraw2DUtils::prepareAndDrawMolecule(drawer, *m1);
459       drawer.finishDrawing();
460       std::string text = drawer.getDrawingText();
461       std::ofstream outs("testFoundations_1.svg");
462       outs << text;
463       outs.flush();
464       CHECK(text.find("fill:#0000FF' >N</text>") != std::string::npos);
465     }
466     {
467       MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
468       assignBWPalette(drawer.drawOptions().atomColourPalette);
469       MolDraw2DUtils::prepareAndDrawMolecule(drawer, *m1);
470       drawer.finishDrawing();
471       std::string text = drawer.getDrawingText();
472       std::ofstream outs("testFoundations_2.svg");
473       outs << text;
474       outs.flush();
475       CHECK(text.find("fill:#0000FF' >N</text>") == std::string::npos);
476       CHECK(text.find("fill:#000000' >N</text>") != std::string::npos);
477     }
478   }
479   SECTION("test") {
480     {
481       MolDraw2DSVG drawer(200, 200, -1, -1, NO_FREETYPE);
482       MolDrawOptions options = drawer.drawOptions();
483       assignBWPalette(options.atomColourPalette);
484       drawer.drawOptions() = options;
485       MolDraw2DUtils::prepareAndDrawMolecule(drawer, *m1);
486       drawer.finishDrawing();
487       std::string text = drawer.getDrawingText();
488       std::ofstream outs("testTest_1.svg");
489       outs << text;
490       outs.flush();
491       CHECK(text.find("fill:#0000FF' >N</text>") == std::string::npos);
492       CHECK(text.find("fill:#000000' >N</text>") != std::string::npos);
493     }
494   }
495 }
496 
497 TEST_CASE("bad DrawMolecules() when molecules are not kekulized",
498           "[drawing][bug]") {
499   auto m1 = "CCN(CC)CCn1nc2c3ccccc3sc3c(CNS(C)(=O)=O)ccc1c32"_smiles;
500   REQUIRE(m1);
501   SECTION("foundations") {
502     MolDraw2DSVG drawer(500, 200, 250, 200, NO_FREETYPE);
503     drawer.drawOptions().prepareMolsBeforeDrawing = false;
504     RWMol dm1(*m1);
505     RWMol dm2(*m1);
506     bool kekulize = false;
507     MolDraw2DUtils::prepareMolForDrawing(dm1, kekulize);
508     kekulize = true;
509     MolDraw2DUtils::prepareMolForDrawing(dm2, kekulize);
510     MOL_PTR_VECT ms{&dm1, &dm2};
511     drawer.drawMolecule(dm1);
512     drawer.finishDrawing();
513     std::string text = drawer.getDrawingText();
514     std::ofstream outs("testKekulizationProblems_1.svg");
515     outs << text;
516     outs.flush();
517 
518     // this is a very crude test - really we just need to look at the SVG - but
519     // it's better than nothing.
520     CHECK(text.find(
521               "<path class='bond-18' d='M 169.076,79.056 L 191.285,69.2653' "
522               "style='fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:"
523               "2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
524               "stroke-dasharray:6,6' />") == std::string::npos);
525   }
526 }
527 TEST_CASE("draw atom/bond indices", "[drawing]") {
528   auto m1 = "C[C@H](F)N"_smiles;
529   REQUIRE(m1);
530   SECTION("foundations") {
531     {
532       MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE);
533       drawer.drawMolecule(*m1);
534       drawer.finishDrawing();
535       std::string text = drawer.getDrawingText();
536       std::ofstream outs("testAtomBondIndices_1.svg");
537       outs << text;
538       outs.flush();
539       CHECK(text.find(">1</text>") == std::string::npos);
540       CHECK(text.find(">(</text>") == std::string::npos);
541       CHECK(text.find(">S</text>") == std::string::npos);
542       CHECK(text.find(">)</text>") == std::string::npos);
543     }
544     {
545       MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE);
546       drawer.drawOptions().addAtomIndices = true;
547       drawer.drawMolecule(*m1);
548       drawer.finishDrawing();
549       std::string text = drawer.getDrawingText();
550       std::ofstream outs("testAtomBondIndices_2.svg");
551       outs << text;
552       outs.flush();
553       CHECK(text.find(">1</text>") != std::string::npos);
554       // it only appears once though:
555       CHECK(text.find(">1</text>", text.find(">1</text>") + 1) ==
556             std::string::npos);
557       CHECK(text.find("1,(S)") == std::string::npos);
558     }
559     {
560       MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE);
561       drawer.drawOptions().addBondIndices = true;
562       drawer.drawMolecule(*m1);
563       drawer.finishDrawing();
564       std::string text = drawer.getDrawingText();
565       std::ofstream outs("testAtomBondIndices_3.svg");
566       outs << text;
567       outs.flush();
568       CHECK(text.find(">1</text>") != std::string::npos);
569       // it only appears once though:
570       CHECK(text.find(">1</text>", text.find(">1</text>") + 1) ==
571             std::string::npos);
572     }
573     {
574       MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE);
575       drawer.drawOptions().addAtomIndices = true;
576       drawer.drawOptions().addBondIndices = true;
577       drawer.drawMolecule(*m1);
578       drawer.finishDrawing();
579       std::string text = drawer.getDrawingText();
580       std::ofstream outs("testAtomBondIndices_4.svg");
581       outs << text;
582       outs.flush();
583       CHECK(text.find(">1</text>") != std::string::npos);
584       // it appears twice:
585       CHECK(text.find(">1</text>", text.find(">1</text>") + 1) !=
586             std::string::npos);
587     }
588     {
589       MolDraw2DSVG drawer(250, 200, -1, -1, NO_FREETYPE);
590       m1->getAtomWithIdx(2)->setProp(common_properties::atomNote, "foo");
591       drawer.drawOptions().addAtomIndices = true;
592       drawer.drawOptions().addStereoAnnotation = true;
593       drawer.drawMolecule(*m1);
594       m1->getAtomWithIdx(2)->clearProp(common_properties::atomNote);
595       drawer.finishDrawing();
596       std::string text = drawer.getDrawingText();
597       std::ofstream outs("testAtomBondIndices_5.svg");
598       outs << text;
599       outs.flush();
600       CHECK(text.find(">1</text>") != std::string::npos);
601       CHECK(text.find(">,</text>") != std::string::npos);
602       CHECK(text.find(">(</text>") != std::string::npos);
603       CHECK(text.find(">S</text>") != std::string::npos);
604       CHECK(text.find(")</text>") != std::string::npos);
605       CHECK(text.find(">2</text>") != std::string::npos);
606       CHECK(text.find(">f</text>") != std::string::npos);
607       CHECK(text.find(">o</text>") != std::string::npos);
608     }
609   }
610 }
611 
612 TEST_CASE("Github #3226: Lines in wedge bonds being drawn too closely together",
613           "[drawing]") {
614   auto m1 =
615       "C[C@H](C1=C(C=CC(=C1Cl)F)Cl)OC2=C(N=CC(=C2)C3=CN(N=C3)C4CCNCC4)N"_smiles;
616   REQUIRE(m1);
617   SECTION("larger SVG") {
618     {
619       MolDraw2DSVG drawer(450, 400);
620       drawer.drawMolecule(*m1);
621       drawer.finishDrawing();
622       std::string text = drawer.getDrawingText();
623       std::ofstream outs("testGithub3226_1.svg");
624       outs << text;
625       outs.flush();
626       std::vector<std::string> tkns;
627       boost::algorithm::find_all(tkns, text, "bond-0");
628       CHECK(tkns.size() == 6);
629     }
630   }
631 #ifdef RDK_BUILD_CAIRO_SUPPORT
632   SECTION("larger PNG") {
633     {
634       MolDraw2DCairo drawer(450, 400);
635       drawer.drawMolecule(*m1);
636       drawer.finishDrawing();
637       drawer.writeDrawingText("testGithub3226_1.png");
638     }
639   }
640 #endif
641   SECTION("smaller SVG") {
642     {
643       MolDraw2DSVG drawer(200, 150);
644       drawer.drawMolecule(*m1);
645       drawer.finishDrawing();
646       std::string text = drawer.getDrawingText();
647       std::ofstream outs("testGithub3226_2.svg");
648       outs << text;
649       outs.flush();
650       std::vector<std::string> tkns;
651       boost::algorithm::find_all(tkns, text, "bond-0");
652       CHECK(tkns.size() == 4);
653     }
654   }
655 #ifdef RDK_BUILD_CAIRO_SUPPORT
656   SECTION("smaller PNG") {
657     {
658       MolDraw2DCairo drawer(200, 150);
659       drawer.drawMolecule(*m1);
660       drawer.finishDrawing();
661       drawer.writeDrawingText("testGithub3226_2.png");
662     }
663   }
664 #endif
665   SECTION("middle SVG") {
666     {
667       MolDraw2DSVG drawer(250, 200);
668       drawer.drawMolecule(*m1);
669       drawer.finishDrawing();
670       std::string text = drawer.getDrawingText();
671       std::ofstream outs("testGithub3226_3.svg");
672       outs << text;
673       outs.flush();
674       std::vector<std::string> tkns;
675       boost::algorithm::find_all(tkns, text, "bond-0");
676       CHECK(tkns.size() == 4);
677     }
678   }
679 #ifdef RDK_BUILD_CAIRO_SUPPORT
680   SECTION("middle PNG") {
681     {
682       MolDraw2DCairo drawer(250, 200);
683       drawer.drawMolecule(*m1);
684       drawer.finishDrawing();
685       drawer.writeDrawingText("testGithub3226_3.png");
686     }
687   }
688 #endif
689 }
690 
691 TEST_CASE("github #3258: ", "[drawing][bug]") {
692   auto m1 = "CCN"_smiles;
693   REQUIRE(m1);
694   SECTION("foundations") {
695     MolDraw2DSVG drawer(500, 200, 250, 200, NO_FREETYPE);
696     drawer.drawOptions().addAtomIndices = true;
697     drawer.drawOptions().addBondIndices = true;
698     RWMol dm1(*m1);
699     RWMol dm2(*m1);
700     MOL_PTR_VECT ms{&dm1, &dm2};
701     drawer.drawMolecules(ms);
702     drawer.finishDrawing();
703     std::string text = drawer.getDrawingText();
704     CHECK(text.find(">,</text>") == std::string::npos);
705     CHECK(!dm1.hasProp("_atomIndicesAdded"));
706     CHECK(!dm1.hasProp("_bondIndicesAdded"));
707   }
708 }
709 
710 #ifdef RDK_BUILD_CAIRO_SUPPORT
711 TEST_CASE("adding png metadata", "[drawing][png]") {
712   SECTION("molecule") {
713     auto m1 = R"CTAB(
714   Mrv2014 08172015242D
715 
716   0  0  0     0  0            999 V3000
717 M  V30 BEGIN CTAB
718 M  V30 COUNTS 3 2 0 0 0
719 M  V30 BEGIN ATOM
720 M  V30 1 C 2.31 -1.3337 0 0
721 M  V30 2 C 3.6437 -2.1037 0 0
722 M  V30 3 O 4.9774 -1.3337 0 0
723 M  V30 END ATOM
724 M  V30 BEGIN BOND
725 M  V30 1 1 1 2
726 M  V30 2 1 2 3
727 M  V30 END BOND
728 M  V30 END CTAB
729 M  END
730 )CTAB"_ctab;
731     REQUIRE(m1);
732     {
733       MolDraw2DCairo drawer(250, 200);
734       drawer.drawMolecule(*m1);
735       drawer.finishDrawing();
736       auto png = drawer.getDrawingText();
737       drawer.writeDrawingText("testPNGMetadata_1.png");
738       CHECK(png.find(PNGData::smilesTag) != std::string::npos);
739       CHECK(png.find(PNGData::molTag) != std::string::npos);
740       CHECK(png.find(PNGData::pklTag) != std::string::npos);
741       std::unique_ptr<ROMol> newmol(PNGStringToMol(png));
742       REQUIRE(newmol);
743       CHECK(MolToCXSmiles(*m1) == MolToCXSmiles(*newmol));
744     }
745     {  // disable metadata output
746       MolDraw2DCairo drawer(250, 200);
747       drawer.drawOptions().includeMetadata = false;
748       drawer.drawMolecule(*m1);
749       drawer.finishDrawing();
750       auto png = drawer.getDrawingText();
751       CHECK(png.find(PNGData::smilesTag) == std::string::npos);
752       CHECK(png.find(PNGData::molTag) == std::string::npos);
753       CHECK(png.find(PNGData::pklTag) == std::string::npos);
754     }
755     {  // draw multiple molecules
756       MolDraw2DCairo drawer(250, 200);
757       drawer.drawMolecule(*m1);
758       drawer.drawMolecule(*m1);
759       drawer.finishDrawing();
760       auto png = drawer.getDrawingText();
761       CHECK(png.find(PNGData::smilesTag) != std::string::npos);
762       CHECK(png.find(PNGData::molTag) != std::string::npos);
763       CHECK(png.find(PNGData::pklTag) != std::string::npos);
764       CHECK(png.find(PNGData::smilesTag + "1") != std::string::npos);
765       CHECK(png.find(PNGData::molTag + "1") != std::string::npos);
766       CHECK(png.find(PNGData::pklTag + "1") != std::string::npos);
767     }
768   }
769   SECTION("reaction") {
770     std::unique_ptr<ChemicalReaction> rxn(RxnSmartsToChemicalReaction(
771         "[N:1][C:2][C:3](=[O:4])[O:5].[N:6][C:7][C:8](=[O:9])[O:10]>>[N:1]1[C:"
772         "2][C:3](=[O:4])[N:6][C:7][C:8]1=[O:9].[O:5][O:10]"));
773     REQUIRE(rxn);
774     {
775       MolDraw2DCairo drawer(600, 200);
776       drawer.drawReaction(*rxn);
777       drawer.finishDrawing();
778       auto png = drawer.getDrawingText();
779       drawer.writeDrawingText("testPNGMetadata_2.png");
780       CHECK(png.find(PNGData::smilesTag) == std::string::npos);
781       CHECK(png.find(PNGData::molTag) == std::string::npos);
782       CHECK(png.find(PNGData::pklTag) == std::string::npos);
783       CHECK(png.find(PNGData::rxnPklTag) != std::string::npos);
784       CHECK(png.find(PNGData::rxnSmartsTag) != std::string::npos);
785       std::unique_ptr<ChemicalReaction> rxn2(PNGStringToChemicalReaction(png));
786       REQUIRE(rxn2);
787       CHECK(ChemicalReactionToRxnSmarts(*rxn) ==
788             ChemicalReactionToRxnSmarts(*rxn2));
789     }
790     {  // disable metadata
791       MolDraw2DCairo drawer(600, 200);
792       drawer.drawOptions().includeMetadata = false;
793       drawer.drawReaction(*rxn);
794       drawer.finishDrawing();
795       auto png = drawer.getDrawingText();
796       CHECK(png.find(PNGData::smilesTag) == std::string::npos);
797       CHECK(png.find(PNGData::molTag) == std::string::npos);
798       CHECK(png.find(PNGData::pklTag) == std::string::npos);
799       CHECK(png.find(PNGData::rxnPklTag) == std::string::npos);
800       CHECK(png.find(PNGData::rxnSmartsTag) == std::string::npos);
801     }
802   }
803 }
804 
805 #endif
806 
807 TEST_CASE(
808     "github #3392: prepareMolForDrawing() incorrectly adds chiral Hs if no "
809     "ring info is present",
810     "[bug]") {
811   SECTION("foundations") {
812     SmilesParserParams ps;
813     ps.sanitize = false;
814     ps.removeHs = false;
815     std::unique_ptr<RWMol> m1(SmilesToMol("C[C@H](F)Cl", ps));
816     REQUIRE(m1);
817     m1->updatePropertyCache();
818     CHECK(m1->getNumAtoms() == 4);
819     const bool kekulize = false;
820     const bool addChiralHs = true;
821     MolDraw2DUtils::prepareMolForDrawing(*m1, kekulize, addChiralHs);
822     CHECK(m1->getNumAtoms() == 4);
823   }
824 }
825 
826 TEST_CASE(
827     "github #3369: support new CIP code and StereoGroups in "
828     "addStereoAnnotation()",
829     "[chirality]") {
830   auto m1 =
831       "C[C@@H]1N[C@H](C)[C@@H]([C@H](C)[C@@H]1C)C1[C@@H](C)O[C@@H](C)[C@@H](C)[C@H]1C/C=C/C |a:5,o1:1,8,o2:14,16,&1:11,18,&2:3,6,r|"_smiles;
832   REQUIRE(m1);
833   SECTION("defaults") {
834     ROMol m2(*m1);
835     MolDraw2D_detail::addStereoAnnotation(m2);
836 
837     std::string txt;
838     CHECK(m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote,
839                                                  txt));
840     CHECK(txt == "abs (S)");
841     CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote,
842                                                  txt));
843     CHECK(txt == "and4");
844   }
845   SECTION("including CIP with relative stereo") {
846     ROMol m2(*m1);
847     bool includeRelativeCIP = true;
848     MolDraw2D_detail::addStereoAnnotation(m2, includeRelativeCIP);
849 
850     std::string txt;
851     CHECK(m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote,
852                                                  txt));
853     CHECK(txt == "abs (S)");
854     CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote,
855                                                  txt));
856     CHECK(txt == "and4 (R)");
857   }
858   SECTION("new CIP labels") {
859     ROMol m2(*m1);
860     REQUIRE(m2.getBondBetweenAtoms(20, 21));
861     m2.getBondBetweenAtoms(20, 21)->setStereo(Bond::BondStereo::STEREOTRANS);
862     // initially no label is assigned since we have TRANS
863     MolDraw2D_detail::addStereoAnnotation(m2);
864     CHECK(
865         !m2.getBondBetweenAtoms(20, 21)->hasProp(common_properties::bondNote));
866 
867     CIPLabeler::assignCIPLabels(m2);
868     std::string txt;
869     CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
870         common_properties::_CIPCode, txt));
871     CHECK(txt == "E");
872     MolDraw2D_detail::addStereoAnnotation(m2);
873     CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
874         common_properties::bondNote, txt));
875     CHECK(txt == "(E)");
876   }
877   SECTION("works with the drawing code") {
878     MolDraw2DSVG drawer(300, 250);
879     RWMol dm1(*m1);
880     bool includeRelativeCIP = true;
881     MolDraw2D_detail::addStereoAnnotation(dm1, includeRelativeCIP);
882     drawer.drawMolecule(dm1);
883     drawer.finishDrawing();
884     std::string text = drawer.getDrawingText();
885     std::ofstream outs("testGithub3369_1.svg");
886     outs << text;
887     outs.flush();
888   }
889 }
890 
891 TEST_CASE("includeRadicals", "[options]") {
892   SECTION("basics") {
893     auto m = "[O][C]"_smiles;
894     REQUIRE(m);
895     int panelHeight = -1;
896     int panelWidth = -1;
897     bool noFreeType = true;
898     {
899       MolDraw2DSVG drawer(250, 200, panelWidth, panelHeight, noFreeType);
900       drawer.drawMolecule(*m);
901       drawer.finishDrawing();
902       auto text = drawer.getDrawingText();
903       std::ofstream outs("testIncludeRadicals_1a.svg");
904       outs << text;
905       outs.flush();
906       CHECK(text.find("<path d='M") != std::string::npos);
907     }
908     {
909       MolDraw2DSVG drawer(250, 200, panelWidth, panelHeight, noFreeType);
910       drawer.drawOptions().includeRadicals = false;
911       drawer.drawMolecule(*m);
912       drawer.finishDrawing();
913       auto text = drawer.getDrawingText();
914       std::ofstream outs("testIncludeRadicals_1b.svg");
915       outs << text;
916       outs.flush();
917       CHECK(text.find("<path d='M") == std::string::npos);
918     }
919   }
920 }
921 
922 TEST_CASE("including legend in drawing results in offset drawing later",
923           "[bug]") {
924   SECTION("basics") {
925     auto m = "c1ccccc1"_smiles;
926     REQUIRE(m);
927     MolDraw2DUtils::prepareMolForDrawing(*m);
928     auto &conf = m->getConformer();
929     std::vector<Point2D> polyg;
930     for (const auto &pt : conf.getPositions()) {
931       polyg.emplace_back(pt);
932     }
933     MolDraw2DSVG drawer(350, 300);
934     drawer.drawMolecule(*m, "molecule legend");
935     drawer.setFillPolys(true);
936     drawer.setColour(DrawColour(1.0, 0.3, 1.0));
937     drawer.drawPolygon(polyg);
938     drawer.finishDrawing();
939     auto text = drawer.getDrawingText();
940     std::ofstream outs("testLegendsAndDrawing-1.svg");
941     outs << text;
942     outs.flush();
943 
944     // make sure the polygon starts at a bond
945     CHECK(text.find("<path class='bond-0 atom-0 atom-1' d='M 321.962,140") !=
946           std::string::npos);
947     CHECK(text.find("<path d='M 321.962,140") != std::string::npos);
948   }
949 }
950 
951 TEST_CASE("Github #3577", "[bug]") {
952   SECTION("basics") {
953     auto m = "CCC"_smiles;
954     REQUIRE(m);
955     MolDraw2DUtils::prepareMolForDrawing(*m);
956     m->getAtomWithIdx(1)->setProp("atomNote", "CCC");
957     m->getAtomWithIdx(2)->setProp("atomNote", "ccc");
958     m->getBondWithIdx(0)->setProp("bondNote", "CCC");
959 
960     MolDraw2DSVG drawer(350, 300);
961     drawer.drawMolecule(*m);
962     drawer.finishDrawing();
963     auto text = drawer.getDrawingText();
964     std::ofstream outs("testGithub3577-1.svg");
965     outs << text;
966     outs.flush();
967   }
968 }
969 TEST_CASE("hand drawn", "[play]") {
970   SECTION("basics") {
971     auto m =
972         "CC[CH](C)[CH]1NC(=O)[CH](Cc2ccc(O)cc2)NC(=O)[CH](N)CSSC[CH](C(=O)N2CCC[CH]2C(=O)N[CH](CC(C)C)C(=O)NCC(N)=O)NC(=O)[CH](CC(N)=O)NC(=O)[CH](CCC(N)=O)NC1=O"_smiles;
973     REQUIRE(m);
974     RDDepict::preferCoordGen = true;
975     MolDraw2DUtils::prepareMolForDrawing(*m);
976 
977     std::string fName = getenv("RDBASE");
978     fName += "/Data/Fonts/ComicNeue-Regular.ttf";
979 
980     {
981       MolDraw2DSVG drawer(450, 400);
982       drawer.drawOptions().fontFile = fName;
983       drawer.drawOptions().comicMode = true;
984       drawer.drawMolecule(*m, "Oxytocin (flat)");
985       drawer.finishDrawing();
986       auto text = drawer.getDrawingText();
987       std::ofstream outs("testHandDrawn-1.svg");
988       outs << text;
989       outs.flush();
990     }
991 #ifdef RDK_BUILD_CAIRO_SUPPORT
992     {
993       MolDraw2DCairo drawer(450, 400);
994       drawer.drawOptions().fontFile = fName;
995       drawer.drawOptions().comicMode = true;
996       drawer.drawMolecule(*m, "Oxytocin (flat)");
997       drawer.finishDrawing();
998       drawer.writeDrawingText("testHandDrawn-1.png");
999     }
1000 #endif
1001   }
1002   SECTION("with chirality") {
1003     auto m =
1004         "CC[C@H](C)[C@@H]1NC(=O)[C@H](Cc2ccc(O)cc2)NC(=O)[C@@H](N)CSSC[C@@H](C(=O)N2CCC[C@H]2C(=O)N[C@@H](CC(C)C)C(=O)NCC(N)=O)NC(=O)[C@H](CC(N)=O)NC(=O)[C@H](CCC(N)=O)NC1=O"_smiles;
1005     REQUIRE(m);
1006     RDDepict::preferCoordGen = true;
1007     MolDraw2DUtils::prepareMolForDrawing(*m);
1008 
1009     std::string fName = getenv("RDBASE");
1010     fName += "/Data/Fonts/ComicNeue-Regular.ttf";
1011 
1012     {
1013       MolDraw2DSVG drawer(450, 400);
1014       drawer.drawOptions().fontFile = fName;
1015       drawer.drawOptions().comicMode = true;
1016       drawer.drawMolecule(*m, "Oxytocin");
1017       drawer.finishDrawing();
1018       auto text = drawer.getDrawingText();
1019       std::ofstream outs("testHandDrawn-2.svg");
1020       outs << text;
1021       outs.flush();
1022     }
1023 #ifdef RDK_BUILD_CAIRO_SUPPORT
1024     {
1025       MolDraw2DCairo drawer(450, 400);
1026       drawer.drawOptions().fontFile = fName;
1027       drawer.drawOptions().comicMode = true;
1028       drawer.drawMolecule(*m, "Oxytocin");
1029       drawer.finishDrawing();
1030       drawer.writeDrawingText("testHandDrawn-2.png");
1031     }
1032 #endif
1033   }
1034   SECTION("smaller") {
1035     auto m = "N=c1nc([C@H]2NCCCC2)cc(N)n1O"_smiles;
1036     REQUIRE(m);
1037     RDDepict::preferCoordGen = true;
1038     MolDraw2DUtils::prepareMolForDrawing(*m);
1039 
1040     std::string fName = getenv("RDBASE");
1041     fName += "/Data/Fonts/ComicNeue-Regular.ttf";
1042 
1043     {
1044       MolDraw2DSVG drawer(350, 300);
1045       drawer.drawOptions().fontFile = fName;
1046       drawer.drawOptions().comicMode = true;
1047       drawer.drawMolecule(*m);
1048       drawer.finishDrawing();
1049       auto text = drawer.getDrawingText();
1050       std::ofstream outs("testHandDrawn-3.svg");
1051       outs << text;
1052       outs.flush();
1053     }
1054 #ifdef RDK_BUILD_CAIRO_SUPPORT
1055     {
1056       MolDraw2DCairo drawer(350, 300);
1057       drawer.drawOptions().fontFile = fName;
1058       drawer.drawOptions().comicMode = true;
1059       drawer.drawMolecule(*m);
1060       drawer.finishDrawing();
1061       drawer.writeDrawingText("testHandDrawn-3.png");
1062     }
1063 #endif
1064   }
1065   SECTION("another one") {
1066     auto m =
1067         "CCCc1nn(C)c2c(=O)nc(-c3cc(S(=O)(=O)N4CCN(C)CC4)ccc3OCC)[nH]c12"_smiles;
1068     REQUIRE(m);
1069     RDDepict::preferCoordGen = true;
1070     MolDraw2DUtils::prepareMolForDrawing(*m);
1071 
1072     std::string fName = getenv("RDBASE");
1073     fName += "/Data/Fonts/ComicNeue-Regular.ttf";
1074 
1075     {
1076       MolDraw2DSVG drawer(350, 300);
1077       drawer.drawOptions().fontFile = fName;
1078       drawer.drawOptions().comicMode = true;
1079       drawer.drawMolecule(*m);
1080       drawer.finishDrawing();
1081       auto text = drawer.getDrawingText();
1082       std::ofstream outs("testHandDrawn-4.svg");
1083       outs << text;
1084       outs.flush();
1085     }
1086 #ifdef RDK_BUILD_CAIRO_SUPPORT
1087     {
1088       MolDraw2DCairo drawer(350, 300);
1089       drawer.drawOptions().fontFile = fName;
1090       drawer.drawOptions().comicMode = true;
1091       drawer.drawMolecule(*m);
1092       drawer.finishDrawing();
1093       drawer.writeDrawingText("testHandDrawn-4.png");
1094     }
1095 #endif
1096   }
1097   SECTION("large") {
1098     auto m =
1099         "CC[C@H](C)[C@@H](C(=O)N[C@@H]([C@@H](C)CC)C(=O)N[C@@H](CCCCN)C(=O)N[C@@H](CC(=O)N)C(=O)N[C@@H](C)C(=O)N[C@@H](Cc1ccc(cc1)O)C(=O)N[C@@H](CCCCN)C(=O)N[C@@H](CCCCN)C(=O)NCC(=O)N[C@@H](CCC(=O)N)C(=O)O)NC(=O)[C@H](C)NC(=O)[C@H](CC(=O)N)NC(=O)[C@H](CCCCN)NC(=O)[C@H](Cc2ccccc2)NC(=O)[C@H](CC(C)C)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@H](C(C)C)NC(=O)[C@H](CC(C)C)NC(=O)[C@@H]3CCCN3C(=O)[C@H]([C@@H](C)O)NC(=O)[C@H](CCC(=O)N)NC(=O)[C@H](CO)NC(=O)[C@H](CCCCN)NC(=O)[C@H](CCC(=O)N)NC(=O)[C@H](CO)NC(=O)[C@H]([C@@H](C)O)NC(=O)[C@H](CCSC)NC(=O)[C@H](Cc4ccccc4)NC(=O)CNC(=O)CNC(=O)[C@H](Cc5ccc(cc5)O)N"_smiles;
1100     REQUIRE(m);
1101     RDDepict::preferCoordGen = true;
1102     MolDraw2DUtils::prepareMolForDrawing(*m);
1103 
1104     std::string fName = getenv("RDBASE");
1105     fName += "/Data/Fonts/ComicNeue-Regular.ttf";
1106 
1107     {
1108       MolDraw2DSVG drawer(900, 450);
1109       drawer.drawMolecule(*m);
1110       drawer.finishDrawing();
1111       auto text = drawer.getDrawingText();
1112       std::ofstream outs("testHandDrawn-5a.svg");
1113       outs << text;
1114       outs.flush();
1115     }
1116     {
1117       MolDraw2DSVG drawer(900, 450);
1118       drawer.drawOptions().fontFile = fName;
1119       drawer.drawOptions().comicMode = true;
1120       drawer.drawMolecule(*m);
1121       drawer.finishDrawing();
1122       auto text = drawer.getDrawingText();
1123       std::ofstream outs("testHandDrawn-5b.svg");
1124       outs << text;
1125       outs.flush();
1126     }
1127 #ifdef RDK_BUILD_CAIRO_SUPPORT
1128     {
1129       MolDraw2DCairo drawer(900, 450);
1130       drawer.drawOptions().fontFile = fName;
1131       drawer.drawOptions().comicMode = true;
1132       drawer.drawMolecule(*m);
1133       drawer.finishDrawing();
1134       drawer.writeDrawingText("testHandDrawn-5.png");
1135     }
1136 #endif
1137   }
1138 }
1139 
1140 TEST_CASE("drawMoleculeBrackets", "[extras]") {
1141   SECTION("basics") {
1142     auto m = R"CTAB(
1143   ACCLDraw11042015112D
1144 
1145   0  0  0     0  0            999 V3000
1146 M  V30 BEGIN CTAB
1147 M  V30 COUNTS 5 4 1 0 0
1148 M  V30 BEGIN ATOM
1149 M  V30 1 C 7 -6.7813 0 0
1150 M  V30 2 C 8.0229 -6.1907 0 0 CFG=3
1151 M  V30 3 C 8.0229 -5.0092 0 0
1152 M  V30 4 C 9.046 -6.7814 0 0
1153 M  V30 5 C 10.0692 -6.1907 0 0
1154 M  V30 END ATOM
1155 M  V30 BEGIN BOND
1156 M  V30 1 1 1 2
1157 M  V30 2 1 2 3
1158 M  V30 3 1 2 4
1159 M  V30 4 1 4 5
1160 M  V30 END BOND
1161 M  V30 BEGIN SGROUP
1162 M  V30 1 SRU 1 ATOMS=(3 3 2 4) XBONDS=(2 1 4) BRKXYZ=(9 7.51 -7.08 0 7.51 -
1163 M  V30 -5.9 0 0 0 0) BRKXYZ=(9 9.56 -5.9 0 9.56 -7.08 0 0 0 0) -
1164 M  V30 CONNECT=HT LABEL=n
1165 M  V30 END SGROUP
1166 M  V30 END CTAB
1167 M  END
1168 )CTAB"_ctab;
1169     REQUIRE(m);
1170     {
1171       MolDraw2DSVG drawer(350, 300);
1172       drawer.drawMolecule(*m);
1173       drawer.finishDrawing();
1174       auto text = drawer.getDrawingText();
1175       std::ofstream outs("testBrackets-1a.svg");
1176       outs << text;
1177       outs.flush();
1178     }
1179     {  // rotation
1180       MolDraw2DSVG drawer(350, 300);
1181       drawer.drawOptions().rotate = 90;
1182       drawer.drawMolecule(*m);
1183       drawer.finishDrawing();
1184       auto text = drawer.getDrawingText();
1185       std::ofstream outs("testBrackets-1b.svg");
1186       outs << text;
1187       outs.flush();
1188     }
1189     {  // centering
1190       MolDraw2DSVG drawer(350, 300);
1191       drawer.drawOptions().centreMoleculesBeforeDrawing = true;
1192       drawer.drawMolecule(*m);
1193       drawer.finishDrawing();
1194       auto text = drawer.getDrawingText();
1195       std::ofstream outs("testBrackets-1c.svg");
1196       outs << text;
1197       outs.flush();
1198     }
1199     {  // rotation + centering
1200       MolDraw2DSVG drawer(350, 300);
1201       drawer.drawOptions().centreMoleculesBeforeDrawing = true;
1202       drawer.drawOptions().rotate = 90;
1203       drawer.drawMolecule(*m);
1204       drawer.finishDrawing();
1205       auto text = drawer.getDrawingText();
1206       std::ofstream outs("testBrackets-1d.svg");
1207       outs << text;
1208       outs.flush();
1209     }
1210     {  // rotation
1211       MolDraw2DSVG drawer(350, 300);
1212       drawer.drawOptions().rotate = 180;
1213       drawer.drawMolecule(*m);
1214       drawer.finishDrawing();
1215       auto text = drawer.getDrawingText();
1216       std::ofstream outs("testBrackets-1e.svg");
1217       outs << text;
1218       outs.flush();
1219     }
1220   }
1221   SECTION("three brackets") {
1222     auto m = R"CTAB(three brackets
1223   Mrv2014 11052006542D
1224 
1225   0  0  0     0  0            999 V3000
1226 M  V30 BEGIN CTAB
1227 M  V30 COUNTS 6 5 1 0 0
1228 M  V30 BEGIN ATOM
1229 M  V30 1 * -1.375 3.1667 0 0
1230 M  V30 2 C -0.0413 3.9367 0 0
1231 M  V30 3 C 1.2924 3.1667 0 0
1232 M  V30 4 * 2.626 3.9367 0 0
1233 M  V30 5 C 0.0003 5.6017 0 0
1234 M  V30 6 * 1.334 6.3717 0 0
1235 M  V30 END ATOM
1236 M  V30 BEGIN BOND
1237 M  V30 1 1 1 2
1238 M  V30 2 1 2 3
1239 M  V30 3 1 3 4
1240 M  V30 4 1 2 5
1241 M  V30 5 1 5 6
1242 M  V30 END BOND
1243 M  V30 BEGIN SGROUP
1244 M  V30 1 SRU 0 ATOMS=(3 2 3 5) XBONDS=(3 1 3 5) BRKXYZ=(9 0.0875 6.7189 0 -
1245 M  V30 1.0115 5.1185 0 0 0 0) BRKXYZ=(9 1.3795 4.2839 0 2.3035 2.6835 0 0 0 -
1246 M  V30 0) BRKXYZ=(9 -0.1285 2.8194 0 -1.0525 4.4198 0 0 0 0) CONNECT=HT -
1247 M  V30 LABEL=n
1248 M  V30 END SGROUP
1249 M  V30 END CTAB
1250 M  END)CTAB"_ctab;
1251     REQUIRE(m);
1252     {
1253       MolDraw2DSVG drawer(350, 300);
1254       drawer.drawMolecule(*m);
1255       drawer.finishDrawing();
1256       auto text = drawer.getDrawingText();
1257       std::ofstream outs("testBrackets-2a.svg");
1258       outs << text;
1259       outs.flush();
1260     }
1261     {  // rotation
1262       MolDraw2DSVG drawer(350, 300);
1263       drawer.drawOptions().rotate = 90;
1264       drawer.drawMolecule(*m);
1265       drawer.finishDrawing();
1266       auto text = drawer.getDrawingText();
1267       std::ofstream outs("testBrackets-2b.svg");
1268       outs << text;
1269       outs.flush();
1270     }
1271     {  // centering
1272       MolDraw2DSVG drawer(350, 300);
1273       drawer.drawOptions().centreMoleculesBeforeDrawing = true;
1274       drawer.drawMolecule(*m);
1275       drawer.finishDrawing();
1276       auto text = drawer.getDrawingText();
1277       std::ofstream outs("testBrackets-2c.svg");
1278       outs << text;
1279       outs.flush();
1280     }
1281     {  // rotation + centering
1282       MolDraw2DSVG drawer(350, 300);
1283       drawer.drawOptions().centreMoleculesBeforeDrawing = true;
1284       drawer.drawOptions().rotate = 90;
1285       drawer.drawMolecule(*m);
1286       drawer.finishDrawing();
1287       auto text = drawer.getDrawingText();
1288       std::ofstream outs("testBrackets-2d.svg");
1289       outs << text;
1290       outs.flush();
1291     }
1292   }
1293   SECTION("ChEBI 59342") {
1294     // thanks to John Mayfield for pointing out the example
1295     auto m = R"CTAB(ChEBI59342
1296 Marvin  05041012302D
1297 
1298  29 30  0  0  1  0            999 V2000
1299    10.1615   -7.7974    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1300     8.7305   -6.9763    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1301     8.7309   -7.8004    0.0000 C   0  0  2  0  0  0  0  0  0  0  0  0
1302     9.4464   -8.2109    0.0000 C   0  0  2  0  0  0  0  0  0  0  0  0
1303     8.0153   -8.2225    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1304     9.4464   -9.0437    0.0000 C   0  0  2  0  0  0  0  0  0  0  0  0
1305     8.0138   -9.0500    0.0000 C   0  0  1  0  0  0  0  0  0  0  0  0
1306     8.7293   -9.4606    0.0000 C   0  0  1  0  0  0  0  0  0  0  0  0
1307    10.1669   -9.4529    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1308     7.3058   -9.4590    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1309     8.7368  -10.2801    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
1310     8.0263  -10.6992    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1311     8.0339  -11.5241    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
1312     7.3081  -10.2933    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1313     8.7305   -5.3264    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1314     8.0159   -5.7369    0.0000 C   0  0  2  0  0  0  0  0  0  0  0  0
1315     8.0159   -6.5618    0.0000 C   0  0  2  0  0  0  0  0  0  0  0  0
1316     7.2936   -5.3263    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1317     7.2936   -6.9762    0.0000 C   0  0  2  0  0  0  0  0  0  0  0  0
1318     6.5751   -5.7368    0.0000 C   0  0  1  0  0  0  0  0  0  0  0  0
1319     6.5751   -6.5618    0.0000 C   0  0  1  0  0  0  0  0  0  0  0  0
1320     7.2973   -7.8049    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1321     5.8681   -5.3263    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1322     5.8680   -6.9762    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
1323     5.1510   -6.5684    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1324     4.4392   -6.9856    0.0000 H   0  0  0  0  0  0  0  0  0  0  0  0
1325     5.1455   -5.7435    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1326    10.4142   -5.3560    0.0000 *   0  0  0  0  0  0  0  0  0  0  0  0
1327    11.5590   -7.8297    0.0000 *   0  0  0  0  0  0  0  0  0  0  0  0
1328   3  2  1  6  0  0  0
1329   3  4  1  0  0  0  0
1330   3  5  1  0  0  0  0
1331   4  6  1  0  0  0  0
1332   4  1  1  1  0  0  0
1333   5  7  1  0  0  0  0
1334   6  8  1  0  0  0  0
1335   6  9  1  1  0  0  0
1336   7 10  1  1  0  0  0
1337   8 11  1  6  0  0  0
1338   7  8  1  0  0  0  0
1339  13 12  1  0  0  0  0
1340  14 12  2  0  0  0  0
1341  11 12  1  0  0  0  0
1342  16 15  1  6  0  0  0
1343  16 17  1  0  0  0  0
1344  16 18  1  0  0  0  0
1345  17 19  1  0  0  0  0
1346  17  2  1  1  0  0  0
1347  18 20  1  0  0  0  0
1348  19 21  1  0  0  0  0
1349  19 22  1  1  0  0  0
1350  20 23  1  1  0  0  0
1351  21 24  1  6  0  0  0
1352  20 21  1  0  0  0  0
1353  26 25  1  0  0  0  0
1354  27 25  2  0  0  0  0
1355  24 25  1  0  0  0  0
1356  15 28  1  0  0  0  0
1357   1 29  1  0  0  0  0
1358 M  STY  1   1 SRU
1359 M  SCN  1   1 HT
1360 M  SAL   1 15   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
1361 M  SAL   1 12  16  17  18  19  20  21  22  23  24  25  26  27
1362 M  SDI   1  4    9.4310   -4.9261    9.4165   -5.7510
1363 M  SDI   1  4   10.7464   -7.3983   10.7274   -8.2231
1364 M  SBL   1  2  30  29
1365 M  SMT   1 n
1366 M  END)CTAB"_ctab;
1367     REQUIRE(m);
1368     {
1369       MolDraw2DSVG drawer(350, 300);
1370       drawer.drawMolecule(*m);
1371       drawer.finishDrawing();
1372       auto text = drawer.getDrawingText();
1373       std::ofstream outs("testBrackets-3a.svg");
1374       outs << text;
1375       outs.flush();
1376     }
1377   }
1378   SECTION("pathological bracket orientation") {
1379     {  // including the bonds
1380       auto m = R"CTAB(bogus
1381   Mrv2014 11202009512D
1382 
1383   0  0  0     0  0            999 V3000
1384 M  V30 BEGIN CTAB
1385 M  V30 COUNTS 9 8 1 0 1
1386 M  V30 BEGIN ATOM
1387 M  V30 1 C 23.5462 -14.464 0 0
1388 M  V30 2 C 20.8231 -13.0254 0 0
1389 M  V30 3 C 20.8776 -14.5628 0 0
1390 M  V30 4 C 22.2391 -15.2819 0 0
1391 M  V30 5 C 16.2969 -9.9426 0 0
1392 M  V30 6 C 14.963 -10.7089 0 0
1393 M  V30 7 C 19.463 -12.2987 0 0
1394 M  V30 8 * 19.4398 -9.9979 0 0
1395 M  V30 9 * 26.1554 -14.4332 0 0
1396 M  V30 END ATOM
1397 M  V30 BEGIN BOND
1398 M  V30 1 1 3 4
1399 M  V30 2 1 6 7
1400 M  V30 3 1 5 8
1401 M  V30 4 1 1 9
1402 M  V30 5 1 7 2
1403 M  V30 6 1 6 5
1404 M  V30 7 1 4 1
1405 M  V30 8 1 3 2
1406 M  V30 END BOND
1407 M  V30 BEGIN SGROUP
1408 M  V30 1 SRU 0 ATOMS=(7 4 3 7 6 5 2 1) XBONDS=(2 3 4) BRKXYZ=(9 17.6045 -
1409 M  V30 -9.1954 0 17.5775 -10.7352 0 0 0 0) BRKXYZ=(9 24.6113 -13.6813 0 -
1410 M  V30 24.6296 -15.2213 0 0 0 0) CONNECT=HT LABEL=n
1411 M  V30 END SGROUP
1412 M  V30 END CTAB
1413 M  END
1414 )CTAB"_ctab;
1415       REQUIRE(m);
1416       MolDraw2DSVG drawer(350, 300);
1417       drawer.drawMolecule(*m);
1418       drawer.finishDrawing();
1419       auto text = drawer.getDrawingText();
1420       std::ofstream outs("testBrackets-4a.svg");
1421       outs << text;
1422       outs.flush();
1423     }
1424 
1425     {  // no bonds in the sgroup, the bracket should point the other way
1426        // (towards the majority of the atoms in the sgroup)
1427       auto m = R"CTAB(bogus
1428   Mrv2014 11202009512D
1429 
1430   0  0  0     0  0            999 V3000
1431 M  V30 BEGIN CTAB
1432 M  V30 COUNTS 9 8 1 0 1
1433 M  V30 BEGIN ATOM
1434 M  V30 1 C 23.5462 -14.464 0 0
1435 M  V30 2 C 20.8231 -13.0254 0 0
1436 M  V30 3 C 20.8776 -14.5628 0 0
1437 M  V30 4 C 22.2391 -15.2819 0 0
1438 M  V30 5 C 16.2969 -9.9426 0 0
1439 M  V30 6 C 14.963 -10.7089 0 0
1440 M  V30 7 C 19.463 -12.2987 0 0
1441 M  V30 8 * 19.4398 -9.9979 0 0
1442 M  V30 9 * 26.1554 -14.4332 0 0
1443 M  V30 END ATOM
1444 M  V30 BEGIN BOND
1445 M  V30 1 1 3 4
1446 M  V30 2 1 6 7
1447 M  V30 3 1 5 8
1448 M  V30 4 1 1 9
1449 M  V30 5 1 7 2
1450 M  V30 6 1 6 5
1451 M  V30 7 1 4 1
1452 M  V30 8 1 3 2
1453 M  V30 END BOND
1454 M  V30 BEGIN SGROUP
1455 M  V30 1 SRU 0 ATOMS=(7 4 3 7 6 5 2 1) BRKXYZ=(9 17.6045 -
1456 M  V30 -9.1954 0 17.5775 -10.7352 0 0 0 0) BRKXYZ=(9 24.6113 -13.6813 0 -
1457 M  V30 24.6296 -15.2213 0 0 0 0) CONNECT=HT LABEL=n
1458 M  V30 END SGROUP
1459 M  V30 END CTAB
1460 M  END
1461 )CTAB"_ctab;
1462       REQUIRE(m);
1463       MolDraw2DSVG drawer(350, 300);
1464       drawer.drawMolecule(*m);
1465       drawer.finishDrawing();
1466       auto text = drawer.getDrawingText();
1467       std::ofstream outs("testBrackets-4b.svg");
1468       outs << text;
1469       outs.flush();
1470     }
1471   }
1472   SECTION("comic brackets (no font though)") {
1473     auto m = R"CTAB(
1474   ACCLDraw11042015112D
1475 
1476   0  0  0     0  0            999 V3000
1477 M  V30 BEGIN CTAB
1478 M  V30 COUNTS 5 4 1 0 0
1479 M  V30 BEGIN ATOM
1480 M  V30 1 C 7 -6.7813 0 0
1481 M  V30 2 C 8.0229 -6.1907 0 0 CFG=3
1482 M  V30 3 C 8.0229 -5.0092 0 0
1483 M  V30 4 C 9.046 -6.7814 0 0
1484 M  V30 5 C 10.0692 -6.1907 0 0
1485 M  V30 END ATOM
1486 M  V30 BEGIN BOND
1487 M  V30 1 1 1 2
1488 M  V30 2 1 2 3
1489 M  V30 3 1 2 4
1490 M  V30 4 1 4 5
1491 M  V30 END BOND
1492 M  V30 BEGIN SGROUP
1493 M  V30 1 SRU 1 ATOMS=(3 3 2 4) XBONDS=(2 1 4) BRKXYZ=(9 7.51 -7.08 0 7.51 -
1494 M  V30 -5.9 0 0 0 0) BRKXYZ=(9 9.56 -5.9 0 9.56 -7.08 0 0 0 0) -
1495 M  V30 CONNECT=HT LABEL=n
1496 M  V30 END SGROUP
1497 M  V30 END CTAB
1498 M  END
1499 )CTAB"_ctab;
1500     REQUIRE(m);
1501     {
1502       MolDraw2DSVG drawer(350, 300);
1503       drawer.drawOptions().comicMode = true;
1504       drawer.drawMolecule(*m);
1505       drawer.finishDrawing();
1506       auto text = drawer.getDrawingText();
1507       std::ofstream outs("testBrackets-5a.svg");
1508       outs << text;
1509       outs.flush();
1510     }
1511   }
1512 }
1513 
1514 #ifdef RDK_BUILD_CAIRO_SUPPORT
1515 TEST_CASE("github #3543: Error adding PNG metadata when kekulize=False",
1516           "[bug][metadata][png]") {
1517   SECTION("basics") {
1518     auto m = "n1cccc1"_smarts;
1519     m->updatePropertyCache(false);
1520     MolDraw2DCairo drawer(350, 300);
1521     bool kekulize = false;
1522     MolDraw2DUtils::prepareMolForDrawing(*m, kekulize);
1523     drawer.drawOptions().prepareMolsBeforeDrawing = false;
1524     drawer.drawMolecule(*m);
1525     drawer.finishDrawing();
1526     auto png = drawer.getDrawingText();
1527   }
1528   SECTION("as reported") {
1529     auto m = "n1cnc2c(n)ncnc12"_smarts;
1530     m->updatePropertyCache(false);
1531     MolDraw2DCairo drawer(350, 300);
1532     bool kekulize = false;
1533     MolDraw2DUtils::prepareMolForDrawing(*m, kekulize);
1534     drawer.drawOptions().prepareMolsBeforeDrawing = false;
1535     drawer.drawMolecule(*m);
1536     drawer.finishDrawing();
1537     auto png = drawer.getDrawingText();
1538   }
1539 }
1540 #endif
1541 
1542 TEST_CASE("SGroup Data") {
1543   SECTION("ABS") {
1544     auto m = R"CTAB(
1545   Mrv2014 12072015352D
1546 
1547   0  0  0     0  0            999 V3000
1548 M  V30 BEGIN CTAB
1549 M  V30 COUNTS 9 9 1 0 0
1550 M  V30 BEGIN ATOM
1551 M  V30 1 C -6.5833 4.3317 0 0
1552 M  V30 2 C -7.917 3.5617 0 0
1553 M  V30 3 C -7.917 2.0216 0 0
1554 M  V30 4 C -6.5833 1.2516 0 0
1555 M  V30 5 C -5.2497 2.0216 0 0
1556 M  V30 6 C -5.2497 3.5617 0 0
1557 M  V30 7 C -3.916 4.3317 0 0
1558 M  V30 8 O -3.916 5.8717 0 0
1559 M  V30 9 O -2.5823 3.5617 0 0
1560 M  V30 END ATOM
1561 M  V30 BEGIN BOND
1562 M  V30 1 1 1 2
1563 M  V30 2 2 2 3
1564 M  V30 3 1 3 4
1565 M  V30 4 2 4 5
1566 M  V30 5 1 5 6
1567 M  V30 6 2 1 6
1568 M  V30 7 1 6 7
1569 M  V30 8 2 7 8
1570 M  V30 9 1 7 9
1571 M  V30 END BOND
1572 M  V30 BEGIN SGROUP
1573 M  V30 1 DAT 0 ATOMS=(1 9) FIELDNAME=pKa -
1574 M  V30 FIELDDISP="   -2.2073    2.3950    DAU   ALL  0       0" -
1575 M  V30 MRV_FIELDDISP=0 FIELDDATA=4.2
1576 M  V30 END SGROUP
1577 M  V30 END CTAB
1578 M  END
1579 )CTAB"_ctab;
1580     REQUIRE(m);
1581     {
1582       MolDraw2DSVG drawer(350, 300);
1583       drawer.drawMolecule(*m, "abs");
1584       drawer.finishDrawing();
1585       auto text = drawer.getDrawingText();
1586       std::ofstream outs("testSGroupData-1a.svg");
1587       outs << text;
1588       outs.flush();
1589     }
1590     {
1591       MolDraw2DSVG drawer(350, 300);
1592       drawer.drawOptions().centreMoleculesBeforeDrawing = true;
1593       drawer.drawOptions().rotate = 90;
1594       drawer.drawMolecule(*m, "centered, rotated");
1595       drawer.finishDrawing();
1596       auto text = drawer.getDrawingText();
1597       std::ofstream outs("testSGroupData-1b.svg");
1598       outs << text;
1599       outs.flush();
1600     }
1601   }
1602   SECTION("REL") {
1603     auto m = R"CTAB(
1604   Mrv2014 12072015352D
1605 
1606   0  0  0     0  0            999 V3000
1607 M  V30 BEGIN CTAB
1608 M  V30 COUNTS 9 9 1 0 0
1609 M  V30 BEGIN ATOM
1610 M  V30 1 C -6.5833 4.3317 0 0
1611 M  V30 2 C -7.917 3.5617 0 0
1612 M  V30 3 C -7.917 2.0216 0 0
1613 M  V30 4 C -6.5833 1.2516 0 0
1614 M  V30 5 C -5.2497 2.0216 0 0
1615 M  V30 6 C -5.2497 3.5617 0 0
1616 M  V30 7 C -3.916 4.3317 0 0
1617 M  V30 8 O -3.916 5.8717 0 0
1618 M  V30 9 O -2.5823 3.5617 0 0
1619 M  V30 END ATOM
1620 M  V30 BEGIN BOND
1621 M  V30 1 1 1 2
1622 M  V30 2 2 2 3
1623 M  V30 3 1 3 4
1624 M  V30 4 2 4 5
1625 M  V30 5 1 5 6
1626 M  V30 6 2 1 6
1627 M  V30 7 1 6 7
1628 M  V30 8 2 7 8
1629 M  V30 9 1 7 9
1630 M  V30 END BOND
1631 M  V30 BEGIN SGROUP
1632 M  V30 1 DAT 0 ATOMS=(1 9) FIELDNAME=pKa -
1633 M  V30 FIELDDISP="    0.2000    0.2000    DRU   ALL  0       0" -
1634 M  V30 MRV_FIELDDISP=0 FIELDDATA=4.2
1635 M  V30 END SGROUP
1636 M  V30 END CTAB
1637 M  END
1638 )CTAB"_ctab;
1639     REQUIRE(m);
1640     {
1641       MolDraw2DSVG drawer(350, 300);
1642       drawer.drawMolecule(*m, "rel");
1643       drawer.finishDrawing();
1644       auto text = drawer.getDrawingText();
1645       std::ofstream outs("testSGroupData-2a.svg");
1646       outs << text;
1647       outs.flush();
1648     }
1649     {
1650       MolDraw2DSVG drawer(350, 300);
1651       drawer.drawOptions().centreMoleculesBeforeDrawing = true;
1652       drawer.drawOptions().rotate = 90;
1653       drawer.drawMolecule(*m, "rel, centered, rotated");
1654       drawer.finishDrawing();
1655       auto text = drawer.getDrawingText();
1656       std::ofstream outs("testSGroupData-2b.svg");
1657       outs << text;
1658       outs.flush();
1659     }
1660   }
1661   {
1662     auto m = R"CTAB(random example found on internet
1663    JSDraw204221719232D
1664 
1665  20 21  0  0  0  0              0 V2000
1666    10.1710   -5.6553    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1667    10.9428   -4.2996    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
1668     8.6110   -5.6647    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1669    10.9591   -7.0015    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
1670    12.5190   -6.9921    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1671    13.3072   -8.3384    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1672    13.2909   -5.6364    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1673    12.5028   -4.2902    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1674    13.2746   -2.9345    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1675    14.8508   -5.6270    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
1676    15.6226   -4.2713    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
1677    20.3026   -4.2431    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1678    19.5307   -5.5987    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
1679    21.8625   -4.2336    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1680    19.5144   -2.8968    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
1681    17.9544   -2.9062    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1682    17.1663   -1.5600    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1683    17.1826   -4.2619    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1684    17.9708   -5.6082    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
1685    17.1989   -6.9638    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
1686   1  2  1  0  0  0  0
1687   1  3  2  0  0  0  0
1688   1  4  1  0  0  0  0
1689   4  5  2  0  0  0  0
1690   5  6  1  0  0  0  0
1691   5  7  1  0  0  0  0
1692   7  8  1  0  0  0  0
1693   8  9  2  0  0  0  0
1694   8  2  1  0  0  0  0
1695   7 10  1  0  0  0  0
1696  10 11  2  0  0  0  0
1697  12 13  1  0  0  0  0
1698  12 14  2  0  0  0  0
1699  12 15  1  0  0  0  0
1700  15 16  1  0  0  0  0
1701  16 17  2  0  0  0  0
1702  16 18  1  0  0  0  0
1703  18 19  1  0  0  0  0
1704  19 20  1  0  0  0  0
1705  19 13  2  0  0  0  0
1706  11 18  1  0  0  0  0
1707 M  STY  1   1 DAT
1708 M  SDT   1 UNKNOWN                        F
1709 M  SDD   1    16.0856   -8.1573    DA    ALL  1       5
1710 M  SED   1 Ni-complex
1711 M  END)CTAB"_ctab;
1712     {
1713       MolDraw2DSVG drawer(350, 300);
1714       drawer.drawMolecule(*m);
1715       drawer.finishDrawing();
1716       auto text = drawer.getDrawingText();
1717       std::ofstream outs("testSGroupData-3a.svg");
1718       outs << text;
1719       outs.flush();
1720     }
1721   }
1722 }
1723 
1724 TEST_CASE("position variation bonds", "[extras]") {
1725   SECTION("simple") {
1726     auto m = R"CTAB(
1727   Mrv2014 12092006072D
1728 
1729   0  0  0     0  0            999 V3000
1730 M  V30 BEGIN CTAB
1731 M  V30 COUNTS 9 8 0 0 0
1732 M  V30 BEGIN ATOM
1733 M  V30 1 C -4.7083 4.915 0 0
1734 M  V30 2 C -6.042 4.145 0 0
1735 M  V30 3 C -6.042 2.605 0 0
1736 M  V30 4 C -4.7083 1.835 0 0
1737 M  V30 5 C -3.3747 2.605 0 0
1738 M  V30 6 C -3.3747 4.145 0 0
1739 M  V30 7 * -3.8192 3.8883 0 0
1740 M  V30 8 O -3.8192 6.1983 0 0
1741 M  V30 9 C -2.4855 6.9683 0 0
1742 M  V30 END ATOM
1743 M  V30 BEGIN BOND
1744 M  V30 1 1 1 2
1745 M  V30 2 2 2 3
1746 M  V30 3 1 3 4
1747 M  V30 4 2 4 5
1748 M  V30 5 1 5 6
1749 M  V30 6 2 1 6
1750 M  V30 7 1 7 8 ENDPTS=(3 1 6 5) ATTACH=ANY
1751 M  V30 8 1 8 9
1752 M  V30 END BOND
1753 M  V30 END CTAB
1754 M  END
1755 )CTAB"_ctab;
1756     REQUIRE(m);
1757     {
1758       MolDraw2DSVG drawer(350, 300);
1759       drawer.drawMolecule(*m, "variations");
1760       drawer.finishDrawing();
1761       auto text = drawer.getDrawingText();
1762       std::ofstream outs("testPositionVariation-1.svg");
1763       outs << text;
1764       outs.flush();
1765     }
1766     {  // make sure comic mode doesn't screw this up
1767       MolDraw2DSVG drawer(350, 300);
1768       drawer.drawOptions().comicMode = true;
1769       drawer.drawMolecule(*m, "comic variations");
1770       drawer.finishDrawing();
1771       auto text = drawer.getDrawingText();
1772       std::ofstream outs("testPositionVariation-1b.svg");
1773       outs << text;
1774       outs.flush();
1775     }
1776   }
1777   SECTION("multiple") {
1778     auto m = R"CTAB(
1779   Mrv2014 12092006082D
1780 
1781   0  0  0     0  0            999 V3000
1782 M  V30 BEGIN CTAB
1783 M  V30 COUNTS 15 14 0 0 0
1784 M  V30 BEGIN ATOM
1785 M  V30 1 C -4.7083 4.915 0 0
1786 M  V30 2 C -6.042 4.145 0 0
1787 M  V30 3 C -6.042 2.605 0 0
1788 M  V30 4 C -4.7083 1.835 0 0
1789 M  V30 5 C -3.3747 2.605 0 0
1790 M  V30 6 C -3.3747 4.145 0 0
1791 M  V30 7 * -3.8192 3.8883 0 0
1792 M  V30 8 O -3.8192 6.1983 0 0
1793 M  V30 9 C -2.4855 6.9683 0 0
1794 M  V30 10 C -7.3757 4.915 0 0
1795 M  V30 11 C -8.7093 4.145 0 0
1796 M  V30 12 C -8.7093 2.605 0 0
1797 M  V30 13 C -7.3757 1.835 0 0
1798 M  V30 14 * -8.7093 3.375 0 0
1799 M  V30 15 O -10.2922 3.375 0 0
1800 M  V30 END ATOM
1801 M  V30 BEGIN BOND
1802 M  V30 1 1 1 2
1803 M  V30 2 1 2 3
1804 M  V30 3 1 3 4
1805 M  V30 4 2 4 5
1806 M  V30 5 1 5 6
1807 M  V30 6 2 1 6
1808 M  V30 7 1 7 8 ENDPTS=(3 1 6 5) ATTACH=ANY
1809 M  V30 8 1 8 9
1810 M  V30 9 1 10 11
1811 M  V30 10 2 11 12
1812 M  V30 11 1 12 13
1813 M  V30 12 2 10 2
1814 M  V30 13 2 13 3
1815 M  V30 14 1 14 15 ENDPTS=(2 11 12) ATTACH=ANY
1816 M  V30 END BOND
1817 M  V30 END CTAB
1818 M  END
1819 )CTAB"_ctab;
1820     REQUIRE(m);
1821     {
1822       MolDraw2DSVG drawer(350, 300);
1823       drawer.drawMolecule(*m, "multiple variations");
1824       drawer.finishDrawing();
1825       auto text = drawer.getDrawingText();
1826       std::ofstream outs("testPositionVariation-2.svg");
1827       outs << text;
1828       outs.flush();
1829     }
1830   }
1831   SECTION("non-contiguous") {
1832     auto m = R"CTAB(
1833   Mrv2014 12092006102D
1834 
1835   0  0  0     0  0            999 V3000
1836 M  V30 BEGIN CTAB
1837 M  V30 COUNTS 9 8 0 0 0
1838 M  V30 BEGIN ATOM
1839 M  V30 1 C -0.875 8.7484 0 0
1840 M  V30 2 C -2.2087 7.9784 0 0
1841 M  V30 3 C -2.2087 6.4383 0 0
1842 M  V30 4 C -0.875 5.6683 0 0
1843 M  V30 5 C 0.4587 6.4383 0 0
1844 M  V30 6 C 0.4587 7.9784 0 0
1845 M  V30 7 * -0.4304 6.9517 0 0
1846 M  V30 8 O -0.4304 4.6417 0 0
1847 M  V30 9 C -1.7641 3.8717 0 0
1848 M  V30 END ATOM
1849 M  V30 BEGIN BOND
1850 M  V30 1 1 1 2
1851 M  V30 2 2 2 3
1852 M  V30 3 1 3 4
1853 M  V30 4 2 4 5
1854 M  V30 5 1 5 6
1855 M  V30 6 2 1 6
1856 M  V30 7 1 7 8 ENDPTS=(3 1 5 4) ATTACH=ANY
1857 M  V30 8 1 8 9
1858 M  V30 END BOND
1859 M  V30 END CTAB
1860 M  END
1861 )CTAB"_ctab;
1862     REQUIRE(m);
1863     {
1864       MolDraw2DSVG drawer(350, 300);
1865       drawer.drawMolecule(*m, "non-contiguous atoms");
1866       drawer.finishDrawing();
1867       auto text = drawer.getDrawingText();
1868       std::ofstream outs("testPositionVariation-3.svg");
1869       outs << text;
1870       outs.flush();
1871     }
1872   }
1873   SECTION("larger mol") {
1874     auto m = R"CTAB(
1875   Mrv2014 12092009152D
1876 
1877   0  0  0     0  0            999 V3000
1878 M  V30 BEGIN CTAB
1879 M  V30 COUNTS 23 24 0 0 0
1880 M  V30 BEGIN ATOM
1881 M  V30 1 C -0.875 8.7484 0 0
1882 M  V30 2 C -2.2087 7.9784 0 0
1883 M  V30 3 C -2.2087 6.4383 0 0
1884 M  V30 4 C -0.875 5.6683 0 0
1885 M  V30 5 N 0.4587 6.4383 0 0
1886 M  V30 6 C 0.4587 7.9784 0 0
1887 M  V30 7 * -0.4304 6.9517 0 0
1888 M  V30 8 O -0.4304 4.6417 0 0
1889 M  V30 9 C -1.7641 3.8717 0 0
1890 M  V30 10 C -3.5423 8.7484 0 0
1891 M  V30 11 C -4.876 7.9784 0 0
1892 M  V30 12 C -4.876 6.4383 0 0
1893 M  V30 13 C -3.5423 5.6683 0 0
1894 M  V30 14 C -4.876 11.0584 0 0
1895 M  V30 15 C -6.2097 10.2884 0 0
1896 M  V30 16 C -6.2097 8.7484 0 0
1897 M  V30 17 C -3.5423 10.2884 0 0
1898 M  V30 18 C -6.2097 13.3685 0 0
1899 M  V30 19 C -7.5433 12.5985 0 0
1900 M  V30 20 C -7.5433 11.0584 0 0
1901 M  V30 21 C -4.876 12.5985 0 0
1902 M  V30 22 * -5.5428 9.1334 0 0
1903 M  V30 23 C -7.3712 7.7304 0 0
1904 M  V30 END ATOM
1905 M  V30 BEGIN BOND
1906 M  V30 1 1 1 2
1907 M  V30 2 2 2 3
1908 M  V30 3 1 3 4
1909 M  V30 4 2 4 5
1910 M  V30 5 1 5 6
1911 M  V30 6 2 1 6
1912 M  V30 7 1 7 8 ENDPTS=(3 1 4 5) ATTACH=ANY
1913 M  V30 8 1 8 9
1914 M  V30 9 2 10 11
1915 M  V30 10 1 11 12
1916 M  V30 11 2 12 13
1917 M  V30 12 1 10 2
1918 M  V30 13 1 13 3
1919 M  V30 14 1 14 15
1920 M  V30 15 2 15 16
1921 M  V30 16 2 14 17
1922 M  V30 17 1 10 17
1923 M  V30 18 1 16 11
1924 M  V30 19 1 18 19
1925 M  V30 20 2 19 20
1926 M  V30 21 2 18 21
1927 M  V30 22 1 14 21
1928 M  V30 23 1 20 15
1929 M  V30 24 1 22 23 ENDPTS=(2 15 11) ATTACH=ANY
1930 M  V30 END BOND
1931 M  V30 END CTAB
1932 M  END
1933 )CTAB"_ctab;
1934     REQUIRE(m);
1935     {
1936       MolDraw2DSVG drawer(250, 200);
1937       drawer.drawMolecule(*m, "smaller");
1938       drawer.finishDrawing();
1939       auto text = drawer.getDrawingText();
1940       std::ofstream outs("testPositionVariation-4.svg");
1941       outs << text;
1942       outs.flush();
1943     }
1944   }
1945 }
1946 
1947 TEST_CASE("disable atom labels", "[feature]") {
1948   SECTION("basics") {
1949     auto m = "NCC(=O)O"_smiles;
1950     MolDraw2DSVG drawer(350, 300);
1951     MolDraw2DUtils::prepareMolForDrawing(*m);
1952     drawer.drawOptions().noAtomLabels = true;
1953     drawer.drawMolecule(*m);
1954     drawer.finishDrawing();
1955     auto text = drawer.getDrawingText();
1956     std::ofstream outs("testNoAtomLabels-1.svg");
1957     outs << text;
1958     outs.flush();
1959     CHECK(text.find("class='atom-0") == std::string::npos);
1960     CHECK(text.find("class='atom-3") == std::string::npos);
1961   }
1962 }
1963 
1964 TEST_CASE("drawing query bonds", "[queries]") {
1965   SECTION("basics") {
1966     auto m = R"CTAB(
1967   Mrv2014 12072005332D
1968 
1969   0  0  0     0  0            999 V3000
1970 M  V30 BEGIN CTAB
1971 M  V30 COUNTS 14 14 0 0 0
1972 M  V30 BEGIN ATOM
1973 M  V30 1 C 3.7917 -2.96 0 0
1974 M  V30 2 C 2.458 -3.73 0 0
1975 M  V30 3 C 2.458 -5.27 0 0
1976 M  V30 4 C 3.7917 -6.04 0 0
1977 M  V30 5 C 5.1253 -5.27 0 0
1978 M  V30 6 C 5.1253 -3.73 0 0
1979 M  V30 7 C 6.459 -2.96 0 0
1980 M  V30 8 C 3.7917 -7.58 0 0
1981 M  V30 9 C 4.8806 -8.669 0 0
1982 M  V30 10 C 4.482 -10.1565 0 0
1983 M  V30 11 C 6.459 -6.04 0 0
1984 M  V30 12 C 7.7927 -5.27 0 0
1985 M  V30 13 C 9.1263 -6.0399 0 0
1986 M  V30 14 C 9.1263 -7.5799 0 0
1987 M  V30 END ATOM
1988 M  V30 BEGIN BOND
1989 M  V30 1 1 2 3
1990 M  V30 2 1 4 5
1991 M  V30 3 1 1 6
1992 M  V30 4 5 1 2
1993 M  V30 5 6 5 6
1994 M  V30 6 7 3 4
1995 M  V30 7 8 6 7
1996 M  V30 8 1 4 8
1997 M  V30 9 1 8 9 TOPO=1
1998 M  V30 10 1 9 10 TOPO=2
1999 M  V30 11 1 5 11
2000 M  V30 12 1 12 13
2001 M  V30 13 2 11 12 TOPO=1
2002 M  V30 14 2 13 14 TOPO=2
2003 M  V30 END BOND
2004 M  V30 END CTAB
2005 M  END
2006 )CTAB"_ctab;
2007     REQUIRE(m);
2008     {
2009       MolDraw2DSVG drawer(350, 300);
2010       drawer.drawMolecule(*m);
2011       drawer.finishDrawing();
2012       auto text = drawer.getDrawingText();
2013       std::ofstream outs("testQueryBonds-1a.svg");
2014       outs << text;
2015       outs.flush();
2016     }
2017     {
2018       MolDraw2DSVG drawer(350, 300);
2019       m->getBondWithIdx(3)->setProp("bondNote", "S/D");
2020       m->getBondWithIdx(4)->setProp("bondNote", "S/A");
2021       m->getBondWithIdx(5)->setProp("bondNote", "D/A");
2022       m->getBondWithIdx(6)->setProp("bondNote", "Any");
2023       drawer.drawMolecule(*m);
2024       drawer.finishDrawing();
2025       auto text = drawer.getDrawingText();
2026       std::ofstream outs("testQueryBonds-1b.svg");
2027       outs << text;
2028       outs.flush();
2029     }
2030     {
2031       MolDraw2DSVG drawer(350, 300);
2032       std::vector<int> highlightAtoms = {0, 1, 2, 3, 4, 5, 7, 8, 9};
2033       std::vector<int> highlightBonds = {0, 3, 2, 4, 1, 5, 8, 9};
2034 
2035       drawer.drawMolecule(*m, "", &highlightAtoms, &highlightBonds);
2036       drawer.finishDrawing();
2037       auto text = drawer.getDrawingText();
2038       std::ofstream outs("testQueryBonds-1c.svg");
2039       outs << text;
2040       outs.flush();
2041     }
2042   }
2043   SECTION("smaller drawing") {
2044     auto m = R"CTAB(
2045   Mrv2014 12012004302D
2046 
2047   0  0  0     0  0            999 V3000
2048 M  V30 BEGIN CTAB
2049 M  V30 COUNTS 26 29 0 0 0
2050 M  V30 BEGIN ATOM
2051 M  V30 1 O 3.7917 -2.96 0 0
2052 M  V30 2 C 2.458 -3.73 0 0
2053 M  V30 3 C 2.458 -5.27 0 0
2054 M  V30 4 N 3.7917 -6.04 0 0
2055 M  V30 5 N 5.1253 -5.27 0 0
2056 M  V30 6 C 5.1253 -3.73 0 0
2057 M  V30 7 C 6.459 -2.96 0 0
2058 M  V30 8 C 3.7917 -7.58 0 0
2059 M  V30 9 C 4.8806 -8.669 0 0
2060 M  V30 10 C 4.482 -10.1565 0 0
2061 M  V30 11 C 1.1243 -2.9599 0 0
2062 M  V30 12 C -0.2093 -3.73 0 0
2063 M  V30 13 C -0.2093 -5.27 0 0
2064 M  V30 14 C 1.1243 -6.04 0 0
2065 M  V30 15 C -0.2093 -0.6499 0 0
2066 M  V30 16 C -1.543 -1.4199 0 0
2067 M  V30 17 C -1.543 -2.9599 0 0
2068 M  V30 18 C 1.1243 -1.4199 0 0
2069 M  V30 19 C -2.8767 -0.6499 0 0
2070 M  V30 20 C -4.2103 -1.4199 0 0
2071 M  V30 21 C -4.2103 -2.9599 0 0
2072 M  V30 22 C -2.8767 -3.73 0 0
2073 M  V30 23 C -5.544 -3.7299 0 0
2074 M  V30 24 C -6.8777 -2.9599 0 0
2075 M  V30 25 C -8.2114 -3.7299 0 0
2076 M  V30 26 C -9.5451 -2.9599 0 0
2077 M  V30 END ATOM
2078 M  V30 BEGIN BOND
2079 M  V30 1 1 2 3
2080 M  V30 2 1 4 5
2081 M  V30 3 1 1 6
2082 M  V30 4 5 1 2
2083 M  V30 5 6 5 6
2084 M  V30 6 7 3 4
2085 M  V30 7 8 6 7
2086 M  V30 8 1 4 8
2087 M  V30 9 1 8 9 TOPO=1
2088 M  V30 10 1 9 10 TOPO=2
2089 M  V30 11 1 12 13
2090 M  V30 12 1 13 14
2091 M  V30 13 1 14 3
2092 M  V30 14 1 11 2
2093 M  V30 15 1 15 16
2094 M  V30 16 1 16 17
2095 M  V30 17 2 15 18
2096 M  V30 18 1 11 18
2097 M  V30 19 1 17 12
2098 M  V30 20 2 12 11
2099 M  V30 21 1 19 20
2100 M  V30 22 2 20 21
2101 M  V30 23 1 21 22
2102 M  V30 24 2 19 16
2103 M  V30 25 2 22 17
2104 M  V30 26 1 21 23
2105 M  V30 27 1 23 24
2106 M  V30 28 1 24 25
2107 M  V30 29 1 25 26
2108 M  V30 END BOND
2109 M  V30 END CTAB
2110 M  END
2111 )CTAB"_ctab;
2112     REQUIRE(m);
2113     {
2114       MolDraw2DSVG drawer(250, 200);
2115       drawer.drawMolecule(*m);
2116       drawer.finishDrawing();
2117       auto text = drawer.getDrawingText();
2118       std::ofstream outs("testQueryBonds-2.svg");
2119       outs << text;
2120       outs.flush();
2121     }
2122   }
2123   SECTION("two linknodes") {
2124     auto m = R"CTAB(two linknodes
2125   Mrv2014 07072016412D
2126 
2127   0  0  0     0  0            999 V3000
2128 M  V30 BEGIN CTAB
2129 M  V30 COUNTS 7 7 0 0 0
2130 M  V30 BEGIN ATOM
2131 M  V30 1 C 8.25 12.1847 0 0
2132 M  V30 2 C 6.9164 12.9547 0 0
2133 M  V30 3 C 7.2366 14.4611 0 0
2134 M  V30 4 C 8.7681 14.622 0 0
2135 M  V30 5 C 9.3945 13.2151 0 0
2136 M  V30 6 O 8.25 10.6447 0 0
2137 M  V30 7 F 9.5382 15.9557 0 0
2138 M  V30 END ATOM
2139 M  V30 BEGIN BOND
2140 M  V30 1 1 1 2
2141 M  V30 2 1 2 3
2142 M  V30 3 1 4 5
2143 M  V30 4 1 1 5
2144 M  V30 5 1 3 4
2145 M  V30 6 1 1 6
2146 M  V30 7 1 4 7
2147 M  V30 END BOND
2148 M  V30 LINKNODE 1 3 2 1 2 1 5
2149 M  V30 LINKNODE 1 4 2 4 3 4 5
2150 M  V30 END CTAB
2151 M  END)CTAB"_ctab;
2152     std::vector<int> rotns = {0, 30, 60, 90, 120, 150, 180};
2153     for (auto rotn : rotns) {
2154       MolDraw2DSVG drawer(350, 300);
2155       drawer.drawOptions().rotate = (double)rotn;
2156       drawer.drawMolecule(*m);
2157       drawer.finishDrawing();
2158       auto text = drawer.getDrawingText();
2159       std::ofstream outs(
2160           (boost::format("testLinkNodes-2-%d.svg") % rotn).str());
2161       outs << text;
2162       outs.flush();
2163     }
2164   }
2165 }
2166 
2167 TEST_CASE("molecule annotations", "[extra]") {
2168   int panelHeight = -1;
2169   int panelWidth = -1;
2170   bool noFreeType = false;
2171 
2172   SECTION("basics") {
2173     auto m = "NCC(=O)O"_smiles;
2174     MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2175     MolDraw2DUtils::prepareMolForDrawing(*m);
2176     m->setProp(common_properties::molNote, "molecule note");
2177     drawer.drawMolecule(*m, "with note");
2178     drawer.finishDrawing();
2179     auto text = drawer.getDrawingText();
2180     std::ofstream outs("testMolAnnotations-1.svg");
2181     outs << text;
2182     outs.flush();
2183     CHECK(text.find("class='note'") != std::string::npos);
2184   }
2185   SECTION("chiral flag") {
2186     auto m = R"CTAB(
2187   Mrv2014 12152012512D
2188 
2189   0  0  0     0  0            999 V3000
2190 M  V30 BEGIN CTAB
2191 M  V30 COUNTS 8 8 0 0 1
2192 M  V30 BEGIN ATOM
2193 M  V30 1 C -0.6317 0.6787 0 0 CFG=2
2194 M  V30 2 C -1.7207 1.7677 0 0
2195 M  V30 3 C 0.4571 1.7677 0 0
2196 M  V30 4 C -0.6317 2.8566 0 0 CFG=1
2197 M  V30 5 C 0.1729 4.1698 0 0
2198 M  V30 6 N -0.5619 5.5231 0 0
2199 M  V30 7 C -1.4364 4.1698 0 0
2200 M  V30 8 C -0.6316 -0.8613 0 0
2201 M  V30 END ATOM
2202 M  V30 BEGIN BOND
2203 M  V30 1 1 1 2 CFG=3
2204 M  V30 2 1 1 3
2205 M  V30 3 1 4 3
2206 M  V30 4 1 4 2
2207 M  V30 5 1 4 5
2208 M  V30 6 1 5 6
2209 M  V30 7 1 4 7 CFG=1
2210 M  V30 8 1 1 8
2211 M  V30 END BOND
2212 M  V30 END CTAB
2213 M  END
2214 )CTAB"_ctab;
2215     {
2216       MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2217       drawer.drawMolecule(*m, "chiral flag set, option disabled");
2218       drawer.finishDrawing();
2219       auto text = drawer.getDrawingText();
2220       std::ofstream outs("testMolAnnotations-2a.svg");
2221       outs << text;
2222       outs.flush();
2223       CHECK(text.find("class='note'") == std::string::npos);
2224     }
2225     {
2226       MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2227       drawer.drawOptions().includeChiralFlagLabel = true;
2228       drawer.drawMolecule(*m, "chiral flag set, option enabled");
2229       drawer.finishDrawing();
2230       auto text = drawer.getDrawingText();
2231       std::ofstream outs("testMolAnnotations-2b.svg");
2232       outs << text;
2233       outs.flush();
2234       CHECK(text.find("class='note'") != std::string::npos);
2235     }
2236     {
2237       MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2238       drawer.drawOptions().includeChiralFlagLabel = true;
2239       m->clearProp(common_properties::_MolFileChiralFlag);
2240       drawer.drawMolecule(*m, "chiral flag not set, option enabled");
2241       drawer.finishDrawing();
2242       auto text = drawer.getDrawingText();
2243       std::ofstream outs("testMolAnnotations-2c.svg");
2244       outs << text;
2245       outs.flush();
2246       CHECK(text.find("class='note'") == std::string::npos);
2247     }
2248   }
2249   SECTION("simplified stereo 1") {
2250     {
2251       auto m = "C[C@H](F)[C@@H](F)[C@@H](C)Cl |o1:3,5,1|"_smiles;
2252       MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2253       MolDraw2DUtils::prepareMolForDrawing(*m);
2254       drawer.drawOptions().addStereoAnnotation = true;
2255       drawer.drawMolecule(*m, "enhanced no flag");
2256       drawer.finishDrawing();
2257       auto text = drawer.getDrawingText();
2258       std::ofstream outs("testMolAnnotations-3a.svg");
2259       outs << text;
2260       outs.flush();
2261     }
2262     {
2263       auto m = "C[C@H](F)[C@@H](F)[C@@H](C)Cl |o1:3,5,1|"_smiles;
2264       MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2265       MolDraw2DUtils::prepareMolForDrawing(*m);
2266       drawer.drawOptions().addStereoAnnotation = true;
2267       drawer.drawOptions().simplifiedStereoGroupLabel = true;
2268       drawer.drawMolecule(*m, "enhanced with flag");
2269       drawer.finishDrawing();
2270       auto text = drawer.getDrawingText();
2271       std::ofstream outs("testMolAnnotations-3b.svg");
2272       outs << text;
2273       outs.flush();
2274     }
2275     {
2276       auto m = "C[C@H](F)[C@@H](F)[C@@H](C)Cl |&1:3,5,1|"_smiles;
2277       MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2278       MolDraw2DUtils::prepareMolForDrawing(*m);
2279       drawer.drawOptions().addStereoAnnotation = true;
2280       drawer.drawOptions().simplifiedStereoGroupLabel = true;
2281       drawer.drawMolecule(*m, "enhanced & with flag");
2282       drawer.finishDrawing();
2283       auto text = drawer.getDrawingText();
2284       std::ofstream outs("testMolAnnotations-3c.svg");
2285       outs << text;
2286       outs.flush();
2287     }
2288   }
2289   SECTION("simplified stereo 2") {
2290     auto m = "C[C@H](F)[C@@H](F)[C@@H](C)Cl |o1:3,5,o2:1|"_smiles;
2291     MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2292     drawer.drawOptions().addStereoAnnotation = true;
2293     drawer.drawOptions().simplifiedStereoGroupLabel = true;
2294     MolDraw2DUtils::prepareMolForDrawing(*m);
2295     drawer.drawMolecule(*m, "multi-groups");
2296     drawer.finishDrawing();
2297     auto text = drawer.getDrawingText();
2298     std::ofstream outs("testMolAnnotations-3d.svg");
2299     outs << text;
2300     outs.flush();
2301   }
2302   SECTION("label placement") {
2303     auto m = R"CTAB(
2304   Mrv2014 12162004412D
2305 
2306   0  0  0     0  0            999 V3000
2307 M  V30 BEGIN CTAB
2308 M  V30 COUNTS 16 15 0 0 0
2309 M  V30 BEGIN ATOM
2310 M  V30 1 C -9.2917 3.5833 0 0
2311 M  V30 2 C -7.958 4.3533 0 0 CFG=2
2312 M  V30 3 C -6.6243 3.5833 0 0 CFG=1
2313 M  V30 4 C -5.2906 4.3533 0 0 CFG=2
2314 M  V30 5 Cl -7.958 5.8933 0 0
2315 M  V30 6 F -6.6243 2.0433 0 0
2316 M  V30 7 F -3.957 3.5833 0 0
2317 M  V30 8 C -5.2906 5.8933 0 0
2318 M  V30 9 C -3.957 6.6633 0 0
2319 M  V30 10 C -3.957 8.2033 0 0
2320 M  V30 11 C -2.6233 8.9733 0 0
2321 M  V30 12 C -2.6233 5.8933 0 0
2322 M  V30 13 C -5.2906 8.9733 0 0
2323 M  V30 14 C -2.6233 10.5133 0 0
2324 M  V30 15 C -1.2896 8.2033 0 0
2325 M  V30 16 C -1.2896 6.6633 0 0
2326 M  V30 END ATOM
2327 M  V30 BEGIN BOND
2328 M  V30 1 1 1 2
2329 M  V30 2 1 2 3
2330 M  V30 3 1 3 4
2331 M  V30 4 1 2 5 CFG=1
2332 M  V30 5 1 3 6 CFG=1
2333 M  V30 6 1 4 7 CFG=1
2334 M  V30 7 1 4 8
2335 M  V30 8 1 8 9
2336 M  V30 9 1 9 10
2337 M  V30 10 1 10 11
2338 M  V30 11 1 9 12
2339 M  V30 12 1 10 13
2340 M  V30 13 1 11 14
2341 M  V30 14 1 11 15
2342 M  V30 15 1 12 16
2343 M  V30 END BOND
2344 M  V30 BEGIN COLLECTION
2345 M  V30 MDLV30/STEREL1 ATOMS=(3 2 3 4)
2346 M  V30 END COLLECTION
2347 M  V30 END CTAB
2348 M  END
2349 )CTAB"_ctab;
2350     MolDraw2DSVG drawer(350, 300, panelHeight, panelWidth, noFreeType);
2351     drawer.drawOptions().addStereoAnnotation = true;
2352     drawer.drawOptions().simplifiedStereoGroupLabel = true;
2353     drawer.drawMolecule(*m, "label crowding");
2354     drawer.finishDrawing();
2355     auto text = drawer.getDrawingText();
2356     std::ofstream outs("testMolAnnotations-4a.svg");
2357     outs << text;
2358     outs.flush();
2359   }
2360 }
2361 
2362 TEST_CASE("draw link nodes", "[extras]") {
2363   SECTION("one linknode") {
2364     auto m = R"CTAB(one linknode
2365   Mrv2007 06222005102D
2366 
2367   0  0  0     0  0            999 V3000
2368 M  V30 BEGIN CTAB
2369 M  V30 COUNTS 6 6 0 0 0
2370 M  V30 BEGIN ATOM
2371 M  V30 1 C 8.25 12.1847 0 0
2372 M  V30 2 C 6.9164 12.9547 0 0
2373 M  V30 3 C 6.9164 14.4947 0 0
2374 M  V30 4 C 9.5836 14.4947 0 0
2375 M  V30 5 C 9.5836 12.9547 0 0
2376 M  V30 6 O 8.25 10.6447 0 0
2377 M  V30 END ATOM
2378 M  V30 BEGIN BOND
2379 M  V30 1 1 1 2
2380 M  V30 2 1 2 3
2381 M  V30 3 1 4 5
2382 M  V30 4 1 1 5
2383 M  V30 5 1 3 4
2384 M  V30 6 1 1 6
2385 M  V30 END BOND
2386 M  V30 LINKNODE 1 4 2 1 2 1 5
2387 M  V30 END CTAB
2388 M  END)CTAB"_ctab;
2389     std::vector<int> rotns = {0, 30, 60, 90, 120, 150, 180};
2390     for (auto rotn : rotns) {
2391       MolDraw2DSVG drawer(350, 300);
2392       drawer.drawOptions().rotate = (double)rotn;
2393       drawer.drawMolecule(*m);
2394       drawer.finishDrawing();
2395       auto text = drawer.getDrawingText();
2396       std::ofstream outs(
2397           (boost::format("testLinkNodes-1-%d.svg") % rotn).str());
2398       outs << text;
2399       outs.flush();
2400     }
2401   }
2402 }
2403 
2404 TEST_CASE("Github #3744: Double bonds incorrectly drawn outside the ring",
2405           "[drawing]") {
2406   SECTION("SVG") {
2407     ROMOL_SPTR m1(MolBlockToMol(R"CTAB(
2408      RDKit          2D
2409 
2410   6  6  0  0  0  0  0  0  0  0999 V2000
2411     0.0684   -1.2135    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2412     1.4949   -0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2413     1.4949    0.7500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2414     0.0684    1.2135    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2415    -0.8133    0.0000    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
2416    -2.3133   -0.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2417   1  2  2  0
2418   2  3  1  0
2419   3  4  2  0
2420   4  5  1  0
2421   5  6  1  0
2422   5  1  1  0
2423 M  END)CTAB"));
2424     REQUIRE(m1);
2425     MolDraw2DSVG drawer(400, 300);
2426     drawer.drawMolecule(*m1);
2427     drawer.finishDrawing();
2428     std::string text = drawer.getDrawingText();
2429     std::ofstream outs("testGithub3744.svg");
2430     outs << text;
2431     outs.flush();
2432     std::vector<std::string> bond0;
2433     std::vector<std::string> bond2;
2434     std::istringstream ss(text);
2435     std::string line;
2436     while (std::getline(ss, line)) {
2437       if (line.find("bond-0") != std::string::npos) {
2438         bond0.push_back(line);
2439       } else if (line.find("bond-2") != std::string::npos) {
2440         bond2.push_back(line);
2441       }
2442     }
2443     CHECK(bond0.size() == 2);
2444     CHECK(bond2.size() == 2);
2445     std::regex regex(
2446         "^.*d='M\\s+(\\d+\\.\\d+),(\\d+\\.\\d+)\\s+L\\s+(\\d+\\.\\d+),(\\d+\\."
2447         "\\d+)'.*$");
2448     std::smatch bond0OuterMatch;
2449     REQUIRE(std::regex_match(bond0[0], bond0OuterMatch, regex));
2450     REQUIRE(bond0OuterMatch.size() == 5);
2451     std::smatch bond0InnerMatch;
2452     REQUIRE(std::regex_match(bond0[1], bond0InnerMatch, regex));
2453     REQUIRE(bond0InnerMatch.size() == 5);
2454     std::smatch bond2OuterMatch;
2455     REQUIRE(std::regex_match(bond2[0], bond2OuterMatch, regex));
2456     REQUIRE(bond2OuterMatch.size() == 5);
2457     std::smatch bond2InnerMatch;
2458     REQUIRE(std::regex_match(bond2[1], bond2InnerMatch, regex));
2459     REQUIRE(bond2InnerMatch.size() == 5);
2460     RDGeom::Point2D bond0InnerCtd(
2461         RDGeom::Point2D(std::stof(bond0InnerMatch[1]),
2462                         std::stof(bond0InnerMatch[2])) +
2463         RDGeom::Point2D(std::stof(bond0InnerMatch[3]),
2464                         std::stof(bond0InnerMatch[4])) /
2465             2.0);
2466     RDGeom::Point2D bond0OuterCtd(
2467         RDGeom::Point2D(std::stof(bond0OuterMatch[1]),
2468                         std::stof(bond0OuterMatch[2])) +
2469         RDGeom::Point2D(std::stof(bond0OuterMatch[3]),
2470                         std::stof(bond0OuterMatch[4])) /
2471             2.0);
2472     RDGeom::Point2D bond2InnerCtd(
2473         RDGeom::Point2D(std::stof(bond2InnerMatch[1]),
2474                         std::stof(bond2InnerMatch[2])) +
2475         RDGeom::Point2D(std::stof(bond2InnerMatch[3]),
2476                         std::stof(bond2InnerMatch[4])) /
2477             2.0);
2478     RDGeom::Point2D bond2OuterCtd(
2479         RDGeom::Point2D(std::stof(bond2OuterMatch[1]),
2480                         std::stof(bond2OuterMatch[2])) +
2481         RDGeom::Point2D(std::stof(bond2OuterMatch[3]),
2482                         std::stof(bond2OuterMatch[4])) /
2483             2.0);
2484     // we look at the two double bonds of pyrrole
2485     // we check that the ratio between the distance of the centroids of the
2486     // outer bonds and the distance of the centroids of the inner bonds is at
2487     // least 1.3, otherwise the inner bonds are not actually inside the ring.
2488     float outerBondsDistance = (bond0OuterCtd - bond2OuterCtd).length();
2489     float innerBondsDistance = (bond0InnerCtd - bond2InnerCtd).length();
2490     CHECK(outerBondsDistance / innerBondsDistance > 1.3f);
2491   }
2492 }
2493 
2494 TEST_CASE("draw atom list queries", "[extras]") {
2495   SECTION("atom list") {
2496     auto m = R"CTAB(
2497   Mrv2102 02112115002D
2498 
2499   0  0  0     0  0            999 V3000
2500 M  V30 BEGIN CTAB
2501 M  V30 COUNTS 3 3 0 0 0
2502 M  V30 BEGIN ATOM
2503 M  V30 1 [N,O,S] 9.2083 12.8058 0 0
2504 M  V30 2 C 8.4383 11.4721 0 0
2505 M  V30 3 C 9.9783 11.4721 0 0
2506 M  V30 END ATOM
2507 M  V30 BEGIN BOND
2508 M  V30 1 1 1 2
2509 M  V30 2 1 3 1
2510 M  V30 3 1 2 3
2511 M  V30 END BOND
2512 M  V30 END CTAB
2513 M  END
2514 )CTAB"_ctab;
2515     REQUIRE(m);
2516     MolDraw2DSVG drawer(350, 300);
2517     drawer.drawMolecule(*m, "atom list");
2518     drawer.finishDrawing();
2519     auto text = drawer.getDrawingText();
2520     std::ofstream outs("testAtomLists-1.svg");
2521     outs << text;
2522     outs.flush();
2523   }
2524 
2525   SECTION("NOT atom list") {
2526     auto m = R"CTAB(
2527   Mrv2102 02112115032D
2528 
2529   0  0  0     0  0            999 V3000
2530 M  V30 BEGIN CTAB
2531 M  V30 COUNTS 3 3 0 0 0
2532 M  V30 BEGIN ATOM
2533 M  V30 1 "NOT [N,O,S]" 9.2083 12.8058 0 0
2534 M  V30 2 C 8.4383 11.4721 0 0
2535 M  V30 3 C 9.9783 11.4721 0 0
2536 M  V30 END ATOM
2537 M  V30 BEGIN BOND
2538 M  V30 1 1 1 2
2539 M  V30 2 1 3 1
2540 M  V30 3 1 2 3
2541 M  V30 END BOND
2542 M  V30 END CTAB
2543 M  END
2544 )CTAB"_ctab;
2545     REQUIRE(m);
2546     MolDraw2DSVG drawer(350, 300);
2547     drawer.drawMolecule(*m, "NOT atom list");
2548     drawer.finishDrawing();
2549     auto text = drawer.getDrawingText();
2550     std::ofstream outs("testAtomLists-2.svg");
2551     outs << text;
2552     outs.flush();
2553   }
2554 }
2555 
2556 TEST_CASE("test the options that toggle isotope labels", "[drawing]") {
2557   SECTION("test all permutations") {
2558     auto m = "[1*]c1cc([2*])c([3*])c[14c]1"_smiles;
2559     REQUIRE(m);
2560     std::regex regex(R"regex(<text\s+.*>\d</text>)regex");
2561     std::smatch match;
2562     std::string line;
2563     {
2564       MolDraw2DSVG drawer(300, 300, -1, -1, true);
2565       drawer.drawMolecule(*m);
2566       drawer.finishDrawing();
2567       std::string textIsoDummyIso = drawer.getDrawingText();
2568       std::ofstream outs("testIsoDummyIso.svg");
2569       outs << textIsoDummyIso;
2570       outs.flush();
2571       size_t nIsoDummyIso = std::distance(
2572           std::sregex_token_iterator(textIsoDummyIso.begin(),
2573                                      textIsoDummyIso.end(), regex),
2574           std::sregex_token_iterator());
2575       CHECK(nIsoDummyIso == 5);
2576     }
2577     {
2578       MolDraw2DSVG drawer(300, 300, -1, -1, true);
2579       drawer.drawOptions().isotopeLabels = false;
2580       drawer.drawMolecule(*m);
2581       drawer.finishDrawing();
2582       std::string textNoIsoDummyIso = drawer.getDrawingText();
2583       std::ofstream outs("testNoIsoDummyIso.svg");
2584       outs << textNoIsoDummyIso;
2585       outs.flush();
2586       size_t nNoIsoDummyIso = std::distance(
2587           std::sregex_token_iterator(textNoIsoDummyIso.begin(),
2588                                      textNoIsoDummyIso.end(), regex, 1),
2589           std::sregex_token_iterator());
2590       CHECK(nNoIsoDummyIso == 3);
2591     }
2592     {
2593       MolDraw2DSVG drawer(300, 300, -1, -1, true);
2594       drawer.drawOptions().dummyIsotopeLabels = false;
2595       drawer.drawMolecule(*m);
2596       drawer.finishDrawing();
2597       std::string textIsoNoDummyIso = drawer.getDrawingText();
2598       std::ofstream outs("testIsoNoDummyIso.svg");
2599       outs << textIsoNoDummyIso;
2600       outs.flush();
2601       size_t nIsoNoDummyIso = std::distance(
2602           std::sregex_token_iterator(textIsoNoDummyIso.begin(),
2603                                      textIsoNoDummyIso.end(), regex, 1),
2604           std::sregex_token_iterator());
2605       CHECK(nIsoNoDummyIso == 2);
2606     }
2607     {
2608       MolDraw2DSVG drawer(300, 300, -1, -1, true);
2609       drawer.drawOptions().isotopeLabels = false;
2610       drawer.drawOptions().dummyIsotopeLabels = false;
2611       drawer.drawMolecule(*m);
2612       drawer.finishDrawing();
2613       std::string textNoIsoNoDummyIso = drawer.getDrawingText();
2614       std::ofstream outs("testNoIsoNoDummyIso.svg");
2615       outs << textNoIsoNoDummyIso;
2616       outs.flush();
2617       size_t nNoIsoNoDummyIso = std::distance(
2618           std::sregex_token_iterator(textNoIsoNoDummyIso.begin(),
2619                                      textNoIsoNoDummyIso.end(), regex, 1),
2620           std::sregex_token_iterator());
2621       CHECK(nNoIsoNoDummyIso == 0);
2622     }
2623   }
2624   SECTION("test that D/T show up even if isotope labels are hidden") {
2625     auto m = "C([1H])([2H])([3H])[H]"_smiles;
2626     std::regex regex(R"regex(<text\s+.*>[DT]</text>)regex");
2627     std::smatch match;
2628     REQUIRE(m);
2629     std::string line;
2630     MolDraw2DSVG drawer(300, 300, -1, -1, true);
2631     drawer.drawOptions().isotopeLabels = false;
2632     drawer.drawOptions().dummyIsotopeLabels = false;
2633     drawer.drawOptions().atomLabelDeuteriumTritium = true;
2634     drawer.drawMolecule(*m);
2635     drawer.finishDrawing();
2636     std::string textDeuteriumTritium = drawer.getDrawingText();
2637     std::ofstream outs("testDeuteriumTritium.svg");
2638     outs << textDeuteriumTritium;
2639     outs.flush();
2640     size_t nDeuteriumTritium = std::distance(
2641         std::sregex_token_iterator(textDeuteriumTritium.begin(),
2642                                    textDeuteriumTritium.end(), regex, 1),
2643         std::sregex_token_iterator());
2644     CHECK(nDeuteriumTritium == 2);
2645   }
2646 }
2647 
2648 TEST_CASE("draw hydrogen bonds", "[drawing]") {
2649   SECTION("basics") {
2650     auto m = R"CTAB(
2651   Mrv2014 03022114422D
2652 
2653   0  0  0     0  0            999 V3000
2654 M  V30 BEGIN CTAB
2655 M  V30 COUNTS 8 8 0 0 0
2656 M  V30 BEGIN ATOM
2657 M  V30 1 C -5.4583 -0.125 0 0
2658 M  V30 2 C -4.1247 0.645 0 0
2659 M  V30 3 C -2.791 -0.125 0 0
2660 M  V30 4 C -1.4573 0.645 0 0
2661 M  V30 5 O -2.791 -1.665 0 0
2662 M  V30 6 C -6.792 0.645 0 0
2663 M  V30 7 O -5.4583 -1.665 0 0
2664 M  V30 8 H -4.1247 -2.435 0 0
2665 M  V30 END ATOM
2666 M  V30 BEGIN BOND
2667 M  V30 1 1 1 2
2668 M  V30 2 1 2 3
2669 M  V30 3 1 3 4
2670 M  V30 4 2 3 5
2671 M  V30 5 1 1 6
2672 M  V30 6 1 1 7
2673 M  V30 7 1 7 8
2674 M  V30 8 10 5 8
2675 M  V30 END BOND
2676 M  V30 END CTAB
2677 M  END
2678 )CTAB"_ctab;
2679     REQUIRE(m);
2680 
2681     MolDraw2DSVG drawer(300, 300);
2682     drawer.drawMolecule(*m);
2683     drawer.finishDrawing();
2684     std::ofstream outs("testHydrogenBonds1.svg");
2685     outs << drawer.getDrawingText();
2686     outs.flush();
2687   }
2688   SECTION("from CXSMILES") {
2689     auto m = "CC1O[H]O=C(C)C1 |H:4.3|"_smiles;
2690     REQUIRE(m);
2691 
2692     MolDraw2DSVG drawer(300, 300);
2693     drawer.drawMolecule(*m);
2694     drawer.finishDrawing();
2695     std::ofstream outs("testHydrogenBonds2.svg");
2696     outs << drawer.getDrawingText();
2697     outs.flush();
2698   }
2699 }
2700 
2701 TEST_CASE("github #3912: cannot draw atom lists from SMARTS", "[query][bug]") {
2702   SECTION("original") {
2703     auto m = "C-[N,O]"_smarts;
2704     REQUIRE(m);
2705     int panelWidth = -1;
2706     int panelHeight = -1;
2707     bool noFreeType = true;
2708     MolDraw2DSVG drawer(300, 300, panelWidth, panelHeight, noFreeType);
2709     drawer.drawMolecule(*m);
2710     drawer.finishDrawing();
2711     std::ofstream outs("testGithub3912.1.svg");
2712     auto txt = drawer.getDrawingText();
2713     outs << txt;
2714     outs.flush();
2715     CHECK(txt.find(">N<") != std::string::npos);
2716     CHECK(txt.find(">O<") != std::string::npos);
2717     CHECK(txt.find(">!<") == std::string::npos);
2718   }
2719   SECTION("negated") {
2720     auto m = "C-[N,O]"_smarts;
2721     REQUIRE(m);
2722     REQUIRE(m->getAtomWithIdx(1)->hasQuery());
2723     m->getAtomWithIdx(1)->getQuery()->setNegation(true);
2724     int panelWidth = -1;
2725     int panelHeight = -1;
2726     bool noFreeType = true;
2727     MolDraw2DSVG drawer(300, 300, panelWidth, panelHeight, noFreeType);
2728     drawer.drawMolecule(*m);
2729     drawer.finishDrawing();
2730     std::ofstream outs("testGithub3912.2.svg");
2731     auto txt = drawer.getDrawingText();
2732     outs << txt;
2733     outs.flush();
2734     CHECK(txt.find(">N<") != std::string::npos);
2735     CHECK(txt.find(">O<") != std::string::npos);
2736     CHECK(txt.find(">!<") != std::string::npos);
2737   }
2738 }
2739 
2740 TEST_CASE("github #2976: kekulizing reactions when drawing", "[reactions]") {
2741   SECTION("basics") {
2742     bool asSmiles = true;
2743     std::unique_ptr<ChemicalReaction> rxn{
2744         RxnSmartsToChemicalReaction("c1ccccc1>>c1ncccc1", nullptr, asSmiles)};
2745     MolDraw2DSVG drawer(450, 200);
2746     drawer.drawReaction(*rxn);
2747     drawer.finishDrawing();
2748     std::ofstream outs("testGithub2976.svg");
2749     auto txt = drawer.getDrawingText();
2750     outs << txt;
2751     outs.flush();
2752   }
2753 }
2754 
2755 TEST_CASE("preserve Reaction coordinates", "[reactions]") {
2756   SECTION("basics") {
2757     std::string data = R"RXN($RXN
2758 
2759   Mrv16822    031301211645
2760 
2761   2  2  1
2762 $MOL
2763 
2764   Mrv1682203132116452D
2765 
2766   3  2  0  0  0  0            999 V2000
2767    -4.3304    2.5893    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
2768    -4.3304    1.7643    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2769    -3.5054    1.7643    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2770   1  2  1  0  0  0  0
2771   2  3  1  0  0  0  0
2772 M  END
2773 $MOL
2774 
2775   Mrv1682203132116452D
2776 
2777   2  1  0  0  0  0            999 V2000
2778    -2.1652    2.6339    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
2779    -2.1652    1.8089    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2780   1  2  1  0  0  0  0
2781 M  END
2782 $MOL
2783 
2784   Mrv1682203132116452D
2785 
2786   3  2  0  0  0  0            999 V2000
2787     3.6109    1.9512    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2788     2.7859    1.9512    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2789     2.7859    2.7762    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
2790   2  1  1  0  0  0  0
2791   3  2  1  0  0  0  0
2792 M  END
2793 $MOL
2794 
2795   Mrv1682203132116452D
2796 
2797   2  1  0  0  0  0            999 V2000
2798     4.9511    1.9959    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2799     4.9511    2.8209    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
2800   2  1  1  0  0  0  0
2801 M  END
2802 $MOL
2803 
2804   Mrv1682203132116452D
2805 
2806   2  1  0  0  0  0            999 V2000
2807    -0.3571    2.7232    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
2808    -0.4003    3.5471    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
2809   1  2  1  0  0  0  0
2810 M  END
2811 )RXN";
2812     std::unique_ptr<ChemicalReaction> rxn{RxnBlockToChemicalReaction(data)};
2813     MolDraw2DSVG drawer(450, 200);
2814     drawer.drawReaction(*rxn);
2815     drawer.finishDrawing();
2816     std::ofstream outs("testReactionCoords.svg");
2817     auto txt = drawer.getDrawingText();
2818     outs << txt;
2819     outs.flush();
2820 
2821     // the reaction is drawn with some bonds vertical, make sure they remain
2822     // vertical
2823     {
2824       std::regex regex("class='bond-0.*? d='M (\\d+\\.\\d+).* L (\\d+\\.\\d+)");
2825       std::smatch bondMatch;
2826       REQUIRE(std::regex_search(txt, bondMatch, regex));
2827       REQUIRE(bondMatch.size() == 3);  // match both halves of the bond
2828       CHECK(bondMatch[1].str() == bondMatch[2].str());
2829     }
2830     {
2831       std::regex regex("class='bond-2.*? d='M (\\d+\\.\\d+).* L (\\d+\\.\\d+)");
2832       std::smatch bondMatch;
2833       REQUIRE(std::regex_search(txt, bondMatch, regex));
2834       REQUIRE(bondMatch.size() == 3);  // match both halves of the bond
2835       CHECK(bondMatch[1].str() == bondMatch[2].str());
2836     }
2837     {
2838       std::regex regex("class='bond-4.*? d='M (\\d+\\.\\d+).* L (\\d+\\.\\d+)");
2839       std::smatch bondMatch;
2840       REQUIRE(std::regex_search(txt, bondMatch, regex));
2841       REQUIRE(bondMatch.size() == 3);  // match both halves of the bond
2842       CHECK(bondMatch[1].str() == bondMatch[2].str());
2843     }
2844   }
2845 }
2846 TEST_CASE("support annotation colors", "[drawing]") {
2847   SECTION("basics") {
2848     auto m = "CCCO"_smiles;
2849     REQUIRE(m);
2850     int panelWidth = -1;
2851     int panelHeight = -1;
2852     bool noFreeType = true;
2853     MolDraw2DSVG drawer(300, 300, panelWidth, panelHeight, noFreeType);
drawOptions()2854     drawer.drawOptions().annotationColour = DrawColour{0, 0, 1, 1};
2855     drawer.drawOptions().addAtomIndices = true;
2856     drawer.drawMolecule(*m, "blue annotations");
2857     drawer.finishDrawing();
2858     std::ofstream outs("testAnnotationColors.svg");
2859     auto txt = drawer.getDrawingText();
2860     outs << txt;
2861     outs.flush();
2862     CHECK(txt.find("fill:#0000FF' >2<") != std::string::npos);
2863   }
2864 }
2865