Ive been designing a roguelike video game in the console.
My problem is i initially draw the entire map with writeconsoleoutput at once then use writeconsoleoutputcharacter to redraw whats neccessary. But i have found that it draws an extra pixel thick on the right side.
This essentially erases one pixel off the tile on the right side.
Does anyone know a way to make it stop drawing a character and adding a 1 pixel blank space on the right side of the character.
EDIT: ONE WORKAROUND I HAVE FOUND IS TO REDRAW THE LINES THE PLAYER STEPS ON BUT IT ISNT PERFECT ONCE IN A WHILE I CAN NOTICE A FLICKER. Would be preferable to just redraw characters on an individual basis without any pixels attached on the end. or anywhere else.
The Code used to print the character
void printchar(char character, COORD location,short color)
{
const char *pointer = &character;
//short LENGTH = 1;
DWORD dwWritten = 0;
std::vector<WORD> attributes;attributes.clear();;
setAttributesChar(color,attributes);//Pushes back the color
if(attributes.size() == 1)
{ WriteConsoleOutputAttribute(GetStdHandle(STD_OUTPUT_HANDLE),&attributes[0], attributes.size(), location, &dwWritten);
WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), pointer, attributes.size(), location, &dwWritten);
}
}
void setAttributesChar(short color,std::vector<WORD> &attributes)
{
switch(color)
{
case DEFAULTCOLOR:
attributes.push_back(FOREGROUND_INTENSE_WHITE);return;
case HOVEREDCOLOR:
attributes.push_back(FOREGROUND_GREEN);return;
case RED:
attributes.push_back(FOREGROUND_RED);return;
case GOLD:
attributes.push_back(FOREGROUND_INTENSE_YELLOW);return;
case REDWHITE:
attributes.push_back(COMBINED_REDWHITE);return;
case YELLOWPURPLE:
attributes.push_back(COMBINED_YELLOWPURPLE);return;
case NPCCOLOR:
attributes.push_back(FOREGROUND_INTENSE_CYAN);return;
case PURPLE:
attributes.push_back(FOREGROUND_MAGENTA);return;
case REDPURPLE:
attributes.push_back(COMBINED_REDPURPLE);return;
case BLUE:
attributes.push_back(FOREGROUND_BLUE);return;
case BLUEPURPLE:
attributes.push_back(COMBINED_BLUEPURPLE);return;
case INTENSEGREEN:
attributes.push_back(FOREGROUND_INTENSE_GREEN);return;
case REDGREEN:
attributes.push_back(COMBINED_REDGREEN);return;
case BWHITE:
attributes.push_back(BACKGROUND_WHITE);return;
}
}
EXAMPLE:
BEFORE CHARACTER WALKS OVER ROAD: BEFORE IMAGE
AFTER CHARACTER WALKS OVER ROAD: AFTER IMAGE
EXECUTABLE LINK: EXECUTABLE
I have created a working example that you guys can compile(Assuming u use windows) to see what is occuring. In it i create a console set the size fill the screen with the asci symbol 223. then i use Writeconsoleoutchar to print a green 223 symbol. You can see the extra drawn black pixel line.
#include <windows.h>
#include <cwchar>
#include <vector>
enum COLORS{HOVEREDCOLOR};
void setAttributesChar(short color,std::vector<WORD> &attributes)
{
switch(color)
{
case HOVEREDCOLOR:
attributes.push_back(FOREGROUND_GREEN);return;
}
}
void printchar(char character, COORD location,short color)
{
const char *pointer = &character;
//short LENGTH = 1;
DWORD dwWritten = 0;
std::vector<WORD> attributes;attributes.clear();;
setAttributesChar(color,attributes);//Pushes back the color
if(attributes.size() ==1)
{ WriteConsoleOutputAttribute(GetStdHandle(STD_OUTPUT_HANDLE),&attributes[0], attributes.size(), location, &dwWritten);
WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), pointer, attributes.size(), location, &dwWritten);
}
}
#define SCREENWIDTH 1550
#define SCREENHEIGHT 850
void SETWINDOWUP(DWORD &SIZE)
{
CONSOLE_SCREEN_BUFFER_INFO info;
MoveWindow(GetConsoleWindow(),0,0,SCREENWIDTH,SCREENHEIGHT,true); //MOVE WINDOW TO 0,0 AND MAKE 1600 BY 880
::SetWindowPos(GetConsoleWindow(), HWND_TOPMOST, 0, 0, 0,0, SWP_DRAWFRAME | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); //SET WNDOWS POSITION COMPARED TO OTHER WINDOWS IN DRAW ORDER
::SetWindowPos(GetConsoleWindow(),HWND_NOTOPMOST, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
HANDLE hout = GetStdHandle( STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(hout,&info);
COORD newBuffersize =
{
info.srWindow.Right - info.srWindow.Left + 1,
info.srWindow.Bottom - info.srWindow.Top + 1
};
SetConsoleScreenBufferSize(hout,newBuffersize);
SIZE = info.dwSize.X * info.dwSize.Y;
//EDIT REGULATED FONT SIZE
CONSOLE_FONT_INFOEX cfi;
std::wcscpy(cfi.FaceName, L"Lucida Console"); // Choose your font
cfi.FontFamily = FF_DONTCARE;
cfi.nFont = 0;
cfi.dwFontSize.X = 0;
cfi.dwFontSize.Y = 25; // Height
cfi.FontWeight = FW_BOLD;
cfi.cbSize = sizeof(cfi);
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), false, &cfi);
}
int main()
{
Sleep(500);DWORD SIZE = 0;SETWINDOWUP(SIZE);
DWORD charswritten;COORD start = {0,0};TCHAR CHARACTER = 223;
FillConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE),CHARACTER,SIZE,start,&charswritten);
Sleep(500);
printchar(223,{6,5},HOVEREDCOLOR);
Sleep(500);
return 0;
};
Related
I'm trying to enable ANSI color support for created console screen buffer via CreateConsoleScreenBuffer().
hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
DWORD dwMode = 0;
GetConsoleMode(hConsole, &dwMode);
dwMode |= ENABLE_EXTENDED_FLAGS;
SetConsoleMode(hConsole, dwMode);
dwMode = 0;
GetConsoleMode(hConsole, &dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hConsole, dwMode);
SetConsoleActiveScreenBuffer(hConsole);
It seems like SetConsoleMode() isn't doing anything, I can write it to buffer as I intended, but if I try to write any ANSI Escape codes, it looks like this
If I'm not in buffer created by CreateConsoleScreenBuffer(), ANSI Escape codes are working as expected.
EDIT: I'm on Windows 10, 19041.388; C++14, MinGW-64 compiler
You could use this:
#include <windows.h>
#include <iostream>
void Color(int color=0x07)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
int main()
{
Color(0x0A/*Black bg green Fg*/);
std::cout << "Hello";
Color();
std::cout << ",";
Color(0xAC/*Green bg red Fg*/);
std::cout << "World";
Color(/*Black bg white Fg*/);
}
For more information about the colors:
Color attributes are specified by TWO hex digits -- the first
corresponds to the background; the second the foreground. Each digit
can be any of the following values:
0 = Black 8 = Gray
1 = Blue 9 = Light Blue
2 = Green A = Light Green
3 = Aqua B = Light Aqua
4 = Red C = Light Red
5 = Purple D = Light Purple
6 = Yellow E = Light Yellow
7 = White F = Bright White
Virtual terminal sequences are control character sequences that can
control cursor movement, color/font mode, and other operations when
written to the output stream. Sequences may also be received on the
input stream in response to an output stream query information
sequence or as an encoding of user input when the appropriate mode is
set.
You can use GetConsoleMode and SetConsoleMode functions to configure
this behavior.
From ENABLE_VIRTUAL_TERMINAL_PROCESSING, we can use WriteFile or WriteConsole to achieve.
Some code:
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hConsole_c = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleActiveScreenBuffer(hConsole_c);
DWORD dwMode = 0;
GetConsoleMode(hConsole_c, &dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hConsole_c, dwMode);
const char* str = "\x1b[31mThis text has restored the foreground color only.\r\n";
DWORD len = strlen(str);
DWORD dwBytesWritten = 0;
WriteConsole(hConsole_c, str, len, &dwBytesWritten, NULL);
Debug:
# include "DISPLAY.h"
DoubleBuffer::DoubleBuffer()
{
COORD size = { WINDOW_X_SIZE , WINDOW_Y_SIZE };
SMALL_RECT rect;
rect.Left = 0;
rect.Right = WINDOW_X_SIZE - 1;
rect.Top = 0;
rect.Bottom = WINDOW_Y_SIZE - 1;
hBuffer[0] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleScreenBufferSize(hBuffer[0], size);
SetConsoleWindowInfo(hBuffer[0], TRUE, &rect);
hBuffer[1] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleScreenBufferSize(hBuffer[1], size);
SetConsoleWindowInfo(hBuffer[1], TRUE, &rect);
CONSOLE_CURSOR_INFO cursorinfo;
cursorinfo.dwSize = 1;
cursorinfo.bVisible = FALSE;
SetConsoleCursorInfo(hBuffer[0], &cursorinfo);
SetConsoleCursorInfo(hBuffer[1], &cursorinfo);
nBufferIndex = 0;
}
void DoubleBuffer::WriteBuffer(int x, int y, char *string)
{
DWORD dw;
COORD startposition = { x,y };
SetConsoleCursorPosition(hBuffer[nBufferIndex], startposition);
WriteFile(hBuffer[nBufferIndex], string, strlen(string), &dw, NULL);
}
void DoubleBuffer::FlippBuffer()
{
Sleep(1000);
SetConsoleActiveScreenBuffer(hBuffer[nBufferIndex]);
nBufferIndex = !nBufferIndex;
}
void DoubleBuffer::ClearBuffer()
{
COORD coord = { 0,0 };
DWORD dw;
FillConsoleOutputCharacter(hBuffer[nBufferIndex], ' ', WINDOW_X_SIZE*WINDOW_Y_SIZE, coord, &dw);
}
void DoubleBuffer::ReleaseBuffer()
{
CloseHandle(hBuffer[0]);
CloseHandle(hBuffer[1]);
}
I used this code to construct a double buffer.
The entire game map which would be printed at the function 'WriteBuffer' was designed as a 1D char array.
But a big problem is that if the length of string goes over 80, then the console just shows 80 character in one line regardless of the console window size.
I mean, for example the input string is
char 1D_map[90] = {'D'};
and the console window size is 5*20.
Then no matter what the window size is, it shows like behind.
DDDDD (.. the left 70 characters are hidden..)
DDDDD (.. the left 5 character are hidden..)
As I pull the side bar of window to its maximum, it shows like behind.
DDDDDDDDDDDDDDDD ... 80 characters ... DDDDDDDDDDDDDDDDD
DDDDDDDDDD
So when I use these codes, then I cannot adjust the game map size.
(concretely horizontal size.)
Because whatever I do, it just shows 80 characters in a line!!
Can I fix the problem? or Should I apply other double buffering method?
I'm trying to create simple application with custom output. I'm using CreateFont function in order to load monospaced font with specific options. As a result, the text has been drawn (DrawText) with ligatures such as fi.
How to disable this?
I am very sorry for my bad English
Creating font
normal = CreateFont(char_height, 0, 0, 0, FW_NORMAL, 0, 0, 0,
RUSSIAN_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, 5,
FIXED_PITCH | FF_DONTCARE, "Menlo");
bold = CreateFont(char_height, 0, 0, 0, FW_BOLD, 0, 0, 0,
RUSSIAN_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, 5,
FIXED_PITCH | FF_DONTCARE, "Menlo");
Printing
void Console::Print(string text, int color, int weight) {
RECT r;
r.top = padding + temp_y * (char_height + space_y);
r.bottom = r.top + char_height;
r.left = padding + temp_x * char_width;
r.right = padding + length();
HDC hdc = GetDC(hwnd);
SetTextColor(hdc, color);
SetBkColor(hdc, RGB(43, 48, 59));
SelectObject(GetDC(hwnd), weight == WEIGHT_NORMAL ? normal : bold);
DrawText(GetDC(hwnd), text.c_str(), text.length(), &r, DT_LEFT);
temp_x += text.length();
}
Current output (note the word "offline"):
Desired output:
The root problem seems to be that your font is broken. I'm not sure why a monospace (fixed-pitch) font would have ligature information.
Solution 1
Use ExtTextOutW with ETO_IGNORELANGUAGE. You may lose other functionality (like bi-di, digit substitution, shaping for complex scripts), but it does seem to prevent kerning and ligation, so it may be suitable for your purpose.
::ExtTextOutW(hdc, x, y, ETO_IGNORELANGUAGE, &rc, msg.c_str(), msg.size(), nullptr);
I tested with the font Gabriola, since I don't have Menlo or Meslo. Gabriola is a variable pitch font, but it has distinctive ligatures that make it easy to spot, especially fi.
Solution 2
A second approach, which has the same drawbacks, is to draw the strings character by character with TextOut in a loop. This is a little trickier because you have to worry about Unicode surrogate pairs, clipping, and updating the current position.
const auto old_alignment = ::SetTextAlign(hdc, TA_UPDATECP);
const auto old_mode = ::SetBkMode(hdc, TRANSPARENT);
::MoveToEx(hdc, x, y, nullptr);
// Loop simplified for demo. This doesn't handle Unicode surrogate pairs.
for (auto ch : msg) {
::TextOutW(hdc, 0, 0, &ch, 1);
}
::SetBkMode(hdc, old_mode);
::SetTextAlign(hdc, old_alignment);
This produced identical results to the first solution.
Non-Solution
Note that my earlier idea to use GetCharacterPlacement without the GCP_LIGATE flag followed by ExtTextOut with ETO_GLYPH_INDEX does not work. The glyphs returned from GetCharacterPlacement still included ligatures even without the GCP_LIGATE flag.
// DOES NOT WORK
GCP_RESULTSW results = {sizeof(results)};
WCHAR modified[64] = L""; // FIXED BUFFER LENGTHS JUST FOR TESTING
results.lpOutString = modified;
int deltas[64] = {0};
results.lpDx = deltas;
WCHAR glyphs[64] = L"";
glyphs[0] = 1;
results.lpGlyphs = glyphs;
results.nGlyphs = ARRAYSIZE(glyphs);
const DWORD flags = GCP_REORDER; // but not GCP_LIGATE or GCP_USEKERNING
::GetCharacterPlacementW(hdc, msg.c_str(), msg.size(), 0, &results, flags);
::ExtTextOutW(hdc, x, y, ETO_GLYPH_INDEX, &rc, glyphs, results.nGlyphs, deltas);
I have printing code that draws grid on the paper.
Grid has 4 columns, and they have equal horizontal length. Height of the cell is tenth of the paper size. Total number of rows is unknown but I know for a fact that there will be at least one row.
Each cell has same physical size-> width is quarter of the paper width, and height is one tenth of the paper height. Maximum number of characters that can fit into cell is 50.
The problem I face is choosing proper font size so text of maximum length can fit into cell.
Browsing through MSDN documentation and WinAPI examples, I saw that they use GetTextExtPoint32 for similar purposes, but this works only if font already exists and is selected into device context, which is not the case here.
The only thing that crossed my mind was to create "dummy font", see if the example text can fit into cell, and then adjust it's size if the test fails. I have also found this blog that recommends interesting approach to this problem, but being inexperienced I can't decide if "this is the proper way to go".
Can you recommend a correct solution for my problem?
EDITED on June, 30th 2014:
Below is the sample function that draws grid and paints upper left cell in light gray since that cell will contain sample text. That way we can visually validate the success of our drawing code:
// hWnd is the window that owns the property sheet.
HRESULT GDI_PRINT(HWND hWnd)
{
HRESULT hResult;
PRINTDLGEX pdx = {0};
LPPRINTPAGERANGE pPageRanges = NULL;
// Allocate an array of PRINTPAGERANGE structures.
pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if (!pPageRanges)
return E_OUTOFMEMORY;
// Initialize the PRINTDLGEX structure.
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWnd;
pdx.hDevMode = NULL;
pdx.hDevNames = NULL;
pdx.hDC = NULL;
pdx.Flags = PD_RETURNDC;
pdx.Flags2 = 0;
pdx.ExclusionFlags = 0;
pdx.nPageRanges = 0;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.hInstance = 0;
pdx.lpPrintTemplateName = NULL;
pdx.lpCallback = NULL;
pdx.nPropertyPages = 0;
pdx.lphPropertyPages = NULL;
pdx.nStartPage = START_PAGE_GENERAL;
pdx.dwResultAction = 0;
// Invoke the Print property sheet.
hResult = PrintDlgEx(&pdx);
if ( ( hResult == S_OK ) && ( pdx.dwResultAction == PD_RESULT_PRINT ) )
{
// User clicked the Print button,
// so use the DC and other information returned in the
// PRINTDLGEX structure to print the document.
//======= Various initializations ==========//
DOCINFO diDocInfo = {0};
diDocInfo.cbSize = sizeof( DOCINFO );
diDocInfo.lpszDocName = L"Testing printing...";
int pageWidth = GetDeviceCaps( pdx.hDC, HORZRES ),
pageHeight = GetDeviceCaps( pdx.hDC, VERTRES );
//===================== IMPORTANT !!! ==========================//
// Must test this on real printer !!! //
// For now testing is done in XPS and MS OneNote2007 //
//==============================================================//
//================== end of initialization =====================//
if( StartDoc( pdx.hDC, &diDocInfo ) > 0 )
{
if( StartPage( pdx.hDC ) > 0 )
{
//===== creating red pen that will draw grid =====//
LOGBRUSH lb;
lb.lbColor = RGB( 255, 0, 0 );
lb.lbHatch = 0;
lb.lbStyle = BS_SOLID;
HPEN hPen = ExtCreatePen( PS_COSMETIC | PS_SOLID, 1, &lb, 0, NULL);
HGDIOBJ oldPen = SelectObject( pdx.hDC, hPen );
// create test font
HFONT font, oldFont;
long lfHeight = -MulDiv( 14,
GetDeviceCaps( pdx.hDC, LOGPIXELSY ),
72 );
font = CreateFont( lfHeight, 0, 0, 0,
FW_BOLD, TRUE, FALSE, FALSE,
0, 0, 0,
0, 0, L"Microsoft Sans Serif" );
oldFont = SelectFont( pdx.hDC, font );
SetBkMode( pdx.hDC, TRANSPARENT );
SetTextColor( pdx.hDC, RGB( 255, 0, 0 ) );
// testing rectangle -> top left cell of the grid
RECT rcText;
rcText.left = 0;
rcText.top = 0;
rcText.right = pageWidth / 4;
rcText.bottom = pageHeight / 10;
// fill destination rectangle with gray brush
// so we can visually validate rectangle coordinates
FillRect( pdx.hDC, &rcText, (HBRUSH)GetStockObject(LTGRAY_BRUSH) );
// implement solution mentioned in the comment to this question
SIZE s;
::GetTextExtentPoint32( pdx.hDC,
L"Хидрогеотермална енергија Хидрогеотермална енерги",
wcslen( L"Хидрогеотермална енергија Хидрогеотермална енерги" ),
&s );
// select old font back and dispose test font
SelectObject( pdx.hDC, oldFont );
DeleteObject( font );
// adjust font height
lfHeight *= s.cy / ( rcText.bottom - rcText.top );
// now we can create proper font
font = CreateFont( lfHeight, 0, 0, 0,
FW_BOLD, TRUE, FALSE, FALSE,
0, 0, 0,
0, 0, L"Microsoft Sans Serif" );
oldFont = SelectFont( pdx.hDC, font );
// draw text in test rectangle
DrawTextEx( pdx.hDC,
L"Хидрогеотермална енергија Хидрогеотермална енерги",
wcslen( L"Хидрогеотермална енергија Хидрогеотермална енерги" ),
&rcText, DT_CENTER | DT_WORDBREAK | DT_NOCLIP, NULL );
//============== draw a testing grid ===============//
// draw vertical lines of the grid
for( int i = 0; i <= pageWidth; i += pageWidth / 4 )
{
MoveToEx( pdx.hDC, i, 0, NULL );
LineTo( pdx.hDC, i, pageHeight );
}
// draw horizontal lines of the grid
for( int j = 0; j <= pageHeight; j += pageHeight / 10 )
{
MoveToEx( pdx.hDC, 0, j, NULL );
LineTo( pdx.hDC, pageWidth, j );
}
// no need for pen anymore so delete it
SelectObject( pdx.hDC, oldPen );
DeleteObject( hPen );
// no need for font, delete it
SelectFont( pdx.hDC, oldFont );
DeleteFont( font );
if( EndPage( pdx.hDC ) < 0 )
// for now pop a message box saying something went wrong
MessageBox( hWnd, L"EndDoc failed!", L"Error", MB_OK );
}
EndDoc( pdx.hDC );
}
}
if (pdx.hDevMode != NULL)
GlobalFree(pdx.hDevMode);
if (pdx.hDevNames != NULL)
GlobalFree(pdx.hDevNames);
if (pdx.lpPageRanges != NULL)
GlobalFree(pPageRanges);
if (pdx.hDC != NULL)
DeleteDC(pdx.hDC);
return hResult;
}
To use this function, just launch it on button press/menu selection or whatever.
The results in XPS seem consistent, but I get strange results in MS OneNote 2007 which following images illustrate:
Font size is 14 :
Font size is 20 :
Font size is 20, but scaling from the above function was applied :
END OF EDIT
EDITED on July, 6th 2014:
The third picture from above edit was the result of GDI using default height value because the result of my mathematical adjustment for font height was 0. Once zero is passed to CreateFont mentioned behavior is expected.
After performing proper casting from double to int I got nearly perfect output -> last letter in the string barely exceeds the limit. I will continue to try improving this formula since I believe is promising. If anybody has another mathematical solution feel free to post it.
END OF EDIT
If further info / edit is required, leave a comment and I will react as soon as possible.
There are multiple issues involved.
The biggest problem I see is in this line:
lfHeight *= s.cy / ( rcText.bottom - rcText.top );
These are all integers. In C and C++, division with integers results in truncation toward zero. So if the result of the division "should" be 3.7, you'll end up with 3, which can be a pretty crude approximation.
Another problem is that GetTextExtentPoint32 does not wrap text, but DrawText does. So you're measuring the text as though you're going to print it as a single line, and you actually draw it as multiple lines. Instead of using GetTextExtendPoint32, you can measure the height with DrawText by DT_CALCRECT flag.
Putting these together, you want to measure your text like this:
WCHAR szText[] = L"Хидрогеотермална енергија Хидрогеотермална енерги";
RECT rcText;
rcText.left = 0;
rcText.top = 0;
rcText.right = pageWidth / 4;
rcText.bottom = top;
const DWORD options = DT_CENTER | DT_WORDBREAK | DT_NOCLIP;
DrawTextEx( pdx.hDC, szText, -1, &rcText, options | DT_CALCRECT, NULL);
// Because we used DT_CALCRECT, the DrawTextEx call didn't draw anything,
// but it did adjust the bottom of rcText to account for the actual height.
double actual_height = static_cast<double>(rcText.bottom - rcText.top);
double desired_height = pageHeight / 10.0;
double ratio = desired_heigth / actual_height;
// Scale the font height by the ratio, and round it off to the nearest int.
lf.lfHeight = static_cast<int>(lf.lfHeight * ratio + 0.5);
Okay. Basically, I start off with the suggested pointSize (14 in your code) and try to draw the text using the supplied bounding rect. If the text is too large, I go into an iterative loop that decreases the pointsize and measures again until the text will fit into the bounding rect.
If, on the other hand, the text is 'too small' I go into a loop that gradually increases it's size until it is too large. Once I reach this point, I decrease the point-size by 2 and return.
The reduction by 2 is a kludge or hack. I noticed that at times the size was reported as being equal to or smaller than the reported size of the bounding rect, yet still some characters would protrude past the edge of the bounding rect.
A better solution would make use of the DrawTextEx function to both calculate the size and draw the text. This would be better since you could make use of the iLeftmargin and iRightMargin members of the DRAWTEXTPARAMS struct that is passed to that function. Whether you wished to have a margin on each side, or simply wanted to add a single character's width, that you then halved when drawing the text would depend entirely on the desired outcome. I also added the DT_EXTERNALLEADING flag to obtain a small margin above/below the text, though there isn't one for vertical padding, so you'd have to make use of the margin attributes I mention.
Since the DT_VCENTER flag doesn't work with multi-line text, you'd also need to vertically offset the text yourself if you wished it to be vertically centered. You'd just have to offset the rect used for actually drawing the text by half of the difference between the area bounding rect's height and the text bounding rect's height.
I could have used a function like this for a few projects, so thanks for the impetus to actually exercise the grey matter and work it out!
Lastly, I used an interactive demo - one that responded to the WM_PAINT message of a (empty) dialog box. Since a HDC can be treated more-or-less the same whether it be for a printer or the screen, it provided a much quicker way of investigating the result.
Output when plugged into your code: (via cutePDF virtual printer)
Code:
int rectWidth(RECT &r)
{
return (r.right - r.left) + 1;
}
int rectHeight(RECT &r)
{
return (r.bottom - r.top) + 1;
}
void measureFunc(int pointSize, HDC hdc, RECT &pRectBounding, WCHAR *textToDraw, WCHAR *fontFaceName, int &resultWidth, int &resultHeight)
{
int pixelsPerInchY = GetDeviceCaps(hdc, LOGPIXELSY);
int logHeight = -MulDiv(pointSize, pixelsPerInchY, 72);
RECT tmpRect = pRectBounding;
HFONT old, tmp = CreateFont( logHeight, 0, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, 0, 0, 0, 0, 0, fontFaceName );
old = (HFONT)SelectObject(hdc, tmp);
DrawText(hdc, textToDraw, -1, &tmpRect, DT_CENTER | DT_WORDBREAK | DT_NOCLIP | DT_CALCRECT| DT_EXTERNALLEADING );
SelectObject(hdc, old);
DeleteObject(tmp);
resultWidth = rectWidth(tmpRect);
resultHeight = rectHeight(tmpRect);
}
HFONT getMaxFont(HDC hdc, WCHAR *fontName, WCHAR *textToDraw, RECT boundingRect)
{
int maxWidth = rectWidth(boundingRect), maxHeight = rectHeight(boundingRect);
int curWidth, curHeight, pointSize=14;
measureFunc(pointSize, hdc, boundingRect, textToDraw, fontName, curWidth, curHeight);
if ( (curWidth>maxWidth) || (curHeight>maxHeight) )
{
bool tooLarge = true;
while (tooLarge)
{
pointSize--;
measureFunc(pointSize, hdc, boundingRect, textToDraw, fontName, curWidth, curHeight);
if ((curWidth>maxWidth)||(curHeight>maxHeight))
tooLarge = true;
else
tooLarge = false;
}
}
else
{
bool tooSmall = true;
while (tooSmall)
{
pointSize++;
measureFunc(pointSize, hdc, boundingRect, textToDraw, fontName, curWidth, curHeight);
if ( (curWidth<maxWidth) && (curHeight<maxHeight) )
tooSmall = true;
else
tooSmall = false;
}
if ((curWidth>maxWidth) || (curHeight>maxHeight))
{
pointSize-=2;
}
}
int pixelsPerInchY = GetDeviceCaps( hdc, LOGPIXELSY );
int curFontSize;
HFONT result;
curFontSize = -MulDiv(pointSize, pixelsPerInchY, 72);
result = CreateFont(curFontSize, 0, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, 0, 0, 0, 0, 0, fontName );
return result;
}
BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
}
return TRUE;
case WM_SIZE:
InvalidateRect(hwndDlg, NULL, true);
return 0;
case WM_ERASEBKGND:
{
RECT mRect;
GetClientRect(hwndDlg, &mRect);
HBRUSH redBrush = CreateSolidBrush(RGB(255,0,0));
FillRect((HDC)wParam, &mRect, redBrush);
DeleteObject(redBrush);
}
return true;
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
HFONT requiredFont, oldFont;
WCHAR *textToDraw = L"Хидрогеотермална енергија Хидрогеотермална енерги";
WCHAR *fontFace = L"Microsoft Sans Serif";
RECT boundingRect, dlgRect;
hdc = BeginPaint(hwndDlg, &ps);
oldFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
GetClientRect(hwndDlg, &dlgRect);
SetRect(&boundingRect, 0,0, rectWidth(dlgRect) / 4, rectHeight(dlgRect) / 10);
FillRect(hdc, &boundingRect, (HBRUSH)GetStockObject(WHITE_BRUSH));
requiredFont = getMaxFont(hdc, fontFace, textToDraw, boundingRect);
SelectObject(hdc, requiredFont);
SetBkMode(hdc, TRANSPARENT);
DrawText(hdc, textToDraw, -1, &boundingRect, DT_CENTER | DT_WORDBREAK | DT_NOCLIP | DT_EXTERNALLEADING );
SelectObject(hdc, oldFont);
DeleteObject(requiredFont);
EndPaint(hwndDlg, &ps);
}
return false;
case WM_CLOSE:
{
EndDialog(hwndDlg, 0);
}
return TRUE;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
}
}
return TRUE;
}
return FALSE;
}
How can I change the entire console's background color? I've tried SetConsoleTextAttribute and it only changes the background color of new text.
I effectively want the entire console to turn red when a serious error arises.
Thanks to everyone who attempts to help.
Try something like:
system("color c2");
I think the FillConsoleOutputAttribute function will do what you need. Set it to the starting coordinate of the console, and set nLength to the number of characters in the console (width * length).
BOOL WINAPI FillConsoleOutputAttribute(
__in HANDLE hConsoleOutput,
__in WORD wAttribute,
__in DWORD nLength,
__in COORD dwWriteCoord,
__out LPDWORD lpNumberOfAttrsWritten
);
I know this is an old question, but how about this code:
#include <windows.h>
#include <iostream>
VOID WINAPI SetConsoleColors(WORD attribs);
int main() {
SetConsoleColors(BACKGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
std::cout << "Hello, world!" << std::endl;
std::cin.get();
return 0;
}
VOID WINAPI SetConsoleColors(WORD attribs) {
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFOEX cbi;
cbi.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
GetConsoleScreenBufferInfoEx(hOutput, &cbi);
cbi.wAttributes = attribs;
SetConsoleScreenBufferInfoEx(hOutput, &cbi);
}
As far as I know this code should work on Windows Vista and later versions. By the way, this code is based on this article (mainly the sources on the article): http://cecilsunkure.blogspot.fi/2011/12/windows-console-game-set-custom-color.html
It can be done and the whole background can be set to desired color with SetConsoleScreenBufferInfoEx. The code below should not mess with the previous console output, especially if it used colors:
#include "Windows.h"
void FlashConsoleBackgroundColor(int cntFlashes, int flashInterval_ms, COLORREF color)
{
CONSOLE_SCREEN_BUFFER_INFOEX sbInfoEx;
sbInfoEx.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
HANDLE consoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfoEx(consoleOut, &sbInfoEx);
COLORREF storedBG = sbInfoEx.ColorTable[0];
for (int i = 0; i < cntFlashes; ++i)
{
//-- set BG color
Sleep(flashInterval_ms);
sbInfoEx.ColorTable[0] = color;
SetConsoleScreenBufferInfoEx(consoleOut, &sbInfoEx);
//-- restore previous color
Sleep(flashInterval_ms);
sbInfoEx.ColorTable[0] = storedBG;
SetConsoleScreenBufferInfoEx(consoleOut, &sbInfoEx);
}
}
int main()
{
printf("Flashing console BG: RED");
FlashConsoleBackgroundColor(20, 50, RGB(255, 0, 0));
printf("\rFlashing console BG: ORANGE\n");
FlashConsoleBackgroundColor(10, 100, RGB(255, 105, 0));
return 0;
}
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(out, 0x9 | 0x70);
// text color from 0x1-0x9
// text background color from 0x10-0x90
system("color d1");
/*
Sets the default console foreground and background colors
COLOR [attr]
attr Specifies color attribute of console output
Color attributes are specified by TWO hex digits -- the first
corresponds to the background; the second the foreground. Each digit
can be any of the following values:
0 = Black 8 = Gray
1 = Blue 9 = Light Blue
2 = Green A = Light Green
3 = Aqua B = Light Aqua
4 = Red C = Light Red
5 = Purple D = Light Purple
6 = Yellow E = Light Yellow
7 = White F = Bright White
If no argument is given, this command restores the color to what it was
when CMD.EXE started. This value either comes from the current console
window, the /T command line switch or from the DefaultColor registry
value.
The COLOR command sets ERRORLEVEL to 1 if an attempt is made to execute
the COLOR command with a foreground and background color that are the
same.
/*
This works for me. It changes the background color without messing up the foreground color of text already displayed, by changing each console character cell, one at a time. You will need to get the handle to your console output buffer, which I believe is done with GetStdHandle().
DWORD written = 0;
COORD writeCoord = {0};
WORD attribute;
for (int y = 0; y < consoleBufferLength; y++) // rows
{
for (int x = 0; x < consoleBufferWidth; x++) // columns
{
writeCoord.X = x; writeCoord.Y = y;
ReadConsoleOutputAttribute(consoleOutputHandle, &attribute, 1, writeCoord, &written);
attribute &= 0xFF0F; // zero the background color
attribute |= 12 << 4; // change the background color to red
FillConsoleOutputAttribute(consoleOutputHandle, attribute, 1, writeCoord, &written);
}
}
I have a dirty way here, but gives what you exactly want.
#include <windows.h>
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole,30);
system("CLS");
console