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