'Manipulating the standard output stream to print multiline strings horizontally
So I have three strings and these strings are supposed to occupy 3 lines. I thought this was a good way to represent my string:
std::string str1 = "███████\n███1███\n███████";
std::string str2 = "███████\n███2███\n███████";
std::string str3 = "███████\n███3███\n███████";
But I realise that when I do this and just cout the strings, they get printed on top of each other which is not I want. I want the output to look like this:
█████████████████████
███1██████2██████3███
█████████████████████
How can I achieve this effect? I only know setw to manipulate the output however I don't know how that could help here.
note: I will have these stored in an array and than loop over the array and print them, I feel like that might change the solution a bit as well.
Solution 1:[1]
Store the rows of each card as elements in an array. That makes it pretty easy.
#include <iostream>
int main()
{
const char * str1[3] = {"???????","???1???","???????"};
const char * str2[3] = {"???????","???2???","???????"};
const char * str3[3] = {"???????","???3???","???????"};
for( int row = 0; row < 3; row ++ )
{
std::cout << str1[row] << str2[row] << str3[row] << "\n";
}
}
Output:
?????????????????????
???1??????2??????3???
?????????????????????
Again, pretty easy to add a space between those, if you want.
Solution 2:[2]
You could split each on \n
and print them. We can use std::stringstream
for splitting by \n
.
void print(std::array<std::string, 3>& arr){
std::vector<std::stringstream> arr_buf{};
arr_buf.reserve(arr.size());
for(auto& str: arr){
arr_buf.emplace_back(str);
}
for(auto i=0u; i < arr.size(); ++i){
for(auto& stream: arr_buf){
std::string t;
stream >> t;
std::cout << t ;
}
std::cout << "\n";
}
}
Output:
print(arr)
?????????????????????
???1??????2??????3???
?????????????????????
Solution 3:[3]
If you are certain that your output will always be displayed on a modern terminal supporting “ANSI Escape Codes” then you can use that to your advantage.
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
// helper: split a string into a list of views
auto splitv( const std::string & s, const std::string & separators )
{
std::vector <std::string_view> views;
std::string::size_type a = 0, b = 0;
while (true)
{
a = s.find_first_not_of( separators, b );
b = s.find_first_of ( separators, a );
if (a >= s.size()) break;
if (b == s.npos) b = s.size();
views.emplace_back( &(s[a]), b-a );
}
return views;
}
std::string print( const std::string & s )
{
std::ostringstream os;
for (auto sv : splitv( s, "\n" ))
os
<< "\033" "7" // DEC save cursor position
<< sv
<< "\033" "8" // DEC restore cursor position
<< "\033[B"; // move cursor one line down
return os.str().substr( 0, os.str().size()-5 );
}
std::string movexy( int dx, int dy )
{
std::ostringstream os;
if (dy < 0) os << "\033[" << -dy << "A";
else if (dy > 0) os << "\033[" << dy << "B";
if (dx > 0) os << "\033[" << dx << "C";
else if (dx < 0) os << "\033[" << -dx << "D";
return os.str();
}
int main()
{
std::string str1 = "???????\n???1???\n???????";
std::string str2 = "???????\n???2???\n???????";
std::string str3 = "???????\n???3???\n???????";
std::cout
<< "\n" "\n\n" // blank line at top + blocks are three lines high
<< movexy( 2, -2 ) << print( str1 ) // first block is placed two spaces from left edge
<< movexy( 1, -2 ) << print( str2 ) // remaining blocks are placed one space apart
<< movexy( 1, -2 ) << print( str3 )
<< "\n\n"; // newline after last block, plus extra blank line at bottom
}
This produces the output:
??????? ??????? ???????
???1??? ???2??? ???3???
??????? ??????? ???????
The addition of spacing is, of course, entirely optional and only added for demonstrative purposes.
Advantages: UTF-8 and Pretty colors!
The advantage to this method is that you do not have to store or otherwise take any special care for strings containing multi-byte characters (UTF-8, as yours does) or any additional information like terminal color sequences.
That is, you could color each of your blocks differently by adding a color sequence to each strN
variable! (The caveat is that you must repeat a color sequence after every newline. This is a known problem with various terminals...)
// red, white, and blue
std::string str1 = "\033[31m???????\n\033[31m???1???\n\033[31m???????";
std::string str2 = "\033[37m???????\n\033[37m???2???\n\033[37m???????";
std::string str3 = "\033[34m???????\n\033[34m???3???\n\033[34m???????";
Relative vs Absolute Caret Positioning
The other caveat to this particular example is that you must be aware of where the text caret (“cursor”) ends-up after each output. You could also use terminal escape sequences to absolutely position the caret before every output.
std::string gotoxy( int x, int y )
{
std::ostringstream os;
os << "\033[" << y << ";" << x << "H";
return os.str();
}
Then you wouldn’t have to care where the caret ends up. Just specify an absolute position before printing. Just don’t let the text scroll!
Windows OS Considerations
Finally, if you are on Windows and using the old Windows Console, you must initialize the terminal for ANSI terminal sequences and for UTF-8 output:
#ifdef _WIN32
#include <windows.h>
void init_terminal()
{
DWORD mode;
HANDLE hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
GetConsoleMode( hStdOut, &mode );
SetConsoleMode( hStdOut, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING );
SetConsoleOutputCP( 65001 );
}
#else
void init_terminal() { }
#endif
int main()
{
init_terminal();
...
This does no harm to the new Windows Terminal. I recommend you do it either way just because you do not know which of the two your user will use to run your program, alas.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Tim Roberts |
Solution 2 | TheScore |
Solution 3 | Dúthomhas |