// // Copyright (C) 2015-2021 Greg Landrum and other RDKit contributors // // @@ 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 #include #include #ifdef RDK_BUILD_FREETYPE_SUPPORT #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) { 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"; bool hasAlpha = 1.0 - col.a > 1e-3; std::string res(hasAlpha ? 9 : 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]; if (hasAlpha) { v = int(255 * col.a); 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; } // **************************************************************************** MolDraw2DSVG::MolDraw2DSVG(int width, int height, std::ostream &os, int panelWidth, int panelHeight, bool noFreetype) : MolDraw2D(width, height, panelWidth, panelHeight), d_os(os) { if (width > 0 && height > 0) { initDrawing(); needs_init_ = false; } initTextDrawer(noFreetype); } // **************************************************************************** MolDraw2DSVG::MolDraw2DSVG(int width, int height, int panelWidth, int panelHeight, bool noFreetype) : MolDraw2D(width, height, panelWidth, panelHeight), d_os(d_ss) { if (width > 0 && height > 0) { initDrawing(); needs_init_ = false; } initTextDrawer(noFreetype); } // **************************************************************************** void MolDraw2DSVG::initDrawing() { d_os << "\n"; d_os << "\n"} % width() % height(); d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::initTextDrawer(bool noFreetype) { double max_fnt_sz = drawOptions().maxFontSize; double min_fnt_sz = drawOptions().minFontSize; if (noFreetype) { text_drawer_.reset(new MolDraw2D_detail::DrawTextSVG(max_fnt_sz, min_fnt_sz, d_os, d_activeClass)); } else { #ifdef RDK_BUILD_FREETYPE_SUPPORT try { text_drawer_.reset(new MolDraw2D_detail::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 MolDraw2D_detail::DrawTextSVG( max_fnt_sz, min_fnt_sz, d_os, d_activeClass)); } #else text_drawer_.reset(new MolDraw2D_detail::DrawTextSVG(max_fnt_sz, min_fnt_sz, d_os, d_activeClass)); #endif } if (drawOptions().baseFontSize > 0.0) { text_drawer_->setBaseFontSize(drawOptions().baseFontSize); } } // **************************************************************************** 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 &, unsigned int nSegments, double vertOffset, bool rawCoords) { PRECONDITION(nSegments > 1, "too few segments"); setColour(col1); auto segments = MolDraw2D_detail::getWavyLineSegments(cds1, cds2, nSegments, vertOffset); std::string col = DrawColourToSVG(colour()); double width = getDrawLineWidth(); d_os << "(segments[0]); c1 = rawCoords ? c1 : getDrawCoords(c1); d_os << "d='M" << c1.x << "," << c1.y; for (unsigned int i = 0; i < nSegments; ++i) { auto cpt1 = std::get<1>(segments[i]); cpt1 = rawCoords ? cpt1 : getDrawCoords(cpt1); auto cpt2 = std::get<2>(segments[i]); cpt2 = rawCoords ? cpt2 : getDrawCoords(cpt2); auto segpt = std::get<3>(segments[i]); segpt = rawCoords ? segpt : getDrawCoords(segpt); d_os << " C" << cpt1.x << "," << cpt1.y << " " << cpt2.x << "," << cpt2.y << " " << segpt.x << "," << segpt.y; } d_os << "' "; d_os << "style='fill:none;stroke:" << col << ";stroke-width:" << boost::format("%.1f") % width << "px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" << "'"; d_os << " />\n"; } namespace { std::string getDashString(const DashPattern &dashes) { std::string res; if (dashes.size()) { std::stringstream dss; dss << ";stroke-dasharray:"; std::copy(dashes.begin(), dashes.end() - 1, std::ostream_iterator(dss, ",")); dss << dashes.back(); res = dss.str(); } return res; } } // namespace // **************************************************************************** void MolDraw2DSVG::drawLine(const Point2D &cds1, const Point2D &cds2, bool rawCoords) { Point2D c1 = rawCoords ? cds1 : getDrawCoords(cds1); Point2D c2 = rawCoords ? cds2 : getDrawCoords(cds2); std::string col = DrawColourToSVG(colour()); double width = getDrawLineWidth(); std::string dashString = getDashString(dash()); d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::drawPolygon(const std::vector &cds, bool rawCoords) { PRECONDITION(cds.size() >= 3, "must have at least three points"); std::string col = DrawColourToSVG(colour()); double width = getDrawLineWidth(); std::string dashString = getDashString(dash()); d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::drawEllipse(const Point2D &cds1, const Point2D &cds2, bool rawCoords) { Point2D c1 = rawCoords ? cds1 : getDrawCoords(cds1); Point2D c2 = rawCoords ? cds2 : 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 = getDashString(dash()); d_os << "\n"; } // **************************************************************************** void MolDraw2DSVG::clearDrawing() { MolDraw2D::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 << "" << "\n"; d_os << "" << "\n"; 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=\"" << MolDraw2D_detail::formatDouble(dpos.x) << "\"" << " drawing-y=\"" << MolDraw2D_detail::formatDouble(dpos.y) << "\""; d_os << " x=\"" << pos.x << "\"" << " y=\"" << pos.y << "\"" << " z=\"" << pos.z << "\""; outputMetaData(atom, d_os); d_os << " />" << "\n"; } 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_os << " />" << "\n"; } d_os << "" << "\n"; } // **************************************************************************** void MolDraw2DSVG::addMoleculeMetadata(const std::vector &mols, const std::vector confIds) { for (unsigned int i = 0; i < mols.size(); ++i) { int confId = -1; if (confIds.size() == mols.size()) { confId = confIds[i]; } setActiveMolIdx(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(bond->getBeginAtomIdx()); const auto a2pos = getDrawCoords(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(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 (hasActiveBndIdx()) { d_os << "bond-" << getActiveBndIdx(); } if (!hasActiveAtmIdx()) { d_os << "' "; return; } if (hasActiveBndIdx()) { d_os << " "; } 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