I am converting an old Console app to Win32, and want to replicate the font from the console. The existing codebase forces me to work in C/C++. I'm trying to use CreateFont and CreateFontIndirect to construct an equivalent.
The console font settings are:
I think I understand that raster fonts are not TTF and not directly supported, thanks to this post, How to use DOS font in WinForms application, and arbiter's answer.
I want to construct a fixed font that matches the 12 pixel height and 8 pixel width.
Here's some of the code I've tried so far.
HDC hdc = GetDC(w_child);
// "A 12-point font is 16 pixels tall." -- https://msdn.microsoft.com/en-us/library/windows/desktop/ff684173(v=vs.85).aspx
// "An n-point font is 4/3*n pixels tall"?
// I want 12 pixels tall, so 9-point, right?
int PointSize = 9;
int nHeight = -MulDiv(PointSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
ReleaseDC(w_child, hdc);
HFONT hf = CreateFont(
-12, //nHeight, // Logical height
0, //nHeight * 2/3, // Logical avg character width
0, // Angle of escapement (0)
0, // Baseline angle (0)
FW_DONTCARE, // Weight (0)
FALSE, // Italic (0)
FALSE, // Underline (0)
FALSE, // Strikeout (0)
ANSI_CHARSET, // Character set identifier ??
OUT_DEFAULT_PRECIS, // Output precision
CLIP_DEFAULT_PRECIS, // Clip precision (0)
DEFAULT_QUALITY, // Output quality
FIXED_PITCH, // Pitch and family
"Lucida Console" // Pointer to typeface name string
//"Terminal"
//"Courier New"
);
*/
// Getting stock font, creating an indirect as a logical modification,
// seems to work better.
// ANSI_FIXED_FONT, with lf.lfHeight = 10, results in something clear and
// readable, but a little too large.
// And changing lfHeight seems to have no impact.
HFONT hf = (HFONT)GetStockObject(ANSI_FIXED_FONT);
// SYSTEM_FIXED_FONT at lfHeight = 10 is way too big
HFONT hf = (HFONT)GetStockObject(SYSTEM_FIXED_FONT);
// DEVICE_DEFAULT_FONT at lfHeight = 8 is way too big.
HFONT hf = (HFONT)GetStockObject(DEVICE_DEFAULT_FONT);
LOGFONT lf;
GetObject(hf, sizeof(LOGFONT), &lf);
lf.lfHeight = 12;
HFONT nf = CreateFontIndirect(&lf);
SendMessage(w_child, WM_SETFONT, (WPARAM)nf, TRUE);
Seems like success in my situation:
CreateFont(12, 8, 0, 0, 0, FALSE, 0, 0, OEM_CHARSET, OUT_RASTER_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH, L"System");
Related
This question already has an answer here:
How to automatically set the width and height of static control base on its content?
(1 answer)
Closed 1 year ago.
I'm trying to get the size of the text string using GetTextExtentPoint32. I read the documentation many times and do some research and as far as I know, the below code should give me the correct width and height of the text.
vFontFamily = "Segoe UI"; //font family
vFontSize = 26; //font size
HDC hdc = GetDC(hwnd);
HFONT hFont = CreateFont(
-MulDiv(vFontSize, GetDeviceCaps(hdc, LOGPIXELSY), 72), //calculate the actual cHeight.
0, 0, 0, // normal orientation
FW_NORMAL, // normal weight--e.g., bold would be FW_BOLD
false, false, false, // not italic, underlined or strike out
DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, // select only outline (not bitmap) fonts
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH | FF_SWISS, vFontFamily);
SIZE size;
HFONT oldfont = (HFONT)SelectObject(hdc, hFont);
GetTextExtentPoint32(hdc, L"This is Text", wcslen(L"This is Text"), &size);
width = size.cx; //get width
height = size.cy; //get height
SelectObject(hdc, oldfont); //don't forget to select the old.
DeleteObject(hFont); //always delete the object after creating it.
ReleaseDC(hwnd, hdc); //alway reelase dc after using.
Unfortunately, it doesn't give me the correct size. The width has too much for at least 10% and height is too much for at least 5%. To be exact, the result of width in the text of This is Text is 228 and the height is 62 whereas the more accurate is somewhat near to 190 x 55 I think.
The text is painted using GDI+.
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics g(hdc);
FontFamily theFontFamily(vFontFamily);
Font font(&theFontFamily, vFontSize, FontStyleRegular, UnitPixel);
SolidBrush brush(Color(255, R, G, B));
PointF pointF(0.0f, 0.0f);
TextRenderingHint hint = g.GetTextRenderingHint(); // Get the text rendering hint.
g.SetTextRenderingHint(TextRenderingHintAntiAlias); // Set the text rendering hint to TextRenderingHintAntiAlias.
g.DrawString(L"This is Text", strlen("This is Text"), &font, pointF, &brush);
EndPaint(hwnd, &ps);
return TRUE;
}
I'm thinking maybe it's somehow related to how I drew the text because, in the paint message, I used Font instead of HFONT. But that doesn't make any sense, right? As long as I set the correct font family and font size before GetTextExtentPoint32 then it should give me the exact width and height of the text. What do you think?
You are using GDI to measure your text while you are using GDI+ to draw it.
GDI+ is an improvement on GDI and there are differences between the two when you use it to render text, etc. Another thing is the DPI Awareness, when measuring text, you'll need to make sure to handle the scaling too. Your method will have larger result compare to the actual width and height when you drew it using GDI+ and if you don't handle the DPI properly on your measurement.
Try using Graphics::MeasureString and see if you get the expected width and height. Do it the same way how you draw your string just like what AlanBirtles said in the comment.
For example,
HDC hdc = GetDC(hwnd); //get dc of your handle.
Graphics graphics(hdc); //setup graphics.
FontFamily theFontFamily(vFontFamily); //setup your font family.
Font font(&theFontFamily, vFontSize, FontStyleRegular, UnitPixel); //create the font the same way how you do it on your paint message.
PointF pointF(0.0f, 0.0f); //use PointF instead of RectF since thats how you paint it.
RectF boundRect; //setup boundRect to get the width and height.
graphics.MeasureString(text, -1, &font, pointF, &boundRect); //Measure the text of the string
//or
//graphics.MeasureString(L"This is Text", strlen("This is Text"), pointF, &boundRect);
width = boundRect.Width; //get the width of text from boundRect.
height = boundRect.Height; //get the height of text from boundRect.
DeleteObject(&font); //delete the font.
ReleaseDC(LabelHandle, hdc); //always reelase dc after using.
If you need more explanation, the following might be help you.
How to automatically set the width and height of static control base on its content?
GDI+ font size difference under different processes on same machine
DPI-awareness is really needed?
How to draw a GDI + text independent of DPI
https://stackoverflow.com/questions/4551224/what-is-the-difference-between-gdi-and-gdi#:~:text=GDI%2B%20is%20object%20oriented%2C%20and,in%20some%20ways%20GDI%20usage.&text=GDI%2B%20is%20an%20improvement%20on,and%20more%20image%20format%20support.
I have a MFC C++ (unmanaged) Windows application that uses a “standard” icon in the System Tray. This icon was created & edited using Visual Studio and is 32x32 pixels with only 4bit colour (according to VS's Resource Editor).
With Visual Studio, I also set a transparent background (shown as white in the “before” image).
I wish to dynamically change the icon by writing 2 digits (1-99) on top of it.
Using the code below (based on that in this question: How to draw text with transparency using GDI?) to superimpose “55” in yellow on the icon, it works except that the transparency disappears (it appears black in the “after” image and on the System Tray). My actual code differs very very slightly in that the font size (20), font name (Courier New), text colour (yellow - RGB(255, 255, 0)) and the numeric value (55) are run-time variables rather than fixed values.
Any suggestions on how to make the background remain transparent as far as the System Tray is concerned gratefully received.
These images have been captured using MS’s Snipping tool with the image open in MS Paint as a 32x32 icon wouldn't be very visible as-is.
Before Image:
After image:
Code:
void CreateNewIcon(HICON &hNewIcon, HICON hBackgroundIcon)
{
::DestroyIcon(hNewIcon);
// First create font
LOGFONT lf = { 0 };
lf.lfHeight = -20;
lf.lfWeight = FW_BOLD;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfQuality = CLEARTYPE_QUALITY;
wmemset(lf.lfFaceName, 0, LF_FACESIZE);
lstrcpy(lf.lfFaceName, L"Courier New");
HFONT hFont = ::CreateFontIndirect(&lf);
ICONINFO ii = { 0 };
::GetIconInfo(hBackgroundIcon, &ii);
BITMAP bm = { 0 };
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = { bm.bmWidth, bm.bmHeight };
HDC hDc = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 255, 0));
::TextOut(hMemDC, 0, 8, L"55", 2);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
// We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = { 0 };
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
// Create updated icon
hNewIcon = ::CreateIconIndirect(&ii2);
// Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(NULL, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
}
There are multiple issues with our code:
You are trying to draw cleartype-quality text over transparent icon part. But cleartype font rendering must be performed over opaque background because it needs to inspect background color. So you should switch to Anitialiased quality (or to not antialiased quality) or provide opaque background for your text.
When you create hBmpMsk you skip it's content initialization by supplying NULL for bits pointer, so resulting icon will actually have completely random transparency. You need to fill this mask bitmat appropriately.
Also you probably need to switch to higher bit depth because monochrome bitmap mask can't handle semitransparent parts of antialiased text.
update
I think you should draw cleartype text but with opaque background, then get text rectangle using something like GetTextExtentPoint32, then copy data from the original bitmap mask into hBmpMsk and then finally fill white (text) rectangle on it so the new icon will preserve transparency from original and has opaque text block.
Thanks for all the help from VTT without which I wouldn't have been able to get this far. This appears to work for me.
void CreateNewIcon(HICON &hNewIcon, HICON hBackgroundIcon)
{
::DestroyIcon(hNewIcon);
HDC hDc = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hDc);
// Load up background icon
ICONINFO ii = { 0 };
::GetIconInfo(hBackgroundIcon, &ii);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
// Create font
LOGFONT lf = { 0 };
lf.lfHeight = -20;
lf.lfWeight = FW_BOLD;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfQuality = ANTIALIASED_QUALITY;
wmemset(lf.lfFaceName, 0, LF_FACESIZE);
lstrcpy(lf.lfFaceName, L"Courier New");
HFONT hFont = ::CreateFontIndirect(&lf);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
// Write text
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 255, 0));
::TextOut(hMemDC, 0, 8, L"55", 2);
// Set up mask
HDC hMaskDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldMaskBmp = ::SelectObject(hMaskDC, ii.hbmMask);
// Also write text on here
HGDIOBJ hOldMaskFont = ::SelectObject(hMaskDC, hFont);
::SetBkMode(hMaskDC, TRANSPARENT);
::SetTextColor(hMaskDC, RGB(255, 255, 0));
::TextOut(hMaskDC, 0, 8, L"55", 2);
// Get handle to create mask bitmap
HBITMAP hMaskBmp = (HBITMAP)::SelectObject(hMaskDC, hOldMaskBmp);
// Use new icon bitmap with text and new mask bitmap with text
ICONINFO ii2 = { 0 };
ii2.fIcon = TRUE;
ii2.hbmMask = hMaskBmp;
ii2.hbmColor = ii.hbmColor;
// Create updated icon
hNewIcon = ::CreateIconIndirect(&ii2);
// Cleanup bitmap mask
::DeleteObject(hMaskBmp);
::DeleteDC(hMaskDC);
// Cleanup font
::SelectObject(hMaskDC, hOldMaskFont);
::SelectObject(hMemDC, hOldFont);
::DeleteObject(hFont);
// Release background bitmap
::SelectObject(hMemDC, hOldBmp);
// Delete background icon bitmap info
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteDC(hMemDC);
::ReleaseDC(NULL, hDc);
}
My goal is to dynamically put some arbitrary text into an HICON image (at runtime.) I'm using the following code:
//Error checks are omitted for brevity
//First create font
LOGFONT lf = {0};
lf.lfHeight = -58;
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_PRECIS; //Use TrueType fonts for anti-alliasing
lf.lfQuality = CLEARTYPE_QUALITY;
lstrcpy(lf.lfFaceName, L"Segoe UI");
HFONT hFont = ::CreateFontIndirect(&lf);
//HICON hIcon = original icon to use as a source
//I'm using a large 256x256 pixel icon
hIcon = (HICON)::LoadImage(theApp.m_hInstance, MAKEINTRESOURCE(IDI_ICON_GREEN_DIAMOND), IMAGE_ICON, 256, 256, LR_DEFAULTCOLOR);
ICONINFO ii = {0};
::GetIconInfo(hIcon, &ii);
BITMAP bm = {0};
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = {bm.bmWidth, bm.bmHeight};
HDC hDc = ::GetDC(hWnd);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 0, 0)); //Red text
//Draw text
//NOTE that DrawText API behaves in a similar way
::TextOut(hMemDC, 0, 0, L"Hello", 5);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
//We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = {0};
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
//Create updated icon
HICON hIcon2 = ::CreateIconIndirect(&ii2);
//Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(hWnd, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
and then I can display the icon in my window from OnPaint() handler (so that I can see how it turns out) as such:
::DrawIconEx(dc.GetSafeHdc(), 0, 0,
hIcon2,
256, 256, NULL,
::GetSysColorBrush(COLOR_BTNFACE),
DI_NORMAL);
So here's what I get:
To see what's going on pixel-wise in my hIcon2 I called GetDIBits on its ii.hbmColor from the code above. The resulting pixel array where my word "Hello" was supposed to be shown looked like this:
The pixels are encoded as BGRA in that memory dump, so the 4th byte in each DWORD stands for transparency: 0=transparent, FF=opaque. But in this case TextOut doesn't fill out transparency, or leaves it as 0, which is interpreted as "fully transparent." Instead it seems to pre-multiply it into the RGB colors themselves.
Note that if I keep looking further down the same bitmap, where the green diamond begins, the image pixels seem to have transparency bytes set correctly:
Any idea how to draw text so that the API could set those transparency bytes?
EDIT: As was suggested below I tried the following GDI+ method:
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
Graphics grpx(hMemDC);
RectF rcfTxt(0.0f, 0.0f, (REAL)szBmp.cx, (REAL)szBmp.cy);
Font gdiFont(L"Segoe UI", 58.0f, FontStyleRegular, UnitPixel);
SolidBrush gdiBrush(Color(255, 0, 0));
StringFormat gdiSF;
gdiSF.SetAlignment(StringAlignmentNear);
gdiSF.SetFormatFlags(StringFormatFlagsNoWrap);
gdiSF.SetHotkeyPrefix(HotkeyPrefixNone);
//The reason I was using GDI was because I was setting
//spacing between letters using SetTextCharacterExtra()
//Unfortunately with GDI+ this does not work!
HDC hTmpDC = grpx.GetHDC();
::SetTextCharacterExtra(hTmpDC, -4); //This doesn't do anything!
grpx.ReleaseHDC(hTmpDC);
grpx.DrawString(L"Hello", 5, &gdiFont, rcfTxt, &gdiSF, &gdiBrush);
::SelectObject(hMemDC, hOldBmp);
and besides not being able to set character spacing (which I could with GDI using SetTextCharacterExtra) here's what I got (slightly enlarged for visibility):
So clearly still an issue with transparency.
Taken from an old post by Microsoft MVP Mike D Sutton here.
When you create a DC it initially has default 'stock' objects selected
into it, including the stock 1*1*1 Bitmap. Since there is a Bitmap
already selected into the DC when you call DrawText() it will still
try and render to it even though pretty much everything (apart from
one pixel) will be clipped.
What you need to do is to create a Bitmap,
either DDB or DIBSection, and select that into your DC before drawing
to it.
First though you need to find the size of your Bitmap since you
want it large enough to display your text in, so for that you use the
DrawText() call again on the initial DC but include the DT_CALCRECT
flag. What this does is rather than drawing anything it simply
measures how large the text is and dumps that into the RECT you pass
the call. From here you can go ahead and create your DIBSection using
those dimensions and select it into your DC. Finally perform your
existing DrawText ()call (you may also want to use SetBkMode/Color())
which will render the text to the DIBSection from which you can get at
the data.
This seems to work pretty well here:
HBITMAP CreateAlphaTextBitmap(LPCSTR inText, HFONT inFont, COLORREF inColour) {
int TextLength = (int)strlen(inText);
if (TextLength <= 0) return NULL;
// Create DC and select font into it
HDC hTextDC = CreateCompatibleDC(NULL);
HFONT hOldFont = (HFONT)SelectObject(hTextDC, inFont);
HBITMAP hMyDIB = NULL;
// Get text area
RECT TextArea = {0, 0, 0, 0};
DrawText(hTextDC, inText, TextLength, &TextArea, DT_CALCRECT);
if ((TextArea.right > TextArea.left) && (TextArea.bottom > TextArea.top)) {
BITMAPINFOHEADER BMIH;
memset(&BMIH, 0x0, sizeof(BITMAPINFOHEADER));
void *pvBits = NULL;
// Specify DIB setup
BMIH.biSize = sizeof(BMIH);
BMIH.biWidth = TextArea.right - TextArea.left;
BMIH.biHeight = TextArea.bottom - TextArea.top;
BMIH.biPlanes = 1;
BMIH.biBitCount = 32;
BMIH.biCompression = BI_RGB;
// Create and select DIB into DC
hMyDIB = CreateDIBSection(hTextDC, (LPBITMAPINFO)&BMIH, 0, (LPVOID*)&pvBits, NULL, 0);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTextDC, hMyDIB);
if (hOldBMP != NULL) {
// Set up DC properties
SetTextColor(hTextDC, 0x00FFFFFF);
SetBkColor(hTextDC, 0x00000000);
SetBkMode(hTextDC, OPAQUE);
// Draw text to buffer
DrawText(hTextDC, inText, TextLength, &TextArea, DT_NOCLIP);
BYTE* DataPtr = (BYTE*)pvBits;
BYTE FillR = GetRValue(inColour);
BYTE FillG = GetGValue(inColour);
BYTE FillB = GetBValue(inColour);
BYTE ThisA;
for (int LoopY = 0; LoopY < BMIH.biHeight; LoopY++) {
for (int LoopX = 0; LoopX < BMIH.biWidth; LoopX++) {
ThisA = *DataPtr; // Move alpha and pre-multiply with RGB
*DataPtr++ = (FillB * ThisA) >> 8;
*DataPtr++ = (FillG * ThisA) >> 8;
*DataPtr++ = (FillR * ThisA) >> 8;
*DataPtr++ = ThisA; // Set Alpha
}
}
// De-select bitmap
SelectObject(hTextDC, hOldBMP);
}
}
// De-select font and destroy temp DC
SelectObject(hTextDC, hOldFont);
DeleteDC(hTextDC);
// Return DIBSection
return hMyDIB;
}
If you need an example of how to call it then try something like this
(inDC is the DC to render to):
void TestAlphaText(HDC inDC, int inX, int inY) {
const char *DemoText = "Hello World!\0";
RECT TextArea = {0, 0, 0, 0};
HFONT TempFont = CreateFont(50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial\0");
HBITMAP MyBMP = CreateAlphaTextBitmap(DemoText, TempFont, 0xFF);
DeleteObject(TempFont);
if (MyBMP) { // Create temporary DC and select new Bitmap into it
HDC hTempDC = CreateCompatibleDC(inDC);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTempDC, MyBMP);
if (hOldBMP) {
BITMAP BMInf; // Get Bitmap image size
GetObject(MyBMP, sizeof(BITMAP), &BMInf);
// Fill blend function and blend new text to window
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0x80;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(inDC, inX, inY, BMInf.bmWidth, BMInf.bmHeight,
hTempDC, 0, 0, BMInf.bmWidth, BMInf.bmHeight, bf);
// Clean up
SelectObject(hTempDC, hOldBMP);
DeleteObject(MyBMP);
DeleteDC(hTempDC);
}
}
}
All credit to answer and code go to original posters on that forum, I've simply reposted it so that this answer will be valid if the links die.
This reply is coming almost 3 years after the question was posted, but people still consult these things long into the future. So I'll explain what's happening.
DrawText (and other GDI text functions) will work on a transparent bitmap. The text is not coming out black even though it displays that way. The alpha channel is set to 0 on all pixels the text draws to, overriding whatever alpha you had set previously. If you set an alpha value in SetTextColor the text will render all black. If you're feeling ambitious you can run through pixel by pixel and target anything not your fill color (which requires a single fill color) but the problem then becomes one of the nature of ClearType being overridden and all alphas are set to whatever you set them to. The text ends up looking very funky. If you use a constant alpha for your background fill you can simply do a blanket run across the entire bitmap's bits after the text is drawn and reset all the alpha values. Since you have to read a byte to determine if it's background or not, you might as well just set every pixel's alpha to whatever the standard alpha is for that image and bypass the slow compares. This works reasonably well and I've found it to be very acceptable. In this day and age, MS should have taken care of this long ago but it's not to be.
https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-antialiasing-with-text-use
Gdiplus::Bitmap bmp( your_Width, your_Height, PixelFormat64bppARGB);
//PixelFormat64bppARGB ARGB needed
FontFamily fontFamily(L"Arial");
Font font(&fontFamily, 29, FontStyleRegular, UnitPoint);
Gdiplus::RectF rectF(00.0f, 10.0f, your_Width, your_Height);
StringFormat stringFormat;
SolidBrush solidBrush(Color(63, 0, 0, 255));
stringFormat.SetAlignment(StringAlignmentCenter);
//solidBrush Color(63, 0, 0, 255) ARGB neede
graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics.DrawString("your_text", -1, &font, rectF, &stringFormat, &solidBrush);
//TextRenderingHintAntiAlias this needed
I've followed Microsoft's tutorial on creating a Device Context, and I've tried looking around the internet for a decent source (apparently, MFC is a mystical thing). The following successfully prints out "Hello, World!"; except it's extremely tiny.
How can I send a CImage to the printer, rather than text?
And how could I get the text's size to be bigger than a couple millimeters?
I've scoured MSDN, but everything is either outdated (like the example code I am using), or just not well documented.
// get the default printer
CPrintDialog dlg(FALSE);
dlg.GetDefaults();
// is a default printer set up?
HDC hdcPrinter = dlg.GetPrinterDC();
if (hdcPrinter == NULL)
{
//MessageBox(_T("Buy a printer!"));
}
else
{
// create a CDC and attach it to the default printer
CDC dcPrinter;
dcPrinter.Attach(hdcPrinter);
// call StartDoc() to begin printing
DOCINFO docinfo;
memset(&docinfo, 0, sizeof(docinfo));
docinfo.cbSize = sizeof(docinfo);
docinfo.lpszDocName = _T("CDC::StartDoc() Code Fragment");
// if it fails, complain and exit gracefully
if (dcPrinter.StartDoc(&docinfo) < 0)
{
//MessageBox(_T("Printer wouldn't initalize"));
}
else
{
// start a page
if (dcPrinter.StartPage() < 0)
{
//MessageBox(_T("Could not start page"));
dcPrinter.AbortDoc();
}
else
{
// actually do some printing
//CGdiObject* pOldFont = dcPrinter.SelectStockObject(SYSTEM_FONT);
dcPrinter.SetMapMode(MM_HIENGLISH);
auto font = CreateFont(
3'000, // nHeight
1'500, // nWidth
0, // nEscapement
0, // nOrientation
FW_NORMAL, // nWeight
FALSE, // bItalic
FALSE, // bUnderline
0, // cStrikeOut
ANSI_CHARSET, // nCharSet
OUT_DEFAULT_PRECIS, // nOutPrecision
CLIP_DEFAULT_PRECIS, // nClipPrecision
DEFAULT_QUALITY, // nQuality
DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily
_T("Arial")); // lpszFacename
dcPrinter.SelectObject(&font);
dcPrinter.TextOut(450, 450, _T("Hello World!"), 12);
dcPrinter.EndPage();
dcPrinter.EndDoc();
//dcPrinter.SelectObject(pOldFont);
}
}
}
Tiny Text Problem
The problem is that, by default, the size of a font is specified in device-dependent units and printers are generally much higher resolution that a screen. So if you've created a font that is 20 pixels high on the screen (which might have 96 pixels per inch) when you try to use that font on a printer, which maybe has 300 or 600 dots per inch, your text looks really small.
As another answer shows, one idea is to change the mapping mode so that the printer uses units that are closer to what is on the screen.
An alternative way is to create a new font with an appropriate size (the lfHeight field in the LOGFONT structure) based on the DPI of the printer, which you can determine with the GetDeviceCaps function. This can be handy if you want a particular font size, like 14 point text.
LOGFONT lf = {0};
lf.lfHeight = -MulDiv(point_size, ::GetDeviceCaps(dcPrinter, LOGPIXELSY), 72);
// init other field of lf as you like
HFONT hfontPrinter = ::CreateFontIndirect(&lf);
HFONT hfontOld = ::SelectObject(hdcPrinter, hfontPrinter);
// now draw to the hdcPrinter
::SelectObject(hdcPrinter, hfontOld);
::DeleteObject(hfontPrinter);
Sending a CImage
I don't use MFC, but it looks like you can just call CImage::StretchBlt with the printer DC. Once again, you'll probably have to take the printer's much higher resolution into account when you choose the target coordinates.
Use CFont::CreatePointFont() or CFont::CreatePointFontIndirect() to create a font that is reasonable. Most printers are 600 DPI. Most screens are 96 DPI. A 12 point font on the screen is basically a 2 point font and illegible on a printer.
Create the font and select it into your DC. Do not forget to select it out of the DC after using it and before destroying your DC (CDC class). (The CDC destructor automatically deletes the HDC).
Here is the problem:
dcPrinter.SetMapMode(MM_TEXT);
MM_TEXT maps one logical point to one device point; considering typical resolution of 600 DPI for a printer, your stuff will be few times smaller that on the screen.
Use MM_HIENGLISH or some other device-independent mode; here is MSDN link.
I have used the following to successfully print "Hello World!" and "Have a Nice Day!" to the right of a 200x200 monochrome bitmap (MyLogo.bmp) placed at the origin of the printer page (I am using a black & white thermal printer):
CDC printDC( GetMyPrintDC() ); // e.g. as per original code
DOCINFO di( GetMyDocInfo() ); // e.g. as per original code
printDC.StartDoc( &di );
ATL::CImage logo;
logo.Load( "MyLogo.bmp" );
const BOOL result( logo.Draw( printDC.GetSafeHdc(), CPoint( 0, 0 ) ) );
CFont myFont, *old;
myFont.CreatePointFont(100, "Courier New", &printDC);
old = printDC.SelectObject(&myFont);
printDC.TextOut( 250, 50, " Hello World!" );
printDC.TextOut( 250, 150, "Have a nice Day!" );
printDC.SelectObject( old );
myFont.DeleteObject();
printDC.EndPage();
printDC.EndDoc();
printDC.DeleteDC();
The three indented lines highlight all that is required to render a CImage on my printer. Vary the parameter in CreatePointFont() to size the (otherwise tiny) text to suit.
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);