Euphoria
textbox.cc
Go to the documentation of this file.
1 #include "core/textbox.h"
2 
3 #include <cstdlib>
4 #include <numeric>
5 
6 #include "base/cint.h"
7 #include "assert/assert.h"
8 #include "base/stringutils.h"
9 
10 namespace
11 {
12  bool
13  is_terminal_supporting_utf8()
14  {
15  const char* clang = std::getenv("LANG");
16 
17  if(clang != nullptr)
18  {
19  const auto lang = std::string(clang);
20  const auto lower = eu::to_lower(lang);
21  const auto ends = eu::ends_with(lower, "utf-8");
22  return ends;
23  }
24 
25  return false;
26  }
27 
28 
29  bool
30  has_char(char c)
31  {
32  return (c & eu::core::bit_no_line) != 0;
33  }
34 
35 
36  bool
37  is_emtpy(char c)
38  {
39  return c ==' ' || c == 0;
40  }
41 }
42 
43 
44 namespace eu::core
45 {
46  std::string_view
48  {
49  ASSERTX(c>0, c);
50  ASSERTX
51  (
52  c-1 < c_sizet_to_int(connections.size()),
54  connections.size()
55  );
56  return connections[c-1];
57  }
58 
59 
62  {
63  if(is_terminal_supporting_utf8())
64  {
65  // return Utf8StraightStyle();
66  return utf8_rounded_style;
67  }
68  else
69  {
70  return ascii_style;
71  }
72  }
73 
74  TextBox::TextBox() = default;
75 
76  TextBox
78  {
79  return {};
80  }
81 
82 
83  TextBox
84  TextBox::create_from_strings(const std::vector<std::string>& str)
85  {
86  TextBox ret;
87  ret.data = str;
88  return ret;
89  }
90 
91 
92  void
93  TextBox::put_char(int x, int y, char c)
94  {
95  extend_to(x,y);
96  data[y][x] = c;
97  }
98 
99 
100  void
101  TextBox::extend_to(int x, int y)
102  {
103  if(y >= c_sizet_to_int(data.size()))
104  {
105  data.resize(y+1);
106  }
107  if(c_sizet_to_int(data[y].size()) <= x)
108  {
109  data[y].resize(x+1, ' ');
110  }
111  }
112 
113 
114  void
116  (
117  int x,
118  int y,
119  const std::string& line
120  )
121  {
122  for(int index = 0; index < c_sizet_to_int(line.length()); index+=1)
123  {
124  put_char(x+index, y, line[index]);
125  }
126  }
127 
128  TextBox
129  TextBox::from_string(const std::string& s, int x, int y)
130  {
131  auto tb = create_empty();
132  tb.put_string(x, y, s);
133  return tb;
134  }
135 
136 
137  void
139  (
140  int x_start,
141  int y_start,
142  const TextBox& box
143  )
144  {
145  for(int data_index = 0; data_index < c_sizet_to_int(box.data.size()); data_index+=1)
146  {
147  const auto line = box.data[data_index];
148  const auto y = y_start + data_index;
149 
150  const int size_minus_1 = line.empty() ? 0 : c_sizet_to_int(line.size())-1;
151  extend_to(x_start+size_minus_1, y);
152 
153  for(int line_index = 0; line_index < c_sizet_to_int(line.size()); line_index+=1)
154  {
155  const auto x = x_start + line_index;
156  const auto source_texel = line[line_index];
157 
158  ASSERT(x < c_sizet_to_int(data[y].size()));
159 
160  if(!is_emtpy(source_texel))
161  {
162  char& target = data[y][x];
163  if(is_emtpy(target) || has_char(source_texel))
164  {
165  target = source_texel;
166  }
167  else
168  {
169  if( has_char(target) )
170  {
171  target = 0;
172  }
173  target = static_cast<char>(target | source_texel);
174  }
175  }
176  }
177  }
178  }
179 
180 
181  TextBox
183  (
184  int x,
185  int y,
186  const TextBox& box
187  ) const
188  {
189  TextBox self = *this;
190  self.put_box(x, y, box);
191  return self;
192  }
193 
194 
195  void
197  {
198  for(auto& s: data)
199  {
200  int end = c_sizet_to_int(s.size());
201  while(end > 0 && is_emtpy(s[end - 1]))
202  {
203  end-=1;
204  }
205  s.erase(end);
206  }
207 
208  while(!data.empty() && data.back().empty())
209  {
210  data.pop_back();
211  }
212  }
213 
214 
215  int
217  {
218  return c_sizet_to_int(data.size());
219  }
220 
221 
222  int
224  {
225  int result = 0;
226 
227  for(const auto& s: data)
228  {
229  result = std::max(result, c_sizet_to_int(s.size()));
230  }
231 
232  return result;
233  }
234 
235 
236  std::pair<int, int>
238  {
239  return {get_width(), get_height()};
240  }
241 
242 
243  void
245  (
246  int x,
247  int y,
248  int line_width,
249  bool bef,
250  bool aft
251  )
252  {
253  for(int line_index=0; line_index<line_width; line_index+=1)
254  {
255  mod_char(x+line_index, y, [&](char& c)
256  {
257  if( has_char(c) )
258  {
259  c = 0;
260  }
261 
262  if(line_index>0 || bef)
263  {
264  c |= bit_left;
265  }
266 
267  if(aft || (line_index+1)<line_width)
268  {
269  c |= bit_right;
270  }
271  });
272  }
273  }
274 
275 
276  void
278  (
279  int x,
280  int y,
281  int line_height,
282  bool bef,
283  bool aft
284  )
285  {
286  for(int line_index=0; line_index<line_height; line_index+=1)
287  {
288  mod_char(x, y+line_index, [&](char& c)
289  {
290  if( has_char(c) )
291  {
292  c = 0;
293  }
294 
295  if(line_index>0 || bef)
296  {
297  c |= bit_up;
298  }
299 
300  if(aft || (line_index+1)<line_height)
301  {
302  c |= bit_down;
303  }
304  });
305  }
306  }
307 
308 
309  int
311  {
312  const int my_width = get_width();
313 
314  int reduce = my_width;
315  for(int line_index=0; line_index<box.get_height(); line_index+=1)
316  {
317  reduce = std::min(reduce, find_right_padding(y+line_index) + box.find_left_padding(line_index));
318  }
319 
320  return my_width - reduce;
321  }
322 
323 
324  int
326  (
327  int x,
328  const TextBox& box
329  ) const
330  {
331  const int my_height = get_height();
332 
333  int reduce = my_height;
334  for(int box_x=0; box_x<box.get_width(); box_x+=1)
335  {
336  reduce = std::min(reduce, find_bottom_padding(x+box_x) + box.find_top_padding(box_x));
337  }
338 
339  return my_height - reduce;
340  }
341 
342 
343  std::vector<std::string>
344  TextBox::to_string(const TextBoxStyle& style) const
345  {
346  std::vector<std::string> ret;
347  bool want_newline = true;
348  auto last_line = [&ret, &want_newline]() -> std::string&
349  {
350  if(want_newline)
351  {
352  ret.emplace_back("");
353  want_newline = false;
354  }
355 
356  return ret[ret.size()-1];
357  };
358 
359  auto append = [&](char c)
360  {
361  if(c == '\n')
362  {
363  if(want_newline)
364  {
365  // if we want another newline, add the one before first
366  last_line();
367  }
368  want_newline = true;
369  }
370  else
371  {
372  last_line() += c;
373  }
374  };
375 
376  const int h = get_height();
377  for(int y = 0; y < h; y+=1)
378  {
379  const std::string& s = data[y];
380  for(int x = 0; x < c_sizet_to_int(s.size()); x+=1)
381  {
382  char c = s[x];
383  if(c > 0 && c < 16)
384  {
385  const auto str = style.get_string(c);
386  for(auto line_char: str)
387  {
388  append(line_char);
389  }
390  }
391  else
392  {
393  append(c);
394  }
395  }
396  append('\n');
397  }
398  return ret;
399  }
400 
401 
402  int
403  TextBox::find_left_padding(int y) const
404  {
405  const int max = get_width();
406 
407  if(y >= c_sizet_to_int(data.size()))
408  {
409  return max;
410  }
411 
412  const std::string& line = data[y];
413 
414  int result = 0;
415  while(result < c_sizet_to_int(line.size()) && is_emtpy(line[result]))
416  {
417  result+=1;
418  }
419 
420  return result;
421  }
422 
423 
424  int
425  TextBox::find_right_padding(int y) const
426  {
427  const int max = get_width();
428 
429  if(y >= c_sizet_to_int(data.size()))
430  {
431  return max;
432  }
433 
434  const std::string& line = data[y];
435  int position = max;
436  int result = 0;
437  while
438  (
439  position != 0 &&
440  (position-1 >= c_sizet_to_int(line.size()) || is_emtpy(line[position - 1]))
441  )
442  {
443  position-=1;
444  result+=1;
445  }
446 
447  return result;
448  }
449 
450 
451  int
452  TextBox::find_bottom_padding(int x) const
453  {
454  const int max = c_sizet_to_int(data.size());
455 
456  int result = 0;
457  int position = max;
458 
459  while
460  (
461  position != 0 &&
462  (x >= c_sizet_to_int(data[position-1].size()) || is_emtpy(data[position - 1][x]))
463  )
464  {
465  position-=1;
466  result+=1;
467  }
468 
469  return result;
470  }
471 
472 
473  int
474  TextBox::find_top_padding(int x) const
475  {
476  const int max = c_sizet_to_int(data.size());
477  int result = 0;
478 
479  while
480  (
481  result < max &&
482  (x >= c_sizet_to_int(data[result].size()) || is_emtpy(data[result][x]))
483  )
484  {
485  result+=1;
486  }
487 
488  return result;
489  }
490 
491 
492  void
493  TextBox::create_tree_graph_impl
494  (
495  TextBox* result,
496  int maxwidth,
497  const std::vector<TextBox>& boxes,
498  bool consider_oneliner,
499  bool consider_simple,
500  const std::string& label,
501  int margin,
502  int firstx
503  )
504  {
505  constexpr int min_y = 1;
506 
507  const int totalwidth = boxes.empty() ? 0 :
508  std::accumulate
509  (
510  boxes.begin(),
511  boxes.end(),
512  0,
513  [](auto t, const auto& b) {return t + b.get_width();}
514  ) + (c_sizet_to_int(boxes.size())-1) * margin
515  ;
516 
517  bool oneliner = consider_oneliner &&
518  (c_sizet_to_int(label.size()) + margin + totalwidth) < maxwidth;
519  bool simple = consider_simple && oneliner && boxes.size() == 1;
520 
521  int y = simple ? 0 : 1;
522 
523  for(auto box_iterator = boxes.begin(); box_iterator != boxes.end(); box_iterator+=1)
524  {
525  const TextBox& current_box = *box_iterator;
526  const int usemargin = (simple || oneliner) ? (margin/2) : margin;
527  const auto first_valid_x = result->get_horizontal_append_position(y, current_box);
528  int x = first_valid_x != 0 ? first_valid_x + usemargin
529  :(
530  oneliner
531  ? c_sizet_to_int(label.size())+usemargin
532  : firstx
533  );
534 
535  if(!oneliner && (x + current_box.get_width() > maxwidth))
536  {
537  // Start a new line if this item won't fit in the
538  // end of the current line
539  x = firstx;
540  simple = false;
541  oneliner = false;
542  }
543 
544  // At the beginning of line, judge whether to add room for
545  // horizontal placement
546  bool horizontal = x > firstx;
547  if
548  (
549  !oneliner &&
550  !horizontal &&
551  std::next(box_iterator) != boxes.end()
552  )
553  {
554  const auto& next_box = *std::next(box_iterator);
555  int combined_width =
556  current_box.get_horizontal_append_position(0, next_box) +
557  margin +
558  next_box.get_width()
559  ;
560  if(combined_width <= maxwidth)
561  {
562  // Enact horizontal placement by giving 1 row of room
563  // for the connector
564  horizontal = true;
565  const TextBox combined = current_box.put_box_copy
566  (
567  current_box.get_horizontal_append_position(0, next_box) + margin,
568  0,
569  next_box
570  );
571  y = std::max
572  (
573  result->get_vertical_append_position(x, combined),
574  1
575  );
576  if(!oneliner)
577  {
578  y+=1;
579  }
580  }
581  }
582  if(!horizontal)
583  {
584  y = std::max
585  (
586  result->get_vertical_append_position(x, current_box),
587  1
588  );
589  }
590  if(horizontal && !simple && !oneliner)
591  {
592  while(true)
593  {
594  // Check if there is room for a horizontal connector.
595  // If not, increase y
596  const auto connector = TextBox::from_string
597  (
598  std::string(1+x, '-')
599  );
600  if(result->get_horizontal_append_position(y-1, connector) > x)
601  {
602  y+=1;
603  }
604  else
605  {
606  break;
607  }
608  y = std::max
609  (
610  result->get_vertical_append_position(x, current_box),
611  y
612  );
613  }
614  }
615 
616  if(simple)
617  {
618  }
619  else
620  {
621  y = std::max(y, min_y);
622  }
623 
624  if(simple)
625  {
626  if(x > c_sizet_to_int(label.size()))
627  {
628  const auto label_size = c_sizet_to_int(label.size());
629  result->put_horizontal_line
630  (
631  label_size,
632  0,
633  1+x-label_size,
634  false,
635  false
636  );
637  }
638  }
639  else if(oneliner)
640  {
641  const auto label_size = c_sizet_to_int(label.size());
642 
643  int cx = x;
644  int cy = y > min_y ? y-min_y : 0;
645  if(x > c_sizet_to_int(label.size()))
646  {
647  result->put_horizontal_line
648  (
649  label_size,
650  0,
651  1+x-label_size,
652  false,
653  false
654  );
655  }
656  result->put_vertical_line(cx, cy, min_y, false,true);
657  }
658  else if(horizontal)
659  {
660  int cx = x;
661  int cy = y-1;
662  result->put_vertical_line(0, 1, 1 + (cy-1), true,false);
663  result->put_horizontal_line(0, cy, 1 + (cx-0), false,false);
664  result->put_vertical_line(cx, cy, 1, false,true);
665  }
666  else
667  {
668  int cx = x-1;
669  int cy = y;
670  result->put_vertical_line(0,1, 1 + (cy-1), true,false);
671  result->put_horizontal_line(0,cy, 1 + (cx-0), false,true);
672  }
673 
674  result->put_box(x, y, current_box);
675  }
676  }
677 }
678 
#define ASSERTX(x,...)
Definition: assert.h:48
#define ASSERT(x)
Definition: assert.h:29
std::string to_lower(const std::string &str)
Generate a string containing only lower characters.
Definition: stringutils.cc:143
std::string from_char_to_string(char c, CharToStringStyle style)
Definition: stringutils.cc:168
constexpr TextBoxStyle ascii_style
Definition: textbox.h:112
constexpr TextBoxStyle utf8_rounded_style
Definition: textbox.h:64
constexpr unsigned char bit_no_line
Definition: textbox.h:17
constexpr unsigned char bit_down
Definition: textbox.h:14
constexpr unsigned char bit_up
Definition: textbox.h:13
TextBoxStyle get_terminal_style()
Definition: textbox.cc:61
constexpr unsigned char bit_right
Definition: textbox.h:16
constexpr unsigned char bit_left
Definition: textbox.h:15
int c_sizet_to_int(size_t t)
Definition: cint.cc:11
bool ends_with(const std::string &str, char c)
Definition: os.cc:139
size2f min(const size2f lhs, const size2f rhs)
Definition: size2.cc:140
size2f max(const size2f lhs, const size2f rhs)
Definition: size2.cc:149
int line
Definition: nlp_sentence.cc:91
String utility functions.
std::string_view get_string(char s) const
Definition: textbox.cc:47
int get_vertical_append_position(int x, const TextBox &b) const
Calculate the earliest Y coordinate where the given box could be placed without colliding with existi...
Definition: textbox.cc:326
void put_box(int x, int y, const TextBox &b)
Definition: textbox.cc:139
void mod_char(int x, int y, F &&func)
Modify a character using a callback.
Definition: textbox.h:276
int get_height() const
Calculate the current dimensions of the string.
Definition: textbox.cc:216
int get_horizontal_append_position(int y, const TextBox &b) const
Calculate the earliest X coordinate where the given box could be placed.
Definition: textbox.cc:310
static TextBox create_empty()
Definition: textbox.cc:77
int get_width() const
Definition: textbox.cc:223
void put_string(int x, int y, const std::string &s)
Put a string of characters starting at the given coordinate.
Definition: textbox.cc:116
TextBox put_box_copy(int x, int y, const TextBox &b) const
Definition: textbox.cc:183
void put_horizontal_line(int x, int y, int width, bool bef, bool aft)
Draw a horizontal line.
Definition: textbox.cc:245
static TextBox create_from_strings(const std::vector< std::string > &str)
Definition: textbox.cc:84
void put_char(int x, int y, char c)
Place a single character in the given coordinate.
Definition: textbox.cc:93
void trim()
Delete trailing blank from the bottom and right edges.
Definition: textbox.cc:196
std::pair< int, int > get_size() const
Definition: textbox.cc:237
static TextBox from_string(const std::string &s, int x=0, int y=0)
Definition: textbox.cc:129
std::vector< std::string > to_string(const TextBoxStyle &style=get_terminal_style()) const
Definition: textbox.cc:344
void put_vertical_line(int x, int y, int height, bool bef, bool aft)
Draw a vertical line.
Definition: textbox.cc:278