1 //
2 //   @@ All Rights Reserved @@
3 //  This file is part of the RDKit.
4 //  The contents are covered by the terms of the BSD license
5 //  which is included in the file license.txt, found at the root
6 //  of the RDKit source tree.
7 //
8 // Original author: David Cosgrove (CozChemIx) on 29/04/2020.
9 //
10 
11 #include <GraphMol/MolDraw2D/DrawText.h>
12 
13 namespace RDKit {
14 
15 // ****************************************************************************
DrawText(double max_fnt_sz,double min_fnt_sz)16 DrawText::DrawText(double max_fnt_sz, double min_fnt_sz)
17     : colour_(DrawColour(0.0, 0.0, 0.0)),
18       font_scale_(1.0),
19       max_font_size_(max_fnt_sz),
20       min_font_size_(min_fnt_sz) {}
21 
22 // ****************************************************************************
colour() const23 DrawColour const &DrawText::colour() const { return colour_; }
24 
25 // ****************************************************************************
setColour(const DrawColour & col)26 void DrawText::setColour(const DrawColour &col) { colour_ = col; }
27 
28 // ****************************************************************************
fontSize() const29 double DrawText::fontSize() const { return fontScale() * baseFontSize(); }
30 
31 // ****************************************************************************
setFontSize(double new_size)32 void DrawText::setFontSize(double new_size) {
33   if (new_size < minFontSize()) {
34     BOOST_LOG(rdWarningLog)
35         << "The new font size " << new_size << " is below the current minimum ("
36         << minFontSize() << ")." << std::endl;
37   } else if (new_size > maxFontSize()) {
38     BOOST_LOG(rdWarningLog)
39         << "The new font size " << new_size << " is above the current maximum ("
40         << maxFontSize() << ")." << std::endl;
41   }
42   double new_scale = new_size / baseFontSize();
43   setFontScale(new_scale);
44 }
45 
46 // ****************************************************************************
baseFontSize() const47 double DrawText::baseFontSize() const { return base_font_size_; }
48 // ****************************************************************************
setBaseFontSize(double val)49 void DrawText::setBaseFontSize(double val) { base_font_size_ = val; }
50 
51 // ****************************************************************************
maxFontSize() const52 double DrawText::maxFontSize() const { return max_font_size_; }
53 
54 // ****************************************************************************
setMaxFontSize(double new_max)55 void DrawText::setMaxFontSize(double new_max) { max_font_size_ = new_max; }
56 
57 // ****************************************************************************
minFontSize() const58 double DrawText::minFontSize() const { return min_font_size_; }
59 
60 // ****************************************************************************
setMinFontSize(double new_min)61 void DrawText::setMinFontSize(double new_min) { min_font_size_ = new_min; }
62 
63 // ****************************************************************************
fontScale() const64 double DrawText::fontScale() const { return font_scale_; }
65 
66 // ****************************************************************************
setFontScale(double new_scale)67 void DrawText::setFontScale(double new_scale) {
68   font_scale_ = new_scale;
69   double nfs = fontSize();
70   if (max_font_size_ != -1 &&
71       nfs * (baseFontSize() / FONT_SIZE) > max_font_size_) {
72     font_scale_ = max_font_size_ / baseFontSize();
73   }
74   if (min_font_size_ != -1 &&
75       nfs * (baseFontSize() / FONT_SIZE) < min_font_size_) {
76     font_scale_ = min_font_size_ / baseFontSize();
77   }
78 }
79 
80 // ****************************************************************************
drawString(const std::string & str,const Point2D & cds,TextAlignType talign)81 void DrawText::drawString(const std::string &str, const Point2D &cds,
82                           TextAlignType talign) {
83   std::vector<std::shared_ptr<StringRect>> rects;
84   std::vector<TextDrawType> draw_modes;
85   std::vector<char> draw_chars;
86   getStringRects(str, rects, draw_modes, draw_chars);
87   alignString(talign, draw_modes, rects);
88   drawChars(cds, rects, draw_modes, draw_chars);
89 }
90 
91 // ****************************************************************************
drawString(const std::string & label,const Point2D & cds,OrientType orient)92 void DrawText::drawString(const std::string &label, const Point2D &cds,
93                           OrientType orient) {
94   std::vector<std::shared_ptr<StringRect>> rects;
95   std::vector<TextDrawType> draw_modes;
96   std::vector<char> draw_chars;
97   getStringRects(label, orient, rects, draw_modes, draw_chars);
98   drawChars(cds, rects, draw_modes, draw_chars);
99 }
100 
101 // ****************************************************************************
adjustLineForString(const std::string & label,OrientType orient,const Point2D & end1,Point2D & end2) const102 void DrawText::adjustLineForString(const std::string &label, OrientType orient,
103                                    const Point2D &end1, Point2D &end2) const {
104   std::vector<std::shared_ptr<StringRect>> rects;
105   std::vector<TextDrawType> draw_modes;
106   std::vector<char> draw_chars;
107   Point2D lab_pos = end2;
108 
109   getStringRects(label, orient, rects, draw_modes, draw_chars);
110   double bond_len = (end1 - end2).length();
111   for (size_t i = 0; i < rects.size(); ++i) {
112     const auto &r = rects[i];
113     r->trans_ += lab_pos;
114 
115     Point2D tl, tr, bl, br;
116     r->calcCorners(tl, tr, br, bl, 0.025 * bond_len);
117     std::unique_ptr<Point2D> ip(new Point2D);
118 
119     // if it's a wide label, such as C:7, the bond can intersect
120     // more than 1 side of the rectangle, so check them all.
121     if (doLinesIntersect(end2, end1, tl, tr, ip.get())) {
122       end2 = *ip;
123     }
124     if (doLinesIntersect(end2, end1, tr, br, ip.get())) {
125       end2 = *ip;
126     }
127     if (doLinesIntersect(end2, end1, br, bl, ip.get())) {
128       end2 = *ip;
129     }
130     if (doLinesIntersect(end2, end1, bl, tl, ip.get())) {
131       end2 = *ip;
132     }
133   }
134 }
135 
136 // ****************************************************************************
drawStringRects(const std::string & label,OrientType orient,const Point2D & cds,MolDraw2D & mol_draw) const137 void DrawText::drawStringRects(const std::string &label, OrientType orient,
138                                const Point2D &cds, MolDraw2D &mol_draw) const {
139   std::vector<std::shared_ptr<StringRect>> rects;
140   std::vector<TextDrawType> draw_modes;
141   std::vector<char> draw_chars;
142 
143   size_t i = 0;
144   getStringRects(label, orient, rects, draw_modes, draw_chars);
145   for (auto r : rects) {
146     r->trans_.x += cds.x;
147     r->trans_.y += cds.y;
148     Point2D tl, tr, br, bl;
149     r->calcCorners(tl, tr, br, bl, 0.0);
150 
151     tl = mol_draw.getAtomCoords(std::make_pair(tl.x, tl.y));
152     tr = mol_draw.getAtomCoords(std::make_pair(tr.x, tr.y));
153     br = mol_draw.getAtomCoords(std::make_pair(br.x, br.y));
154     bl = mol_draw.getAtomCoords(std::make_pair(bl.x, bl.y));
155 
156     mol_draw.setColour(DrawColour(1.0, 0.0, 0.0));
157     mol_draw.drawLine(tl, tr);
158     mol_draw.setColour(DrawColour(0.0, 1.0, 0.0));
159     mol_draw.drawLine(tr, br);
160     mol_draw.setColour(DrawColour(0.0, 0.0, 1.0));
161     mol_draw.drawLine(br, bl);
162     mol_draw.setColour(DrawColour(0.0, 0.95, 0.95));
163     mol_draw.drawLine(bl, tl);
164     ++i;
165   }
166 }
167 
168 // ****************************************************************************
doesRectIntersect(const std::string & label,OrientType orient,const Point2D & cds,const StringRect & rect) const169 bool DrawText::doesRectIntersect(const std::string &label, OrientType orient,
170                                  const Point2D &cds,
171                                  const StringRect &rect) const {
172   if (label.empty()) {
173     return false;
174   }
175   std::vector<std::shared_ptr<StringRect>> rects;
176   std::vector<TextDrawType> draw_modes;
177   std::vector<char> draw_chars;
178 
179   getStringRects(label, orient, rects, draw_modes, draw_chars);
180   return doesRectIntersect(rects, cds, rect);
181 }
182 
183 // ****************************************************************************
doesRectIntersect(const std::vector<std::shared_ptr<StringRect>> & rects,const Point2D & cds,const StringRect & rect) const184 bool DrawText::doesRectIntersect(
185     const std::vector<std::shared_ptr<StringRect>> &rects, const Point2D &cds,
186     const StringRect &rect) const {
187   for (auto r : rects) {
188     StringRect nr(*r);
189     nr.trans_ += cds;
190     if (nr.doesItIntersect(rect)) {
191       return true;
192     }
193   }
194 
195   return false;
196 }
197 
198 // ****************************************************************************
doesLineIntersect(const std::string & label,OrientType orient,const Point2D & cds,const Point2D & end1,const Point2D & end2,double padding) const199 bool DrawText::doesLineIntersect(const std::string &label, OrientType orient,
200                                  const Point2D &cds, const Point2D &end1,
201                                  const Point2D &end2, double padding) const {
202   std::vector<std::shared_ptr<StringRect>> rects;
203   std::vector<TextDrawType> draw_modes;
204   std::vector<char> draw_chars;
205 
206   getStringRects(label, orient, rects, draw_modes, draw_chars);
207   return doesLineIntersect(rects, cds, end1, end2, padding);
208 }
209 
210 // ****************************************************************************
doesLineIntersect(const std::vector<std::shared_ptr<StringRect>> & rects,const Point2D & cds,const Point2D & end1,const Point2D & end2,double padding) const211 bool DrawText::doesLineIntersect(
212     const std::vector<std::shared_ptr<StringRect>> &rects, const Point2D &cds,
213     const Point2D &end1, const Point2D &end2, double padding) const {
214   for (auto r : rects) {
215     StringRect nr(*r);
216     nr.trans_ += cds;
217 
218     Point2D tl, tr, bl, br;
219     nr.calcCorners(tl, tr, br, bl, padding);
220     if (doLinesIntersect(end2, end1, tl, tr, nullptr)) {
221       return true;
222     }
223     if (doLinesIntersect(end2, end1, tr, br, nullptr)) {
224       return true;
225     }
226     if (doLinesIntersect(end2, end1, br, bl, nullptr)) {
227       return true;
228     }
229     if (doLinesIntersect(end2, end1, bl, tl, nullptr)) {
230       return true;
231     }
232   }
233   return false;
234 }
235 
236 // ****************************************************************************
doesStringIntersect(const std::string & label1,OrientType orient1,const Point2D & cds1,const std::string & label2,OrientType orient2,const Point2D & cds2) const237 bool DrawText::doesStringIntersect(const std::string &label1,
238                                    OrientType orient1, const Point2D &cds1,
239                                    const std::string &label2,
240                                    OrientType orient2,
241                                    const Point2D &cds2) const {
242   if (label1.empty() || label2.empty()) {
243     return false;
244   }
245   std::vector<std::shared_ptr<StringRect>> rects1;
246   std::vector<TextDrawType> draw_modes1;
247   std::vector<char> draw_chars1;
248 
249   getStringRects(label1, orient1, rects1, draw_modes1, draw_chars1);
250 
251   return doesStringIntersect(rects1, cds1, label2, orient2, cds2);
252 }
253 
254 // ****************************************************************************
doesStringIntersect(const std::vector<std::shared_ptr<StringRect>> & rects,const Point2D & cds1,const std::string & label2,OrientType orient2,const Point2D & cds2) const255 bool DrawText::doesStringIntersect(
256     const std::vector<std::shared_ptr<StringRect>> &rects, const Point2D &cds1,
257     const std::string &label2, OrientType orient2, const Point2D &cds2) const {
258   if (label2.empty()) {
259     return false;
260   }
261   std::vector<std::shared_ptr<StringRect>> rects2;
262   std::vector<TextDrawType> draw_modes2;
263   std::vector<char> draw_chars2;
264   getStringRects(label2, orient2, rects2, draw_modes2, draw_chars2);
265 
266   for (auto r1 : rects) {
267     StringRect nr1(*r1);
268     nr1.trans_ += cds1;
269     for (auto r2 : rects2) {
270       StringRect nr2(*r2);
271       nr2.trans_ += cds2;
272       if (nr1.doesItIntersect(nr2)) {
273         return true;
274       }
275     }
276   }
277   return false;
278 }
279 
280 // ****************************************************************************
getStringExtremes(const std::string & label,OrientType orient,double & x_min,double & y_min,double & x_max,double & y_max,bool dontSplit) const281 void DrawText::getStringExtremes(const std::string &label, OrientType orient,
282                                  double &x_min, double &y_min, double &x_max,
283                                  double &y_max, bool dontSplit) const {
284   if (label.empty()) {
285     x_min = x_max = 0.0;
286     y_min = y_max = 0.0;
287     return;
288   }
289 
290   x_min = y_min = std::numeric_limits<double>::max();
291   x_max = y_max = -std::numeric_limits<double>::max();
292 
293   std::vector<std::shared_ptr<StringRect>> rects;
294   std::vector<TextDrawType> draw_modes;
295   std::vector<char> to_draw;
296   getStringRects(label, orient, rects, draw_modes, to_draw, dontSplit);
297 
298   int i = 0;
299   for (auto r : rects) {
300     Point2D tl, tr, br, bl;
301     r->calcCorners(tl, tr, br, bl, 0.0);
302     // sometimes the rect is in a coordinate frame where +ve y is down,
303     // sometimes it's up.  For these purposes, we don't care so long as
304     // the y_max is larger than the y_min.  We probably don't need to do
305     // all the tests for x_min and x_max;
306     x_min = std::min(bl.x, x_min);
307     x_min = std::min(tr.x, x_min);
308     y_min = std::min(bl.y, y_min);
309     y_min = std::min(tr.y, y_min);
310     x_max = std::max(bl.x, x_max);
311     x_max = std::max(tr.x, x_max);
312     y_max = std::max(bl.y, y_max);
313     y_max = std::max(tr.y, y_max);
314     ++i;
315   }
316 }
317 
318 // ****************************************************************************
alignString(TextAlignType talign,const std::vector<TextDrawType> & draw_modes,std::vector<std::shared_ptr<StringRect>> & rects) const319 void DrawText::alignString(
320     TextAlignType talign, const std::vector<TextDrawType> &draw_modes,
321     std::vector<std::shared_ptr<StringRect>> &rects) const {
322   // std::string comes in with rects aligned with first char with its
323   // left hand and bottom edges at 0 on y and x respectively.
324   // Adjust relative to that so that the relative alignment point is at
325   // (0,0).
326   if (talign == TextAlignType::MIDDLE) {
327     size_t num_norm = count(draw_modes.begin(), draw_modes.end(),
328                             TextDrawType::TextDrawNormal);
329     if (num_norm == 1) {
330       talign = TextAlignType::START;
331     }
332   }
333 
334   Point2D align_trans, align_offset;
335   if (talign == TextAlignType::START || talign == TextAlignType::END) {
336     size_t align_char = 0;
337     for (size_t i = 0; i < rects.size(); ++i) {
338       if (draw_modes[i] == TextDrawType::TextDrawNormal) {
339         align_char = i;
340         if (talign == TextAlignType::START) {
341           break;
342         }
343       }
344     }
345     align_trans = rects[align_char]->trans_;
346     align_offset = rects[align_char]->offset_;
347   } else {
348     // centre on the middle of the Normal text.  The super- or subscripts
349     // should be at the ends.
350     double x_min = std::numeric_limits<double>::max();
351     double x_max = -x_min;
352     double y_min = std::numeric_limits<double>::max();
353     double y_max = -y_min;
354     align_offset.x = align_offset.y = 0.0;
355     int num_norm = 0;
356     for (size_t i = 0; i < rects.size(); ++i) {
357       if (draw_modes[i] == TextDrawType::TextDrawNormal) {
358         Point2D tl, tr, br, bl;
359         rects[i]->calcCorners(tl, tr, br, bl, 0.0);
360         // sometimes the rect is in a coordinate frame where +ve y is down,
361         // sometimes it's up.  For these purposes, we don't care so long as
362         // the y_max is larger than the y_min.  We probably don't need to do
363         // all the tests for x_min and x_max;
364         x_min = std::min(bl.x, x_min);
365         x_min = std::min(tr.x, x_min);
366         y_min = std::min(bl.y, y_min);
367         y_min = std::min(tr.y, y_min);
368         x_max = std::max(bl.x, x_max);
369         x_max = std::max(tr.x, x_max);
370         y_max = std::max(bl.y, y_max);
371         y_max = std::max(tr.y, y_max);
372         align_offset += rects[i]->offset_;
373         ++num_norm;
374       }
375     }
376     align_trans.x = (x_max - x_min) / 2.0;
377     align_trans.y = 0.0;
378     align_offset /= num_norm;
379   }
380 
381   for (auto r : rects) {
382     r->trans_ -= align_trans;
383     r->offset_ = align_offset;
384   }
385 }
386 
387 // ****************************************************************************
adjustStringRectsForSuperSubScript(const std::vector<TextDrawType> & draw_modes,std::vector<std::shared_ptr<StringRect>> & rects) const388 void DrawText::adjustStringRectsForSuperSubScript(
389     const std::vector<TextDrawType> &draw_modes,
390     std::vector<std::shared_ptr<StringRect>> &rects) const {
391   double last_char = -1;
392   for (size_t i = 0; i < draw_modes.size(); ++i) {
393     switch (draw_modes[i]) {
394       case TextDrawType::TextDrawSuperscript:
395         // superscripts may come before or after a letter.  If before,
396         // spin through to first non-superscript for last_height
397         if (last_char < 0) {
398           for (size_t j = i + 1; j < draw_modes.size(); ++j) {
399             if (draw_modes[j] == TextDrawType::TextDrawNormal) {
400               last_char = j;
401               break;
402             }
403           }
404         }
405         // adjust up by last height / 2.
406         rects[i]->rect_corr_ = rects[last_char]->height_;
407         rects[i]->trans_.y -= rects[i]->rect_corr_ / 2.0;
408         // if the last char was a subscript, remove the advance
409         if (i && draw_modes[i - 1] == TextDrawType::TextDrawSubscript) {
410           double move_by = rects[i]->trans_.x - rects[i - 1]->trans_.x;
411           if (move_by > 0.0) {
412             for (size_t j = 0; j < i; ++j) {
413               rects[j]->trans_.x += move_by;
414             }
415           } else {
416             for (size_t j = i; j < draw_modes.size(); ++j) {
417               rects[j]->trans_.x += move_by;
418             }
419           }
420           rects[i]->trans_.x = rects[i - 1]->trans_.x;
421         }
422         break;
423       case TextDrawType::TextDrawSubscript:
424         // adjust y down by last height / 2
425         rects[i]->rect_corr_ = -rects[last_char]->height_;
426         rects[i]->trans_.y -= rects[i]->rect_corr_ / 2.0;
427         // if the last char was a superscript, remove the advance
428         if (i && draw_modes[i - 1] == TextDrawType::TextDrawSuperscript) {
429           double move_by = rects[i]->trans_.x - rects[i - 1]->trans_.x;
430           if (move_by > 0.0) {
431             for (size_t j = 0; j < i; ++j) {
432               rects[j]->trans_.x += move_by;
433             }
434           } else {
435             for (size_t j = i; j < draw_modes.size(); ++j) {
436               rects[j]->trans_.x += move_by;
437             }
438           }
439           rects[i]->trans_.x = rects[i - 1]->trans_.x;
440         }
441         break;
442       case TextDrawType::TextDrawNormal:
443         last_char = i;
444         break;
445     }
446   }
447 }
448 
449 // ****************************************************************************
selectScaleFactor(char c,TextDrawType draw_type) const450 double DrawText::selectScaleFactor(char c, TextDrawType draw_type) const {
451   switch (draw_type) {
452     case TextDrawType::TextDrawNormal:
453       return 1.0;
454     case TextDrawType::TextDrawSubscript:
455       return SUBS_SCALE;
456     case TextDrawType::TextDrawSuperscript:
457       if (c == '-' || c == '+') {
458         return SUBS_SCALE;
459       } else {
460         return SUPER_SCALE;
461       }
462   }
463   return 1.0;
464 }
465 
466 // ****************************************************************************
getStringSize(const std::string & label,double & label_width,double & label_height) const467 void DrawText::getStringSize(const std::string &label, double &label_width,
468                              double &label_height) const {
469   double x_min, y_min, x_max, y_max;
470   getStringExtremes(label, OrientType::E, x_min, y_min, x_max, y_max);
471   label_width = x_max - x_min;
472   label_height = y_max - y_min;
473 }
474 
475 // ****************************************************************************
setStringDrawMode(const std::string & instring,TextDrawType & draw_mode,size_t & i)476 bool setStringDrawMode(const std::string &instring, TextDrawType &draw_mode,
477                        size_t &i) {
478   std::string bit1 = instring.substr(i, 5);
479   std::string bit2 = instring.substr(i, 6);
480 
481   // could be markup for super- or sub-script
482   if (std::string("<sub>") == bit1) {
483     draw_mode = TextDrawType::TextDrawSubscript;
484     i += 4;
485     return true;
486   } else if (std::string("<sup>") == bit1) {
487     draw_mode = TextDrawType::TextDrawSuperscript;
488     i += 4;
489     return true;
490   } else if (std::string("</sub>") == bit2) {
491     draw_mode = TextDrawType::TextDrawNormal;
492     i += 5;
493     return true;
494   } else if (std::string("</sup>") == bit2) {
495     draw_mode = TextDrawType::TextDrawNormal;
496     i += 5;
497     return true;
498   }
499 
500   return false;
501 }
502 
503 // ****************************************************************************
getStringRects(const std::string & text,OrientType orient,std::vector<std::shared_ptr<StringRect>> & rects,std::vector<TextDrawType> & draw_modes,std::vector<char> & draw_chars,bool dontSplit) const504 void DrawText::getStringRects(const std::string &text, OrientType orient,
505                               std::vector<std::shared_ptr<StringRect>> &rects,
506                               std::vector<TextDrawType> &draw_modes,
507                               std::vector<char> &draw_chars,
508                               bool dontSplit) const {
509   std::vector<std::string> text_bits;
510   if (!dontSplit) {
511     text_bits = atomLabelToPieces(text, orient);
512   } else {
513     text_bits.push_back(text);
514   }
515 
516   if (orient == OrientType::W) {
517     // stick the pieces together again backwards and draw as one so there
518     // aren't ugly splits in the string.
519     std::string new_lab;
520     for (auto i = text_bits.rbegin(); i != text_bits.rend(); ++i) {
521       new_lab += *i;
522     }
523     getStringRects(new_lab, rects, draw_modes, draw_chars);
524     alignString(TextAlignType::END, draw_modes, rects);
525   } else if (orient == OrientType::E) {
526     // likewise, but forwards
527     std::string new_lab;
528     for (auto lab : text_bits) {
529       new_lab += lab;
530     }
531     getStringRects(new_lab, rects, draw_modes, draw_chars);
532     alignString(TextAlignType::START, draw_modes, rects);
533   } else {
534     double running_y = 0;
535     for (size_t i = 0; i < text_bits.size(); ++i) {
536       std::vector<std::shared_ptr<StringRect>> t_rects;
537       std::vector<TextDrawType> t_draw_modes;
538       std::vector<char> t_draw_chars;
539       getStringRects(text_bits[i], t_rects, t_draw_modes, t_draw_chars);
540       alignString(TextAlignType::MIDDLE, t_draw_modes, t_rects);
541       double max_height = -std::numeric_limits<double>::max();
542       for (auto r : t_rects) {
543         max_height = std::max(r->height_, max_height);
544         r->y_shift_ = running_y;
545       }
546       rects.insert(rects.end(), t_rects.begin(), t_rects.end());
547       draw_modes.insert(draw_modes.end(), t_draw_modes.begin(),
548                         t_draw_modes.end());
549       draw_chars.insert(draw_chars.end(), t_draw_chars.begin(),
550                         t_draw_chars.end());
551       if (orient == OrientType::N) {
552         running_y -= 1.1 * max_height;
553       } else if (orient == OrientType::S) {
554         running_y += 1.1 * max_height;
555       }
556     }
557   }
558 }
559 
560 // ****************************************************************************
drawChars(const Point2D & a_cds,const std::vector<std::shared_ptr<StringRect>> & rects,const std::vector<TextDrawType> & draw_modes,const std::vector<char> & draw_chars)561 void DrawText::drawChars(const Point2D &a_cds,
562                          const std::vector<std::shared_ptr<StringRect>> &rects,
563                          const std::vector<TextDrawType> &draw_modes,
564                          const std::vector<char> &draw_chars) {
565   double full_scale = fontScale();
566   for (size_t i = 0; i < rects.size(); ++i) {
567     Point2D draw_cds;
568     draw_cds.x = a_cds.x + rects[i]->trans_.x - rects[i]->offset_.x;
569     draw_cds.y = a_cds.y - rects[i]->trans_.y +
570                  rects[i]->offset_.y;  // opposite sign convention
571     draw_cds.y -= rects[i]->rect_corr_ + rects[i]->y_shift_;
572     double mfs = minFontSize();
573     setMinFontSize(-1);
574     setFontScale(full_scale * selectScaleFactor(draw_chars[i], draw_modes[i]));
575     drawChar(draw_chars[i], draw_cds);
576     setMinFontSize(mfs);
577     setFontScale(full_scale);
578   }
579 }
580 
581 // ****************************************************************************
atomLabelToPieces(const std::string & label,OrientType orient)582 std::vector<std::string> atomLabelToPieces(const std::string &label,
583                                            OrientType orient) {
584   // cout << "splitting " << label << " : " << orient << endl;
585   std::vector<std::string> label_pieces;
586   if (label.empty()) {
587     return label_pieces;
588   }
589 
590   // if we have the mark-up <lit>XX</lit> the symbol is to be used
591   // without modification
592   if (label.substr(0, 5) == "<lit>") {
593     std::string lit_sym = label.substr(5);
594     size_t idx = lit_sym.find("</lit>");
595     if (idx != std::string::npos) {
596       lit_sym = lit_sym.substr(0, idx);
597     }
598     label_pieces.emplace_back(lit_sym);
599     return label_pieces;
600   }
601 
602   std::string next_piece;
603   size_t i = 0;
604   while (true) {
605     if (i == label.length()) {
606       if (!next_piece.empty()) {
607         label_pieces.emplace_back(next_piece);
608         break;
609       }
610     }
611     if (label.substr(i, 2) == "<s" || label[i] == ':' || isupper(label[i])) {
612       // save the old piece, start a new one
613       if (!next_piece.empty()) {
614         label_pieces.emplace_back(next_piece);
615         next_piece.clear();
616       }
617     }
618     next_piece += label[i++];
619   }
620   if (label_pieces.size() < 2) {
621     return label_pieces;
622   }
623 
624   // if the orientation is S or E, any charge flag needs to be at the end.
625   if (orient == OrientType::E || orient == OrientType::S) {
626     for (size_t i = 0; i < label_pieces.size(); ++i) {
627       if (label_pieces[i] == "<sup>+</sup>" ||
628           label_pieces[i] == "<sup>-</sup>") {
629         label_pieces.push_back(label_pieces[i]);
630         label_pieces[i].clear();
631         break;
632       }
633     }
634   }
635 
636   // Now group some together.  This relies on the order that
637   // getAtomLabel built them in the first place.  Each atom symbol
638   // needs to be flanked by any <sub> and <super> pieces.
639   std::vector<std::string> final_pieces;
640   std::string curr_piece;
641   bool had_symbol = false;
642   for (const auto &p : label_pieces) {
643     if (p.empty()) {
644       continue;
645     }
646     if (!isupper(p[0])) {
647       curr_piece += p;
648     } else {
649       if (had_symbol) {
650         final_pieces.push_back(curr_piece);
651         curr_piece = p;
652         had_symbol = true;
653       } else {
654         curr_piece += p;
655         had_symbol = true;
656       }
657     }
658   }
659   if (!curr_piece.empty()) {
660     final_pieces.push_back(curr_piece);
661   }
662 
663   // cout << "Final pieces : " << endl;
664   // for(auto l: final_pieces) {
665   //   cout << l << endl;
666   // }
667   // cout << endl;
668 
669   return final_pieces;
670 }
671 
operator <<(std::ostream & oss,const TextAlignType & tat)672 std::ostream &operator<<(std::ostream &oss, const TextAlignType &tat) {
673   switch (tat) {
674     case TextAlignType::START:
675       oss << "START";
676       break;
677     case TextAlignType::MIDDLE:
678       oss << "MIDDLE";
679       break;
680     case TextAlignType::END:
681       oss << "END";
682       break;
683   }
684   return oss;
685 }
operator <<(std::ostream & oss,const TextDrawType & tdt)686 std::ostream &operator<<(std::ostream &oss, const TextDrawType &tdt) {
687   switch (tdt) {
688     case TextDrawType::TextDrawNormal:
689       oss << "TextDrawNormal";
690       break;
691     case TextDrawType::TextDrawSuperscript:
692       oss << "TextDrawSuperscript";
693       break;
694     case TextDrawType::TextDrawSubscript:
695       oss << "TextDrawSubscript";
696       break;
697   }
698   return oss;
699 }
operator <<(std::ostream & oss,const OrientType & o)700 std::ostream &operator<<(std::ostream &oss, const OrientType &o) {
701   switch (o) {
702     case OrientType::C:
703       oss << "C";
704       break;
705     case OrientType::N:
706       oss << "N";
707       break;
708     case OrientType::S:
709       oss << "S";
710       break;
711     case OrientType::E:
712       oss << "E";
713       break;
714     case OrientType::W:
715       oss << "W";
716       break;
717   }
718   return oss;
719 }
720 
721 }  // namespace RDKit
722