// // Copyright (C) 2015-2019 Greg Landrum // // @@ All Rights Reserved @@ // This file is part of the RDKit. // The contents are covered by the terms of the BSD license // which is included in the file license.txt, found at the root // of the RDKit source tree. // // derived from Dave Cosgrove's MolDraw2D // #include #include #include #include #ifdef RDK_BUILD_FREETYPE_SUPPORT #include #else #include #endif #include #include #include namespace RDKit { namespace { template void outputTagClasses(const t_obj *obj, std::ostream &d_os, const std::string &d_activeClass) { if (!d_activeClass.empty()) { d_os << " " << d_activeClass; } if (obj->hasProp("_tagClass")) { std::string value; obj->getProp("_tagClass", value); std::replace(value.begin(), value.end(), '\"', '_'); std::replace(value.begin(), value.end(), '\'', '_'); std::replace(value.begin(), value.end(), '.', '_'); d_os << " " << value; } } template void outputMetaData(const t_obj *obj, std::ostream &d_os, const std::string &d_activeClass) { RDUNUSED_PARAM(d_activeClass); std::string value; for (const auto &prop : obj->getPropList()) { if (prop.length() < 11 || prop.rfind("_metaData-", 0) != 0) { continue; } obj->getProp(prop, value); boost::replace_all(value, "\"", """); d_os << " " << prop.substr(10) << "=\"" << value << "\""; } } } // namespace std::string DrawColourToSVG(const DrawColour &col) { const char *convert = "0123456789ABCDEF"; std::string res(7, ' '); res[0] = '#'; unsigned int v; unsigned int i = 1; v = int(255 * col.r); if (v > 255) { throw ValueErrorException( "elements of the color should be between 0 and 1"); } res[i++] = convert[v / 16]; res[i++] = convert[v % 16]; v = int(255 * col.g); if (v > 255) { throw ValueErrorException( "elements of the color should be between 0 and 1"); } res[i++] = convert[v / 16]; res[i++] = convert[v % 16]; v = int(255 * col.b); if (v > 255) { throw ValueErrorException( "elements of the color should be between 0 and 1"); } res[i++] = convert[v / 16]; res[i++] = convert[v % 16]; return res; } // **************************************************************************** void MolDraw2DSVG::initDrawing() { d_os << "\n"; d_os << "\n"} % width() % height(); d_os << "\n"; // d_os<<""; } // **************************************************************************** void MolDraw2DSVG::initTextDrawer(bool noFreetype) { double max_fnt_sz = drawOptions().maxFontSize; double min_fnt_sz = drawOptions().minFontSize; if (noFreetype) { text_drawer_.reset( new DrawTextSVG(max_fnt_sz, min_fnt_sz, d_os, d_activeClass)); } else { #ifdef RDK_BUILD_FREETYPE_SUPPORT try { text_drawer_.reset(new DrawTextFTSVG( max_fnt_sz, min_fnt_sz, drawOptions().fontFile, d_os, d_activeClass)); } catch (std::runtime_error &e) { BOOST_LOG(rdWarningLog) << e.what() << std::endl << "Falling back to native SVG text handling." << std::endl; text_drawer_.reset( new DrawTextSVG(max_fnt_sz, min_fnt_sz, d_os, d_activeClass)); } #else text_drawer_.reset( new DrawTextSVG(max_fnt_sz, min_fnt_sz, d_os, d_activeClass)); #endif } } // **************************************************************************** void MolDraw2DSVG::finishDrawing() { // d_os << ""; d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::setColour(const DrawColour &col) { MolDraw2D::setColour(col); } // **************************************************************************** void MolDraw2DSVG::drawWavyLine(const Point2D &cds1, const Point2D &cds2, const DrawColour &col1, const DrawColour &col2, unsigned int nSegments, double vertOffset) { PRECONDITION(nSegments > 1, "too few segments"); RDUNUSED_PARAM(col2); if (nSegments % 2) { ++nSegments; // we're going to assume an even number of segments } setColour(col1); Point2D delta = (cds2 - cds1); Point2D perp(delta.y, -delta.x); perp.normalize(); perp *= vertOffset; delta /= nSegments; Point2D c1 = getDrawCoords(cds1); std::string col = DrawColourToSVG(colour()); double width = getDrawLineWidth(); d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::drawBond( const ROMol &mol, const Bond *bond, int at1_idx, int at2_idx, const std::vector *highlight_atoms, const std::map *highlight_atom_map, const std::vector *highlight_bonds, const std::map *highlight_bond_map, const std::vector> *bond_colours) { PRECONDITION(bond, "bad bond"); std::string o_class = d_activeClass; if (!d_activeClass.empty()) { d_activeClass += " "; } d_activeClass += boost::str(boost::format("bond-%d") % bond->getIdx()); MolDraw2D::drawBond(mol, bond, at1_idx, at2_idx, highlight_atoms, highlight_atom_map, highlight_bonds, highlight_bond_map, bond_colours); d_activeClass = o_class; }; // **************************************************************************** void MolDraw2DSVG::drawAtomLabel(int atom_num, const DrawColour &draw_colour) { std::string o_class = d_activeClass; if (!d_activeClass.empty()) { d_activeClass += " "; } d_activeClass += boost::str(boost::format("atom-%d") % atom_num); MolDraw2D::drawAtomLabel(atom_num, draw_colour); d_activeClass = o_class; } // **************************************************************************** void MolDraw2DSVG::drawAnnotation(const AnnotationType &annot) { std::string o_class = d_activeClass; if (!d_activeClass.empty()) { d_activeClass += " "; } d_activeClass += "note"; MolDraw2D::drawAnnotation(annot); d_activeClass = o_class; } // **************************************************************************** void MolDraw2DSVG::drawLine(const Point2D &cds1, const Point2D &cds2) { Point2D c1 = getDrawCoords(cds1); Point2D c2 = getDrawCoords(cds2); std::string col = DrawColourToSVG(colour()); double width = getDrawLineWidth(); std::string dashString = ""; const DashPattern &dashes = dash(); if (dashes.size()) { std::stringstream dss; dss << ";stroke-dasharray:"; std::copy(dashes.begin(), dashes.end() - 1, std::ostream_iterator(dss, ",")); dss << dashes.back(); dashString = dss.str(); } d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::drawPolygon(const std::vector &cds) { PRECONDITION(cds.size() >= 3, "must have at least three points"); std::string col = DrawColourToSVG(colour()); double width = getDrawLineWidth(); std::string dashString = ""; d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::drawEllipse(const Point2D &cds1, const Point2D &cds2) { Point2D c1 = getDrawCoords(cds1); Point2D c2 = getDrawCoords(cds2); double w = c2.x - c1.x; double h = c2.y - c1.y; double cx = c1.x + w / 2; double cy = c1.y + h / 2; w = w > 0 ? w : -1 * w; h = h > 0 ? h : -1 * h; std::string col = DrawColourToSVG(colour()); double width = getDrawLineWidth(); std::string dashString = ""; d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::clearDrawing() { std::string col = DrawColourToSVG(drawOptions().backgroundColour); d_os << " \n"; } // **************************************************************************** static const char *RDKIT_SVG_VERSION = "0.9"; void MolDraw2DSVG::addMoleculeMetadata(const ROMol &mol, int confId) const { PRECONDITION(d_os, "no output stream"); d_os << "" << std::endl; d_os << "" << std::endl; for (const auto atom : mol.atoms()) { d_os << "getIdx() + 1 << "\""; bool doKekule = false, allHsExplicit = true, isomericSmiles = true; d_os << " atom-smiles=\"" << SmilesWrite::GetAtomSmiles(atom, doKekule, nullptr, allHsExplicit, isomericSmiles) << "\""; auto tag = boost::str(boost::format("_atomdrawpos_%d") % confId); const Conformer &conf = mol.getConformer(confId); RDGeom::Point3D pos = conf.getAtomPos(atom->getIdx()); Point2D dpos(pos.x, pos.y); if (atom->hasProp(tag)) { dpos = atom->getProp(tag); } else { dpos = getDrawCoords(dpos); } d_os << " drawing-x=\"" << dpos.x << "\"" << " drawing-y=\"" << dpos.y << "\""; d_os << " x=\"" << pos.x << "\"" << " y=\"" << pos.y << "\"" << " z=\"" << pos.z << "\""; outputMetaData(atom, d_os, d_activeClass); d_os << " />" << std::endl; } for (const auto bond : mol.bonds()) { d_os << "getIdx() + 1 << "\""; d_os << " begin-atom-idx=\"" << bond->getBeginAtomIdx() + 1 << "\""; d_os << " end-atom-idx=\"" << bond->getEndAtomIdx() + 1 << "\""; bool doKekule = false, allBondsExplicit = true; d_os << " bond-smiles=\"" << SmilesWrite::GetBondSmiles(bond, -1, doKekule, allBondsExplicit) << "\""; outputMetaData(bond, d_os, d_activeClass); d_os << " />" << std::endl; } d_os << "" << std::endl; } void MolDraw2DSVG::addMoleculeMetadata(const std::vector &mols, const std::vector confIds) const { for (unsigned int i = 0; i < mols.size(); ++i) { int confId = -1; if (confIds.size() == mols.size()) { confId = confIds[i]; } addMoleculeMetadata(*(mols[i]), confId); } }; void MolDraw2DSVG::tagAtoms(const ROMol &mol, double radius, const std::map &events) { PRECONDITION(d_os, "no output stream"); // first bonds so that they are under the atoms for (const auto &bond : mol.bonds()) { const auto this_idx = bond->getIdx(); const auto a_idx1 = bond->getBeginAtomIdx(); const auto a_idx2 = bond->getEndAtomIdx(); const auto a1pos = getDrawCoords(atomCoords()[bond->getBeginAtomIdx()]); const auto a2pos = getDrawCoords(atomCoords()[bond->getEndAtomIdx()]); const auto width = 2 + lineWidth(); if (drawOptions().splitBonds) { const auto midp = (a1pos + a2pos) / 2; // from begin to mid d_os << "\n"; // mid to end d_os << "\n"; } else { d_os << "\n"; } } for (const auto &at : mol.atoms()) { auto this_idx = at->getIdx(); auto pos = getDrawCoords(atomCoords()[this_idx]); d_os << "\n"; } } void MolDraw2DSVG::outputClasses() { if (d_activeClass.empty() && !hasActiveAtmIdx()) return; d_os << "class='"; if (!d_activeClass.empty()) { d_os << d_activeClass; } if (!hasActiveAtmIdx()) { d_os << "' "; return; } d_os << (!d_activeClass.empty() ? " " : ""); const auto aidx1 = getActiveAtmIdx1(); if (aidx1 >= 0) { d_os << "atom-" << aidx1; } const auto aidx2 = getActiveAtmIdx2(); if (aidx2 >= 0 && aidx2 != aidx1) { d_os << " atom-" << aidx2; } d_os << "' "; } } // namespace RDKit