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